0

Question: how do you edit how git calls a hook?

TL;DR - my hosting platform's security is weird and I need to figure out how to edit how git calls the post-receive hook.

I'm trying to setup automatic deployment via GIT, but the main way people suggest you do that is with a post-receive script. If I cd into hooks and do

$ sh post-receive

, it works just fine, but if I

$ ./post-receive

, it will complain about permissions though it has

$ chmod 777 post-receive

(which you can check out with $ ls -l).

Here's the message I got from support: "The reason is the SSH environment is mounted in as a non-executable environment, for security reasons. This means that you can't run scripts just by the path.

As an example, running: /www/repos/webprosjekt18.git/hooks/post-receive will not work, it will give a permission error.

Running: bash /www/repos/webprosjekt18.git/hooks/post-receive will work just fine (depending on what that script does ofc, but it will at least run the file).

We think git by default run hooks by calling: /www/repos/webprosjekt18.git/hooks/post-receive which will fail. "

remote script:

mkdir repos
cd repos
git init --bare
cat > hooks/post-receive <<END
#!/bin/sh
echo "executing post-receive hook"
END
chmod 777 hooks/post-receive
cd hooks
sh post-receive
Philip Aarseth
  • 301
  • 4
  • 16
  • How do they do this? What happens if you execute `bash` from the shell you get by logging in using ssh. Still not able to execute scripts? (Does this affect just scripts?) What if you do `PATH=$PATH:.` to add the current directory into the `PATH`? – sneep Mar 22 '18 at 14:50
  • What do you mean how do they do this? as stated in the text, the script is executable by stating what script language it is when calling on it, but I guess git doesn't do that. If i cd into hooks and do $ sh post-receive, it works fine. Also not sure where you suggest I do the PATH stuff. – Philip Aarseth Mar 22 '18 at 14:58
  • Ah sorry. I think they must be using some weird trickery to do this. So I'm wondering if they're maybe giving you some kind of special shell? 1) After logging in by ssh, try executing e.g. `/usr/bin/bash` (or any other shell, e.g. `/bin/sh`, `tcsh`, etc.). Will the resulting shell have the same limitations? 2) After logging in through ssh, try executing `PATH=$PATH:.`. If you do this, you should be able to execute scripts in the current path by doing `script.sh` rather than `./script.sh`. Does this work? 3) What is the output of `which bash` 4) Is `.bashrc`/`.bash_profile` executed on login? – sneep Mar 22 '18 at 15:07
  • BTW, git just `execve`s the hooks scripts. I'm pretty sure there is a way around your hosting platform's security. For example, do binary executables work? If yes, you could e.g. use a tiny C program like `int main() { system("bash script.sh"); }` as a hook. – sneep Mar 22 '18 at 16:23

2 Answers2

1

As sneep noted in a comment, Git essentially just does a fork followed by execve of the hook path, once it's decided to run some hook. (See below for more about "decided to run some hook".) That means it's up to the kernel to decide whether to allow the exec operation.

The specific source code involved is here. Note the test for ENOEXEC, but not for EACCES.

If you look at the Linux mount documentation, you will see that the noexec option prohibits execution (exec operations) of any file on that mounted file system. (BSD's mount has a similar option, in case the hosting system is BSD-based.) The error returned for this case is EACCES, not ENOEXEC. This means your hosting system's analysis, that you literally cannot do this at all, is correct.


Interestingly, if Git would run a hook that does not have the x bits set, the code section called out above could get ENOEXEC in some cases (not these—you'd want to mess with the "magic number" to trigger this) and the second code path that runs the shell on the hook could actually work. However, the find_hook code specifically requires that access(path, X_OK) succeed. This needs both the x bits set and the file system mounted without noexec.

You could try building a variant version of Git that changes these tests and have your hosting company install it as their Git version, but if they would be willing to do that, they would probably be willing to put your repositories on a file system that allows exec.

torek
  • 448,244
  • 59
  • 642
  • 775
1

This isn't really an answer, as it heavily builds on @torek's suggestion that it's probably using the NOEXEC mount option. So how about this workaround: you could add a hook that is a symbolic link to /usr/bin/script (symbolic links to other filesystems are executable even with NOEXEC). script will execute bash, and bash will think it's in a terminal. (If you just symlink to /bin/bash in the hooks directory, bash will give up seeing that it cannot open /dev/tty, but script takes care of this for us.) If we put some code in ~/.bashrc to determine if this was called as a git hook, we execute the real script. So we could put this right at the top of /.bashrc:

pppid=$(ps -o ppid= $PPID) # determine grandparent PID
ppproc=$(ps -o comm= $pppid) # determine grandparent process name
if [ "$ppproc" == "git" ]; then # is it 'git'?
    echo running from git @ `date` # call script here
    exit
else
    echo not running from git # you might want to remove this
fi

Example:

$ touch test15
$ git add test15
$ git commit -m "Test15"
Script started, file is typescript
running from git @ Fri Mar 23 13:19:54 JST 2018
Script done, file is typescript
[master 608083b] Test15
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 test15

(This creates a file called typescript in your Git directory.)

sneep
  • 1,828
  • 14
  • 19
  • That's a rather impressive trick. It's not clear to me whether someone might consider following symlinks outside the mount point resulting in "allowed to exec" to be a bug. It doesn't seem to open a lot of cans-of-worms, so it might be OK after all, but using your trick, it does enable some things someone might think are impossible (I did think it was impossible!). – torek Mar 26 '18 at 01:45