daemon: recognize hidden request arguments

A normal request to git-daemon is structured as
"command path/to/repo\0host=..\0" and due to a bug introduced in
49ba83fb6 (Add virtualization support to git-daemon, 2006-09-19) we
aren't able to place any extra arguments (separated by NULs) besides the
host otherwise the parsing of those arguments would enter an infinite
loop.  This bug was fixed in 73bb33a94 (daemon: Strictly parse the
"extra arg" part of the command, 2009-06-04) but a check was put in
place to disallow extra arguments so that new clients wouldn't trigger
this bug in older servers.

In order to get around this limitation teach git-daemon to recognize
additional request arguments hidden behind a second NUL byte.  Requests
can then be structured like:
"command path/to/repo\0host=..\0\0version=1\0key=value\0".  git-daemon
can then parse out the extra arguments and set 'GIT_PROTOCOL'
accordingly.

By placing these extra arguments behind a second NUL byte we can skirt
around both the infinite loop bug in 49ba83fb6 (Add virtualization
support to git-daemon, 2006-09-19) as well as the explicit disallowing
of extra arguments introduced in 73bb33a94 (daemon: Strictly parse the
"extra arg" part of the command, 2009-06-04) because both of these
versions of git-daemon check for a single NUL byte after the host
argument before terminating the argument parsing.

Signed-off-by: Brandon Williams <bmwill@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Brandon Williams 2017-10-16 10:55:25 -07:00 committed by Junio C Hamano
parent 373d70efb2
commit dfe422d04d

View File

@ -282,7 +282,7 @@ static const char *path_ok(const char *directory, struct hostinfo *hi)
return NULL; /* Fallthrough. Deny by default */ return NULL; /* Fallthrough. Deny by default */
} }
typedef int (*daemon_service_fn)(void); typedef int (*daemon_service_fn)(const struct argv_array *env);
struct daemon_service { struct daemon_service {
const char *name; const char *name;
const char *config_name; const char *config_name;
@ -363,7 +363,7 @@ error_return:
} }
static int run_service(const char *dir, struct daemon_service *service, static int run_service(const char *dir, struct daemon_service *service,
struct hostinfo *hi) struct hostinfo *hi, const struct argv_array *env)
{ {
const char *path; const char *path;
int enabled = service->enabled; int enabled = service->enabled;
@ -422,7 +422,7 @@ static int run_service(const char *dir, struct daemon_service *service,
*/ */
signal(SIGTERM, SIG_IGN); signal(SIGTERM, SIG_IGN);
return service->fn(); return service->fn(env);
} }
static void copy_to_log(int fd) static void copy_to_log(int fd)
@ -462,25 +462,34 @@ static int run_service_command(struct child_process *cld)
return finish_command(cld); return finish_command(cld);
} }
static int upload_pack(void) static int upload_pack(const struct argv_array *env)
{ {
struct child_process cld = CHILD_PROCESS_INIT; struct child_process cld = CHILD_PROCESS_INIT;
argv_array_pushl(&cld.args, "upload-pack", "--strict", NULL); argv_array_pushl(&cld.args, "upload-pack", "--strict", NULL);
argv_array_pushf(&cld.args, "--timeout=%u", timeout); argv_array_pushf(&cld.args, "--timeout=%u", timeout);
argv_array_pushv(&cld.env_array, env->argv);
return run_service_command(&cld); return run_service_command(&cld);
} }
static int upload_archive(void) static int upload_archive(const struct argv_array *env)
{ {
struct child_process cld = CHILD_PROCESS_INIT; struct child_process cld = CHILD_PROCESS_INIT;
argv_array_push(&cld.args, "upload-archive"); argv_array_push(&cld.args, "upload-archive");
argv_array_pushv(&cld.env_array, env->argv);
return run_service_command(&cld); return run_service_command(&cld);
} }
static int receive_pack(void) static int receive_pack(const struct argv_array *env)
{ {
struct child_process cld = CHILD_PROCESS_INIT; struct child_process cld = CHILD_PROCESS_INIT;
argv_array_push(&cld.args, "receive-pack"); argv_array_push(&cld.args, "receive-pack");
argv_array_pushv(&cld.env_array, env->argv);
return run_service_command(&cld); return run_service_command(&cld);
} }
@ -573,8 +582,11 @@ static void canonicalize_client(struct strbuf *out, const char *in)
/* /*
* Read the host as supplied by the client connection. * Read the host as supplied by the client connection.
*
* Returns a pointer to the character after the NUL byte terminating the host
* arguemnt, or 'extra_args' if there is no host arguemnt.
*/ */
static void parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen) static char *parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen)
{ {
char *val; char *val;
int vallen; int vallen;
@ -602,6 +614,43 @@ static void parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen)
if (extra_args < end && *extra_args) if (extra_args < end && *extra_args)
die("Invalid request"); die("Invalid request");
} }
return extra_args;
}
static void parse_extra_args(struct hostinfo *hi, struct argv_array *env,
char *extra_args, int buflen)
{
const char *end = extra_args + buflen;
struct strbuf git_protocol = STRBUF_INIT;
/* First look for the host argument */
extra_args = parse_host_arg(hi, extra_args, buflen);
/* Look for additional arguments places after a second NUL byte */
for (; extra_args < end; extra_args += strlen(extra_args) + 1) {
const char *arg = extra_args;
/*
* Parse the extra arguments, adding most to 'git_protocol'
* which will be used to set the 'GIT_PROTOCOL' envvar in the
* service that will be run.
*
* If there ends up being a particular arg in the future that
* git-daemon needs to parse specificly (like the 'host' arg)
* then it can be parsed here and not added to 'git_protocol'.
*/
if (*arg) {
if (git_protocol.len > 0)
strbuf_addch(&git_protocol, ':');
strbuf_addstr(&git_protocol, arg);
}
}
if (git_protocol.len > 0)
argv_array_pushf(env, GIT_PROTOCOL_ENVIRONMENT "=%s",
git_protocol.buf);
strbuf_release(&git_protocol);
} }
/* /*
@ -695,6 +744,7 @@ static int execute(void)
int pktlen, len, i; int pktlen, len, i;
char *addr = getenv("REMOTE_ADDR"), *port = getenv("REMOTE_PORT"); char *addr = getenv("REMOTE_ADDR"), *port = getenv("REMOTE_PORT");
struct hostinfo hi; struct hostinfo hi;
struct argv_array env = ARGV_ARRAY_INIT;
hostinfo_init(&hi); hostinfo_init(&hi);
@ -716,8 +766,9 @@ static int execute(void)
pktlen--; pktlen--;
} }
/* parse additional args hidden behind a NUL byte */
if (len != pktlen) if (len != pktlen)
parse_host_arg(&hi, line + len + 1, pktlen - len - 1); parse_extra_args(&hi, &env, line + len + 1, pktlen - len - 1);
for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { for (i = 0; i < ARRAY_SIZE(daemon_service); i++) {
struct daemon_service *s = &(daemon_service[i]); struct daemon_service *s = &(daemon_service[i]);
@ -730,13 +781,15 @@ static int execute(void)
* Note: The directory here is probably context sensitive, * Note: The directory here is probably context sensitive,
* and might depend on the actual service being performed. * and might depend on the actual service being performed.
*/ */
int rc = run_service(arg, s, &hi); int rc = run_service(arg, s, &hi, &env);
hostinfo_clear(&hi); hostinfo_clear(&hi);
argv_array_clear(&env);
return rc; return rc;
} }
} }
hostinfo_clear(&hi); hostinfo_clear(&hi);
argv_array_clear(&env);
logerror("Protocol error: '%s'", line); logerror("Protocol error: '%s'", line);
return -1; return -1;
} }