2014-01-10 03:59:34 +00:00
|
|
|
/* parsing.c: parsing of config files
|
2006-12-10 21:41:14 +00:00
|
|
|
*
|
2014-01-08 14:10:49 +00:00
|
|
|
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
|
2006-12-10 21:41:14 +00:00
|
|
|
*
|
|
|
|
* Licensed under GNU General Public License v2
|
|
|
|
* (see COPYING for full license text)
|
|
|
|
*/
|
|
|
|
|
2006-12-09 14:18:17 +00:00
|
|
|
#include "cgit.h"
|
|
|
|
|
2007-05-18 01:00:54 +00:00
|
|
|
/*
|
|
|
|
* url syntax: [repo ['/' cmd [ '/' path]]]
|
|
|
|
* repo: any valid repo url, may contain '/'
|
|
|
|
* cmd: log | commit | diff | tree | view | blob | snapshot
|
|
|
|
* path: any valid path, may contain '/'
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
void cgit_parse_url(const char *url)
|
|
|
|
{
|
2014-03-28 22:18:29 +00:00
|
|
|
char *c, *cmd, *p;
|
|
|
|
struct cgit_repo *repo;
|
2007-05-18 01:00:54 +00:00
|
|
|
|
2008-02-16 12:56:09 +00:00
|
|
|
ctx.repo = NULL;
|
2007-05-18 01:00:54 +00:00
|
|
|
if (!url || url[0] == '\0')
|
|
|
|
return;
|
|
|
|
|
2008-02-16 12:56:09 +00:00
|
|
|
ctx.repo = cgit_get_repoinfo(url);
|
|
|
|
if (ctx.repo) {
|
|
|
|
ctx.qry.repo = ctx.repo->url;
|
2007-05-18 01:00:54 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-03-28 22:18:29 +00:00
|
|
|
cmd = NULL;
|
|
|
|
c = strchr(url, '/');
|
|
|
|
while (c) {
|
|
|
|
c[0] = '\0';
|
|
|
|
repo = cgit_get_repoinfo(url);
|
|
|
|
if (repo) {
|
|
|
|
ctx.repo = repo;
|
|
|
|
cmd = c;
|
2007-05-18 01:00:54 +00:00
|
|
|
}
|
2014-03-28 22:18:29 +00:00
|
|
|
c[0] = '/';
|
|
|
|
c = strchr(c + 1, '/');
|
|
|
|
}
|
2007-05-18 01:00:54 +00:00
|
|
|
|
2014-03-28 22:18:29 +00:00
|
|
|
if (ctx.repo) {
|
2008-02-16 12:56:09 +00:00
|
|
|
ctx.qry.repo = ctx.repo->url;
|
2007-05-18 01:00:54 +00:00
|
|
|
p = strchr(cmd + 1, '/');
|
|
|
|
if (p) {
|
|
|
|
p[0] = '\0';
|
2007-05-18 11:06:45 +00:00
|
|
|
if (p[1])
|
2008-02-16 10:53:40 +00:00
|
|
|
ctx.qry.path = trim_end(p + 1, '/');
|
2007-05-18 01:00:54 +00:00
|
|
|
}
|
2008-03-24 00:09:39 +00:00
|
|
|
if (cmd[1])
|
|
|
|
ctx.qry.page = xstrdup(cmd + 1);
|
2007-05-18 01:00:54 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-03-04 07:52:33 +00:00
|
|
|
static char *substr(const char *head, const char *tail)
|
2006-12-15 17:17:36 +00:00
|
|
|
{
|
|
|
|
char *buf;
|
|
|
|
|
do not write outside heap buffer
* parsing.c (substr): Handle tail < head.
This started when I noticed some cgit segfaults on savannah.gnu.org.
Finding the offending URL/commit and then constructing a stand-alone
reproducer were far more time-consuming than writing the actual patch.
The problem arises with a commit like this, in which the user name
part of the "Author" field is empty:
$ git log -1
commit 6f3f41d73393278f3ede68a2cb1e7a2a23fa3421
Author: <T at h.or>
Date: Mon Apr 23 22:29:16 2012 +0200
Here's what happens:
(this is due to buf=malloc(0); strncpy (buf, head, -1);
where "head" may point to plenty of attacker-specified non-NUL bytes,
so we can overwrite a zero-length heap buffer with arbitrary data)
Invalid write of size 1
at 0x4A09361: strncpy (mc_replace_strmem.c:463)
by 0x408977: substr (parsing.c:61)
by 0x4089EF: parse_user (parsing.c:73)
by 0x408D10: cgit_parse_commit (parsing.c:153)
by 0x40A540: cgit_mk_refinfo (shared.c:171)
by 0x40A581: cgit_refs_cb (shared.c:181)
by 0x43DEB3: do_for_each_ref (refs.c:690)
by 0x41075E: cgit_print_branches (ui-refs.c:191)
by 0x416EF2: cgit_print_summary (ui-summary.c:56)
by 0x40780A: summary_fn (cmd.c:120)
by 0x40667A: process_request (cgit.c:544)
by 0x404078: cache_process (cache.c:322)
Address 0x4c718d0 is 0 bytes after a block of size 0 alloc'd
at 0x4A0884D: malloc (vg_replace_malloc.c:263)
by 0x455C85: xmalloc (wrapper.c:35)
by 0x40894C: substr (parsing.c:60)
by 0x4089EF: parse_user (parsing.c:73)
by 0x408D10: cgit_parse_commit (parsing.c:153)
by 0x40A540: cgit_mk_refinfo (shared.c:171)
by 0x40A581: cgit_refs_cb (shared.c:181)
by 0x43DEB3: do_for_each_ref (refs.c:690)
by 0x41075E: cgit_print_branches (ui-refs.c:191)
by 0x416EF2: cgit_print_summary (ui-summary.c:56)
by 0x40780A: summary_fn (cmd.c:120)
by 0x40667A: process_request (cgit.c:544)
Invalid write of size 1
at 0x4A09400: strncpy (mc_replace_strmem.c:463)
by 0x408977: substr (parsing.c:61)
by 0x4089EF: parse_user (parsing.c:73)
by 0x408D10: cgit_parse_commit (parsing.c:153)
by 0x40A540: cgit_mk_refinfo (shared.c:171)
by 0x40A581: cgit_refs_cb (shared.c:181)
by 0x43DEB3: do_for_each_ref (refs.c:690)
by 0x41075E: cgit_print_branches (ui-refs.c:191)
by 0x416EF2: cgit_print_summary (ui-summary.c:56)
by 0x40780A: summary_fn (cmd.c:120)
by 0x40667A: process_request (cgit.c:544)
by 0x404078: cache_process (cache.c:322)
Address 0x4c7192b is not stack'd, malloc'd or (recently) free'd
Invalid write of size 1
at 0x4A0940E: strncpy (mc_replace_strmem.c:463)
by 0x408977: substr (parsing.c:61)
by 0x4089EF: parse_user (parsing.c:73)
by 0x408D10: cgit_parse_commit (parsing.c:153)
by 0x40A540: cgit_mk_refinfo (shared.c:171)
by 0x40A581: cgit_refs_cb (shared.c:181)
by 0x43DEB3: do_for_each_ref (refs.c:690)
by 0x41075E: cgit_print_branches (ui-refs.c:191)
by 0x416EF2: cgit_print_summary (ui-summary.c:56)
by 0x40780A: summary_fn (cmd.c:120)
by 0x40667A: process_request (cgit.c:544)
by 0x404078: cache_process (cache.c:322)
Address 0x4c7192d is not stack'd, malloc'd or (recently) free'd
Process terminating with default action of signal 11 (SIGSEGV)
Access not within mapped region at address 0x502F000
at 0x4A09400: strncpy (mc_replace_strmem.c:463)
by 0x408977: substr (parsing.c:61)
by 0x4089EF: parse_user (parsing.c:73)
by 0x408D10: cgit_parse_commit (parsing.c:153)
by 0x40A540: cgit_mk_refinfo (shared.c:171)
by 0x40A581: cgit_refs_cb (shared.c:181)
by 0x43DEB3: do_for_each_ref (refs.c:690)
by 0x41075E: cgit_print_branches (ui-refs.c:191)
by 0x416EF2: cgit_print_summary (ui-summary.c:56)
by 0x40780A: summary_fn (cmd.c:120)
by 0x40667A: process_request (cgit.c:544)
by 0x404078: cache_process (cache.c:322)
This happens when tail - head == -1 here:
(parsing.c)
char *substr(const char *head, const char *tail)
{
char *buf;
buf = xmalloc(tail - head + 1);
strncpy(buf, head, tail - head);
buf[tail - head] = '\0';
return buf;
}
char *parse_user(char *t, char **name, char **email, unsigned long *date)
{
char *p = t;
int mode = 1;
while (p && *p) {
if (mode == 1 && *p == '<') {
*name = substr(t, p - 1);
t = p;
mode++;
} else if (mode == 1 && *p == '\n') {
The fix is to handle the case of (tail < head) before calling xmalloc,
thus avoiding passing an invalid value to xmalloc.
And here's the reproducer:
It was tricky to reproduce, because git prohibits use of an empty "name"
in a commit ID. To construct the offending commit, I had to resort to
using "git hash-object".
git init -q foo &&
( cd foo &&
echo a > j && git add . && git ci -q --author='au <T at h.or>' -m. . &&
h=$(git cat-file commit HEAD|sed 's/au //' \
|git hash-object -t commit -w --stdin) &&
git co -q -b test $h &&
git br -q -D master &&
git br -q -m test master)
git clone -q --bare foo foo.git
cat <<EOF > in
repo.url=foo.git
repo.path=foo.git
EOF
CGIT_CONFIG=in QUERY_STRING=url=foo.git valgrind ./cgit
The valgrind output is what you see above.
AFAICS, this is not exploitable thanks (ironically) to the use of strncpy.
Since that -1 translates to SIZE_MAX and this is strncpy, not only does it
copy whatever is in "head" (up to first NUL), but it also writes
SIZE_MAX - strlen(head) NUL bytes into the destination buffer, and that
latter is guaranteed to evoke a segfault. Since cgit is single-threaded,
AFAICS, there is no way that the buffer clobbering can be turned into
an exploit.
2012-04-23 20:06:35 +00:00
|
|
|
if (tail < head)
|
|
|
|
return xstrdup("");
|
2006-12-15 17:17:36 +00:00
|
|
|
buf = xmalloc(tail - head + 1);
|
|
|
|
strncpy(buf, head, tail - head);
|
|
|
|
buf[tail - head] = '\0';
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
2013-03-04 07:52:33 +00:00
|
|
|
static char *parse_user(char *t, char **name, char **email, unsigned long *date)
|
2008-09-14 07:45:37 +00:00
|
|
|
{
|
|
|
|
char *p = t;
|
|
|
|
int mode = 1;
|
|
|
|
|
|
|
|
while (p && *p) {
|
|
|
|
if (mode == 1 && *p == '<') {
|
|
|
|
*name = substr(t, p - 1);
|
|
|
|
t = p;
|
|
|
|
mode++;
|
|
|
|
} else if (mode == 1 && *p == '\n') {
|
|
|
|
*name = substr(t, p);
|
|
|
|
p++;
|
|
|
|
break;
|
|
|
|
} else if (mode == 2 && *p == '>') {
|
|
|
|
*email = substr(t, p + 1);
|
|
|
|
t = p;
|
|
|
|
mode++;
|
|
|
|
} else if (mode == 2 && *p == '\n') {
|
|
|
|
*email = substr(t, p);
|
|
|
|
p++;
|
|
|
|
break;
|
|
|
|
} else if (mode == 3 && isdigit(*p)) {
|
|
|
|
*date = atol(p);
|
|
|
|
mode++;
|
|
|
|
} else if (*p == '\n') {
|
|
|
|
p++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
p++;
|
|
|
|
}
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
2008-12-05 18:10:28 +00:00
|
|
|
#ifdef NO_ICONV
|
|
|
|
#define reencode(a, b, c)
|
|
|
|
#else
|
2013-03-04 07:52:33 +00:00
|
|
|
static const char *reencode(char **txt, const char *src_enc, const char *dst_enc)
|
2008-09-14 07:45:37 +00:00
|
|
|
{
|
|
|
|
char *tmp;
|
|
|
|
|
2011-04-05 08:35:43 +00:00
|
|
|
if (!txt)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (!*txt || !src_enc || !dst_enc)
|
2008-09-14 07:45:37 +00:00
|
|
|
return *txt;
|
|
|
|
|
2011-03-10 16:03:23 +00:00
|
|
|
/* no encoding needed if src_enc equals dst_enc */
|
2013-03-04 04:21:33 +00:00
|
|
|
if (!strcasecmp(src_enc, dst_enc))
|
2011-03-10 16:03:23 +00:00
|
|
|
return *txt;
|
|
|
|
|
|
|
|
tmp = reencode_string(*txt, dst_enc, src_enc);
|
2008-09-14 07:45:37 +00:00
|
|
|
if (tmp) {
|
|
|
|
free(*txt);
|
|
|
|
*txt = tmp;
|
|
|
|
}
|
|
|
|
return *txt;
|
|
|
|
}
|
2008-12-05 18:10:28 +00:00
|
|
|
#endif
|
2008-09-14 07:45:37 +00:00
|
|
|
|
2006-12-15 17:17:36 +00:00
|
|
|
struct commitinfo *cgit_parse_commit(struct commit *commit)
|
|
|
|
{
|
|
|
|
struct commitinfo *ret;
|
2011-07-21 21:04:53 +00:00
|
|
|
char *p = commit->buffer, *t;
|
2006-12-15 17:17:36 +00:00
|
|
|
|
|
|
|
ret = xmalloc(sizeof(*ret));
|
|
|
|
ret->commit = commit;
|
2007-01-16 17:41:01 +00:00
|
|
|
ret->author = NULL;
|
|
|
|
ret->author_email = NULL;
|
|
|
|
ret->committer = NULL;
|
|
|
|
ret->committer_email = NULL;
|
|
|
|
ret->subject = NULL;
|
|
|
|
ret->msg = NULL;
|
2007-10-26 22:09:06 +00:00
|
|
|
ret->msg_encoding = NULL;
|
2006-12-15 17:17:36 +00:00
|
|
|
|
2007-05-26 01:27:49 +00:00
|
|
|
if (p == NULL)
|
|
|
|
return ret;
|
|
|
|
|
2014-05-29 15:35:46 +00:00
|
|
|
if (!starts_with(p, "tree "))
|
2006-12-15 17:17:36 +00:00
|
|
|
die("Bad commit: %s", sha1_to_hex(commit->object.sha1));
|
|
|
|
else
|
|
|
|
p += 46; // "tree " + hex[40] + "\n"
|
|
|
|
|
2014-05-29 15:35:46 +00:00
|
|
|
while (starts_with(p, "parent "))
|
2006-12-15 17:17:36 +00:00
|
|
|
p += 48; // "parent " + hex[40] + "\n"
|
|
|
|
|
2014-05-29 15:35:46 +00:00
|
|
|
if (p && starts_with(p, "author ")) {
|
2008-09-14 07:45:37 +00:00
|
|
|
p = parse_user(p + 7, &ret->author, &ret->author_email,
|
|
|
|
&ret->author_date);
|
2006-12-15 17:17:36 +00:00
|
|
|
}
|
|
|
|
|
2014-05-29 15:35:46 +00:00
|
|
|
if (p && starts_with(p, "committer ")) {
|
2014-01-15 22:09:08 +00:00
|
|
|
p = parse_user(p + 10, &ret->committer, &ret->committer_email,
|
2008-09-14 07:45:37 +00:00
|
|
|
&ret->committer_date);
|
2006-12-15 17:17:36 +00:00
|
|
|
}
|
|
|
|
|
2014-05-29 15:35:46 +00:00
|
|
|
if (p && starts_with(p, "encoding ")) {
|
2007-10-26 22:10:26 +00:00
|
|
|
p += 9;
|
2008-09-14 07:45:37 +00:00
|
|
|
t = strchr(p, '\n');
|
|
|
|
if (t) {
|
|
|
|
ret->msg_encoding = substr(p, t + 1);
|
|
|
|
p = t + 1;
|
|
|
|
}
|
|
|
|
}
|
2007-10-26 22:10:26 +00:00
|
|
|
|
2011-03-10 16:03:23 +00:00
|
|
|
/* if no special encoding is found, assume UTF-8 */
|
2013-03-04 04:21:33 +00:00
|
|
|
if (!ret->msg_encoding)
|
2011-03-10 16:03:23 +00:00
|
|
|
ret->msg_encoding = xstrdup("UTF-8");
|
|
|
|
|
2008-09-14 07:45:37 +00:00
|
|
|
// skip unknown header fields
|
|
|
|
while (p && *p && (*p != '\n')) {
|
|
|
|
p = strchr(p, '\n');
|
|
|
|
if (p)
|
|
|
|
p++;
|
|
|
|
}
|
2007-10-24 19:14:44 +00:00
|
|
|
|
2008-09-14 07:45:37 +00:00
|
|
|
// skip empty lines between headers and message
|
|
|
|
while (p && *p == '\n')
|
|
|
|
p++;
|
|
|
|
|
|
|
|
if (!p)
|
|
|
|
return ret;
|
2006-12-15 17:17:36 +00:00
|
|
|
|
|
|
|
t = strchr(p, '\n');
|
2007-05-26 00:19:38 +00:00
|
|
|
if (t) {
|
2008-09-14 07:45:37 +00:00
|
|
|
ret->subject = substr(p, t);
|
2007-01-16 17:41:01 +00:00
|
|
|
p = t + 1;
|
2006-12-15 17:17:36 +00:00
|
|
|
|
2008-09-14 07:45:37 +00:00
|
|
|
while (p && *p == '\n') {
|
|
|
|
p = strchr(p, '\n');
|
|
|
|
if (p)
|
|
|
|
p++;
|
2007-10-26 22:13:41 +00:00
|
|
|
}
|
2008-09-14 07:45:37 +00:00
|
|
|
if (p)
|
|
|
|
ret->msg = xstrdup(p);
|
|
|
|
} else
|
|
|
|
ret->subject = xstrdup(p);
|
2007-10-26 22:13:41 +00:00
|
|
|
|
2011-03-10 16:03:23 +00:00
|
|
|
reencode(&ret->author, ret->msg_encoding, PAGE_ENCODING);
|
|
|
|
reencode(&ret->author_email, ret->msg_encoding, PAGE_ENCODING);
|
|
|
|
reencode(&ret->committer, ret->msg_encoding, PAGE_ENCODING);
|
|
|
|
reencode(&ret->committer_email, ret->msg_encoding, PAGE_ENCODING);
|
|
|
|
reencode(&ret->subject, ret->msg_encoding, PAGE_ENCODING);
|
|
|
|
reencode(&ret->msg, ret->msg_encoding, PAGE_ENCODING);
|
2007-10-26 22:13:41 +00:00
|
|
|
|
2006-12-15 17:17:36 +00:00
|
|
|
return ret;
|
|
|
|
}
|
2007-01-17 00:09:51 +00:00
|
|
|
|
|
|
|
|
|
|
|
struct taginfo *cgit_parse_tag(struct tag *tag)
|
|
|
|
{
|
|
|
|
void *data;
|
2007-05-08 20:40:59 +00:00
|
|
|
enum object_type type;
|
2007-01-17 00:09:51 +00:00
|
|
|
unsigned long size;
|
2008-09-14 07:45:37 +00:00
|
|
|
char *p;
|
2007-01-17 00:09:51 +00:00
|
|
|
struct taginfo *ret;
|
|
|
|
|
2007-05-08 20:40:59 +00:00
|
|
|
data = read_sha1_file(tag->object.sha1, &type, &size);
|
|
|
|
if (!data || type != OBJ_TAG) {
|
2007-01-17 00:09:51 +00:00
|
|
|
free(data);
|
|
|
|
return 0;
|
|
|
|
}
|
2007-05-15 21:28:40 +00:00
|
|
|
|
2007-01-17 00:09:51 +00:00
|
|
|
ret = xmalloc(sizeof(*ret));
|
|
|
|
ret->tagger = NULL;
|
|
|
|
ret->tagger_email = NULL;
|
|
|
|
ret->tagger_date = 0;
|
|
|
|
ret->msg = NULL;
|
|
|
|
|
|
|
|
p = data;
|
|
|
|
|
2007-02-03 15:11:41 +00:00
|
|
|
while (p && *p) {
|
2007-01-17 00:09:51 +00:00
|
|
|
if (*p == '\n')
|
|
|
|
break;
|
|
|
|
|
2014-05-29 15:35:46 +00:00
|
|
|
if (starts_with(p, "tagger ")) {
|
2008-09-14 07:45:37 +00:00
|
|
|
p = parse_user(p + 7, &ret->tagger, &ret->tagger_email,
|
|
|
|
&ret->tagger_date);
|
|
|
|
} else {
|
|
|
|
p = strchr(p, '\n');
|
|
|
|
if (p)
|
|
|
|
p++;
|
2007-01-17 00:09:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-09-14 07:45:37 +00:00
|
|
|
// skip empty lines between headers and message
|
|
|
|
while (p && *p == '\n')
|
|
|
|
p++;
|
2007-10-24 19:14:44 +00:00
|
|
|
|
2007-02-03 15:11:41 +00:00
|
|
|
if (p && *p)
|
2007-01-17 00:09:51 +00:00
|
|
|
ret->msg = xstrdup(p);
|
|
|
|
free(data);
|
|
|
|
return ret;
|
|
|
|
}
|