Julius Plenz – Blog

cgit and setuid

I'm still in the process of setting up my new server. Today, I migrated the Git repositories. I wanted a more secure setup, that is I don't want my web server to be able to read the repositories. (It spawns CGit, which has to read them somehow.)

So I created all repositories below /var/git/repositories, such that they are only readable by the system user "git".

However, that presents a problem to the CGit CGI: It cannot access the repositories any more. My first approach was: Just chown git.git /usr/lib/cgi-bin/cgit.cgi and set the setuid bit on it.

But, alas, that will only make the CGit binary run with effective user ID "git". What's needed to actually access the files is a real user ID "git".

The only way to set the real user ID is to call setuid() with effective root privileges. So I created a wrapper that

  1. searches for git's user ID
  2. setuid()s to that ID (real an effective)
  3. executes the actual CGit CGI

That's what I came up with:

 #include <unistd.h>
 #include <sys/types.h>
 #include <pwd.h>
 #include <string.h>

int main(int argc, char *argv[])
{
    struct passwd *p;
    uid_t git_uid = 0;

    while((p = getpwent()) != NULL) {
        if(strcmp(p->pw_name, "git"))
            continue;
        /* got user "git" */
        git_uid = p->pw_uid;
    }

    endpwent();

    if(!git_uid)
        return 1;

    setuid(git_uid);
    execv("/usr/lib/cgi-bin/cgit.cgi", argv);

    return 0;
}

Provide it with a Makefile... (beware, use real tabs!)

default:
        gcc -Wall -o cgit-as-git.cgi cgit-as-git.c

install:
        install -o root -g root -m 4755 cgit-as-git.cgi /usr/lib/cgi-bin

... and change the Lighttpd configuration accordingly:

$HTTP["host"] == "git.plenz.com" {
    setenv.add-environment += ( "CGIT_CONFIG" => "/etc/cgit/plenz.com.conf" )
    alias.url = (
        "/cgit.css" => "/usr/share/cgit/cgit.css",
        "/cgit.png" => "/usr/share/cgit/cgit.png",
        "/cgit.cgi" => "/usr/lib/cgi-bin/cgit-as-git.cgi",
        "/"         => "/usr/lib/cgi-bin/cgit-as-git.cgi",
    )
    cgi.assign = ( ".cgi" => "" )
    url.rewrite-once = (
        "^/cgit\.(css|png)" => "$0",
        "^/.+" => "/cgit.cgi$0" 
    )
}

Done! Lighttpd can now call CGit as if it was a "usual" binary, but it will get wrapped and get the real and effective user ID of the system's user "git".

N.B.: If you want to use the wrapper as well, you might want to change the (hard coded) user name "git" and the binary (see execv call).

posted 2011-09-25 tagged git, cgit and lighttpd