From f514ef9787f320287d7ba71f2965127b9d8b3832 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 19 Aug 2015 14:12:37 -0400 Subject: [PATCH 1/5] verify_absent: allow filenames longer than PATH_MAX When unpack-trees wants to know whether a path will overwrite anything in the working tree, we use lstat() to see if there is anything there. But if we are going to write "foo/bar", we can't just lstat("foo/bar"); we need to look for leading prefixes (e.g., "foo"). So we use the lstat cache to find the length of the leading prefix, and copy the filename up to that length into a temporary buffer (since the original name is const, we cannot just stick a NUL in it). The copy we make goes into a PATH_MAX-sized buffer, which will overflow if the prefix is longer than PATH_MAX. How this happens is a little tricky, since in theory PATH_MAX is the biggest path we will have read from the filesystem. But this can happen if: - the compiled-in PATH_MAX does not accurately reflect what the filesystem is capable of - the leading prefix is not _quite_ what is on disk; it contains the next element from the name we are checking. So if we want to write "aaa/bbb/ccc/ddd" and "aaa/bbb" exists, the prefix of interest is "aaa/bbb/ccc". If "aaa/bbb" approaches PATH_MAX, then "ccc" can overflow it. So this can be triggered, but it's hard to do. In particular, you cannot just "git clone" a bogus repo. The verify_absent checks happen before unpack-trees writes anything to the filesystem, so there are never any leading prefixes during the initial checkout, and the bug doesn't trigger. And by definition, these files are larger than PATH_MAX, so writing them will fail, and clone will complain (though it may write a partial path, which will cause a subsequent "git checkout" to hit the bug). We can fix it by creating the temporary path on the heap. The extra malloc overhead is not important, as we are already making at least one stat() call (and probably more for the prefix discovery). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- unpack-trees.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/unpack-trees.c b/unpack-trees.c index 256df47b3..4a6347899 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -1432,15 +1432,18 @@ static int verify_absent_1(const struct cache_entry *ce, if (!len) return 0; else if (len > 0) { - char path[PATH_MAX + 1]; - memcpy(path, ce->name, len); - path[len] = 0; + char *path; + int ret; + + path = xmemdupz(ce->name, len); if (lstat(path, &st)) - return error("cannot stat '%s': %s", path, + ret = error("cannot stat '%s': %s", path, strerror(errno)); - - return check_ok_to_remove(path, len, DT_UNKNOWN, NULL, &st, - error_type, o); + else + ret = check_ok_to_remove(path, len, DT_UNKNOWN, NULL, + &st, error_type, o); + free(path); + return ret; } else if (lstat(ce->name, &st)) { if (errno != ENOENT) return error("cannot stat '%s': %s", ce->name, From c29edfefb6f6a3fef80172c16bcc34c826d417b0 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 19 Aug 2015 14:12:41 -0400 Subject: [PATCH 2/5] notes: use a strbuf in add_non_note When we are loading a notes tree into our internal hash table, we also collect any files that are clearly non-notes. We format the name of the file into a PATH_MAX buffer, but unlike true notes (which cannot be larger than a fanned-out sha1 hash), these tree entries can be arbitrarily long, overflowing our buffer. We can fix this by switching to a strbuf. It doesn't even cost us an extra allocation, as we can simply hand ownership of the buffer over to the non-note struct. This is of moderate security interest, as you might fetch notes trees from an untrusted remote. However, we do not do so by default, so you would have to manually fetch into the notes namespace. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- notes.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/notes.c b/notes.c index 5fe691dbc..6a9cc6295 100644 --- a/notes.c +++ b/notes.c @@ -362,13 +362,14 @@ static int non_note_cmp(const struct non_note *a, const struct non_note *b) return strcmp(a->path, b->path); } -static void add_non_note(struct notes_tree *t, const char *path, +/* note: takes ownership of path string */ +static void add_non_note(struct notes_tree *t, char *path, unsigned int mode, const unsigned char *sha1) { struct non_note *p = t->prev_non_note, *n; n = (struct non_note *) xmalloc(sizeof(struct non_note)); n->next = NULL; - n->path = xstrdup(path); + n->path = path; n->mode = mode; hashcpy(n->sha1, sha1); t->prev_non_note = n; @@ -482,17 +483,17 @@ static void load_subtree(struct notes_tree *t, struct leaf_node *subtree, * component. */ { - char non_note_path[PATH_MAX]; - char *p = non_note_path; + struct strbuf non_note_path = STRBUF_INIT; const char *q = sha1_to_hex(subtree->key_sha1); int i; for (i = 0; i < prefix_len; i++) { - *p++ = *q++; - *p++ = *q++; - *p++ = '/'; + strbuf_addch(&non_note_path, *q++); + strbuf_addch(&non_note_path, *q++); + strbuf_addch(&non_note_path, '/'); } - strcpy(p, entry.path); - add_non_note(t, non_note_path, entry.mode, entry.sha1); + strbuf_addstr(&non_note_path, entry.path); + add_non_note(t, strbuf_detach(&non_note_path, NULL), + entry.mode, entry.sha1); } } free(buf); From 5015f01c12a45a1042c1aa6b6f7f6b62bfa00ade Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 19 Aug 2015 14:12:45 -0400 Subject: [PATCH 3/5] read_info_alternates: handle paths larger than PATH_MAX This function assumes that the relative_base path passed into it is no larger than PATH_MAX, and writes into a fixed-size buffer. However, this path may not have actually come from the filesystem; for example, add_submodule_odb generates a path using a strbuf and passes it in. This is hard to trigger in practice, though, because the long submodule directory would have to exist on disk before we would try to open its info/alternates file. We can easily avoid the bug, though, by simply creating the filename on the heap. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- sha1_file.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/sha1_file.c b/sha1_file.c index d7f1838c1..b231b6278 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -377,15 +377,12 @@ void read_info_alternates(const char * relative_base, int depth) char *map; size_t mapsz; struct stat st; - const char alt_file_name[] = "info/alternates"; - /* Given that relative_base is no longer than PATH_MAX, - ensure that "path" has enough space to append "/", the - file name, "info/alternates", and a trailing NUL. */ - char path[PATH_MAX + 1 + sizeof alt_file_name]; + char *path; int fd; - sprintf(path, "%s/%s", relative_base, alt_file_name); + path = xstrfmt("%s/info/alternates", relative_base); fd = git_open_noatime(path); + free(path); if (fd < 0) return; if (fstat(fd, &st) || (st.st_size == 0)) { From 78f23bdf68dae56d644892990484951583a64014 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 19 Aug 2015 14:12:48 -0400 Subject: [PATCH 4/5] show-branch: use a strbuf for reflog descriptions When we show "branch@{0}", we format into a fixed-size buffer using sprintf. This can overflow if you have long branch names. We can fix it by using a temporary strbuf. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/show-branch.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 270e39c6c..9e60b1244 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -720,7 +720,6 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) if (reflog) { unsigned char sha1[20]; - char nth_desc[256]; char *ref; int base = 0; unsigned int flags = 0; @@ -759,6 +758,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) for (i = 0; i < reflog; i++) { char *logmsg; + char *nth_desc; const char *msg; unsigned long timestamp; int tz; @@ -777,8 +777,10 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) show_date(timestamp, tz, 1), msg); free(logmsg); - sprintf(nth_desc, "%s@{%d}", *av, base+i); + + nth_desc = xstrfmt("%s@{%d}", *av, base+i); append_ref(nth_desc, sha1, 1); + free(nth_desc); } free(ref); } From 441c4a40173fe1ee8a5c0094e587dfc47e2a6460 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 4 Sep 2015 10:25:47 -0700 Subject: [PATCH 5/5] Git 2.2.3 Signed-off-by: Junio C Hamano --- Documentation/RelNotes/2.2.3.txt | 9 +++++++++ Documentation/git.txt | 3 ++- GIT-VERSION-GEN | 2 +- RelNotes | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 Documentation/RelNotes/2.2.3.txt diff --git a/Documentation/RelNotes/2.2.3.txt b/Documentation/RelNotes/2.2.3.txt new file mode 100644 index 000000000..5bfffa410 --- /dev/null +++ b/Documentation/RelNotes/2.2.3.txt @@ -0,0 +1,9 @@ +Git v2.2.3 Release Notes +======================== + +Fixes since v2.2.2 +------------------ + + * A handful of codepaths that used to use fixed-sized arrays to hold + pathnames have been corrected to use strbuf and other mechanisms to + allow longer pathnames without fearing overflows. diff --git a/Documentation/git.txt b/Documentation/git.txt index edceb5086..577d63449 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,10 @@ unreleased) version of Git, that is available from the 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v2.2.2/git.html[documentation for release 2.2.2] +* link:v2.2.3/git.html[documentation for release 2.2.3] * release notes for + link:RelNotes/2.2.3.txt[2.2.3], link:RelNotes/2.2.2.txt[2.2.2], link:RelNotes/2.2.1.txt[2.2.1], link:RelNotes/2.2.0.txt[2.2]. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index f9f8f3274..71d66e4fa 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.2.2 +DEF_VER=v2.2.3 LF=' ' diff --git a/RelNotes b/RelNotes index c5fa3efb9..bc5f54365 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes/2.2.2.txt \ No newline at end of file +Documentation/RelNotes/2.2.3.txt \ No newline at end of file