Merge branch 'jk/tree-walk-overflow'

Codepaths to walk tree objects have been audited for integer
overflows and hardened.

* jk/tree-walk-overflow:
  tree-walk: harden make_traverse_path() length computations
  tree-walk: add a strbuf wrapper for make_traverse_path()
  tree-walk: accept a raw length for traverse_path_len()
  tree-walk: use size_t consistently
  tree-walk: drop oid from traverse_info
  setup_traverse_info(): stop copying oid
This commit is contained in:
Junio C Hamano 2019-08-22 12:34:10 -07:00
commit 1b01cdbf2e
6 changed files with 103 additions and 68 deletions

View File

@ -62,9 +62,7 @@ Initializing
`setup_traverse_info`:: `setup_traverse_info`::
Initialize a `traverse_info` given the pathname of the tree to start Initialize a `traverse_info` given the pathname of the tree to start
traversing from. The `base` argument is assumed to be the `path` traversing from.
member of the `name_entry` being recursed into unless the tree is a
top-level tree in which case the empty string ("") is used.
Walking Walking
------- -------
@ -140,6 +138,10 @@ same in the next callback invocation.
This utilizes the memory structure of a tree entry to avoid the This utilizes the memory structure of a tree entry to avoid the
overhead of using a generic strlen(). overhead of using a generic strlen().
`strbuf_make_traverse_path`::
Convenience wrapper to `make_traverse_path` into a strbuf.
Authors Authors
------- -------

View File

@ -180,8 +180,9 @@ static struct merge_list *create_entry(unsigned stage, unsigned mode, const stru
static char *traverse_path(const struct traverse_info *info, const struct name_entry *n) static char *traverse_path(const struct traverse_info *info, const struct name_entry *n)
{ {
char *path = xmallocz(traverse_path_len(info, n) + the_hash_algo->rawsz); struct strbuf buf = STRBUF_INIT;
return make_traverse_path(path, info, n); strbuf_make_traverse_path(&buf, info, n->path, n->pathlen);
return strbuf_detach(&buf, NULL);
} }
static void resolve(const struct traverse_info *info, struct name_entry *ours, struct name_entry *result) static void resolve(const struct traverse_info *info, struct name_entry *ours, struct name_entry *result)

View File

@ -713,7 +713,7 @@ static struct cache_tree *find_cache_tree_from_traversal(struct cache_tree *root
if (!info->prev) if (!info->prev)
return root; return root;
our_parent = find_cache_tree_from_traversal(root, info->prev); our_parent = find_cache_tree_from_traversal(root, info->prev);
return cache_tree_find(our_parent, info->name.path); return cache_tree_find(our_parent, info->name);
} }
int cache_tree_matches_traversal(struct cache_tree *root, int cache_tree_matches_traversal(struct cache_tree *root,

View File

@ -170,40 +170,61 @@ int tree_entry_gently(struct tree_desc *desc, struct name_entry *entry)
void setup_traverse_info(struct traverse_info *info, const char *base) void setup_traverse_info(struct traverse_info *info, const char *base)
{ {
int pathlen = strlen(base); size_t pathlen = strlen(base);
static struct traverse_info dummy; static struct traverse_info dummy;
memset(info, 0, sizeof(*info)); memset(info, 0, sizeof(*info));
if (pathlen && base[pathlen-1] == '/') if (pathlen && base[pathlen-1] == '/')
pathlen--; pathlen--;
info->pathlen = pathlen ? pathlen + 1 : 0; info->pathlen = pathlen ? pathlen + 1 : 0;
info->name.path = base; info->name = base;
info->name.pathlen = pathlen; info->namelen = pathlen;
if (pathlen) { if (pathlen)
hashcpy(info->name.oid.hash, (const unsigned char *)base + pathlen + 1);
info->prev = &dummy; info->prev = &dummy;
}
} }
char *make_traverse_path(char *path, const struct traverse_info *info, const struct name_entry *n) char *make_traverse_path(char *path, size_t pathlen,
const struct traverse_info *info,
const char *name, size_t namelen)
{ {
int len = tree_entry_len(n); /* Always points to the end of the name we're about to add */
int pathlen = info->pathlen; size_t pos = st_add(info->pathlen, namelen);
path[pathlen + len] = 0; if (pos >= pathlen)
BUG("too small buffer passed to make_traverse_path");
path[pos] = 0;
for (;;) { for (;;) {
memcpy(path + pathlen, n->path, len); if (pos < namelen)
if (!pathlen) BUG("traverse_info pathlen does not match strings");
pos -= namelen;
memcpy(path + pos, name, namelen);
if (!pos)
break; break;
path[--pathlen] = '/'; path[--pos] = '/';
n = &info->name;
len = tree_entry_len(n); if (!info)
BUG("traverse_info ran out of list items");
name = info->name;
namelen = info->namelen;
info = info->prev; info = info->prev;
pathlen -= len;
} }
return path; return path;
} }
void strbuf_make_traverse_path(struct strbuf *out,
const struct traverse_info *info,
const char *name, size_t namelen)
{
size_t len = traverse_path_len(info, namelen);
strbuf_grow(out, len);
make_traverse_path(out->buf + out->len, out->alloc - out->len,
info, name, namelen);
strbuf_setlen(out, out->len + len);
}
struct tree_desc_skip { struct tree_desc_skip {
struct tree_desc_skip *prev; struct tree_desc_skip *prev;
const void *ptr; const void *ptr;
@ -400,13 +421,12 @@ int traverse_trees(struct index_state *istate,
tx[i].d = t[i]; tx[i].d = t[i];
if (info->prev) { if (info->prev) {
strbuf_grow(&base, info->pathlen); strbuf_make_traverse_path(&base, info->prev,
make_traverse_path(base.buf, info->prev, &info->name); info->name, info->namelen);
base.buf[info->pathlen-1] = '/'; strbuf_addch(&base, '/');
strbuf_setlen(&base, info->pathlen); traverse_path = xstrndup(base.buf, base.len);
traverse_path = xstrndup(base.buf, info->pathlen);
} else { } else {
traverse_path = xstrndup(info->name.path, info->pathlen); traverse_path = xstrndup(info->name, info->pathlen);
} }
info->traverse_path = traverse_path; info->traverse_path = traverse_path;
for (;;) { for (;;) {

View File

@ -58,8 +58,11 @@ enum get_oid_result get_tree_entry_follow_symlinks(struct repository *r, struct
struct traverse_info { struct traverse_info {
const char *traverse_path; const char *traverse_path;
struct traverse_info *prev; struct traverse_info *prev;
struct name_entry name; const char *name;
int pathlen; size_t namelen;
unsigned mode;
size_t pathlen;
struct pathspec *pathspec; struct pathspec *pathspec;
unsigned long df_conflicts; unsigned long df_conflicts;
@ -69,12 +72,17 @@ struct traverse_info {
}; };
int get_tree_entry(struct repository *, const struct object_id *, const char *, struct object_id *, unsigned short *); int get_tree_entry(struct repository *, const struct object_id *, const char *, struct object_id *, unsigned short *);
char *make_traverse_path(char *path, const struct traverse_info *info, const struct name_entry *n); char *make_traverse_path(char *path, size_t pathlen, const struct traverse_info *info,
const char *name, size_t namelen);
void strbuf_make_traverse_path(struct strbuf *out,
const struct traverse_info *info,
const char *name, size_t namelen);
void setup_traverse_info(struct traverse_info *info, const char *base); void setup_traverse_info(struct traverse_info *info, const char *base);
static inline int traverse_path_len(const struct traverse_info *info, const struct name_entry *n) static inline size_t traverse_path_len(const struct traverse_info *info,
size_t namelen)
{ {
return info->pathlen + tree_entry_len(n); return st_add(info->pathlen, namelen);
} }
/* in general, positive means "kind of interesting" */ /* in general, positive means "kind of interesting" */

View File

@ -632,7 +632,7 @@ static int unpack_index_entry(struct cache_entry *ce,
return ret; return ret;
} }
static int find_cache_pos(struct traverse_info *, const struct name_entry *); static int find_cache_pos(struct traverse_info *, const char *p, size_t len);
static void restore_cache_bottom(struct traverse_info *info, int bottom) static void restore_cache_bottom(struct traverse_info *info, int bottom)
{ {
@ -651,7 +651,7 @@ static int switch_cache_bottom(struct traverse_info *info)
if (o->diff_index_cached) if (o->diff_index_cached)
return 0; return 0;
ret = o->cache_bottom; ret = o->cache_bottom;
pos = find_cache_pos(info->prev, &info->name); pos = find_cache_pos(info->prev, info->name, info->namelen);
if (pos < -1) if (pos < -1)
o->cache_bottom = -2 - pos; o->cache_bottom = -2 - pos;
@ -686,21 +686,19 @@ static int index_pos_by_traverse_info(struct name_entry *names,
struct traverse_info *info) struct traverse_info *info)
{ {
struct unpack_trees_options *o = info->data; struct unpack_trees_options *o = info->data;
int len = traverse_path_len(info, names); struct strbuf name = STRBUF_INIT;
char *name = xmalloc(len + 1 /* slash */ + 1 /* NUL */);
int pos; int pos;
make_traverse_path(name, info, names); strbuf_make_traverse_path(&name, info, names->path, names->pathlen);
name[len++] = '/'; strbuf_addch(&name, '/');
name[len] = '\0'; pos = index_name_pos(o->src_index, name.buf, name.len);
pos = index_name_pos(o->src_index, name, len);
if (pos >= 0) if (pos >= 0)
BUG("This is a directory and should not exist in index"); BUG("This is a directory and should not exist in index");
pos = -pos - 1; pos = -pos - 1;
if (!starts_with(o->src_index->cache[pos]->name, name) || if (!starts_with(o->src_index->cache[pos]->name, name.buf) ||
(pos > 0 && starts_with(o->src_index->cache[pos-1]->name, name))) (pos > 0 && starts_with(o->src_index->cache[pos-1]->name, name.buf)))
BUG("pos must point at the first entry in this directory"); BUG("pos must point at the first entry in this directory");
free(name); strbuf_release(&name);
return pos; return pos;
} }
@ -811,8 +809,10 @@ static int traverse_trees_recursive(int n, unsigned long dirmask,
newinfo = *info; newinfo = *info;
newinfo.prev = info; newinfo.prev = info;
newinfo.pathspec = info->pathspec; newinfo.pathspec = info->pathspec;
newinfo.name = *p; newinfo.name = p->path;
newinfo.pathlen += tree_entry_len(p) + 1; newinfo.namelen = p->pathlen;
newinfo.mode = p->mode;
newinfo.pathlen = st_add3(newinfo.pathlen, tree_entry_len(p), 1);
newinfo.df_conflicts |= df_conflicts; newinfo.df_conflicts |= df_conflicts;
/* /*
@ -863,14 +863,18 @@ static int traverse_trees_recursive(int n, unsigned long dirmask,
* itself - the caller needs to do the final check for the cache * itself - the caller needs to do the final check for the cache
* entry having more data at the end! * entry having more data at the end!
*/ */
static int do_compare_entry_piecewise(const struct cache_entry *ce, const struct traverse_info *info, const struct name_entry *n) static int do_compare_entry_piecewise(const struct cache_entry *ce,
const struct traverse_info *info,
const char *name, size_t namelen,
unsigned mode)
{ {
int len, pathlen, ce_len; int pathlen, ce_len;
const char *ce_name; const char *ce_name;
if (info->prev) { if (info->prev) {
int cmp = do_compare_entry_piecewise(ce, info->prev, int cmp = do_compare_entry_piecewise(ce, info->prev,
&info->name); info->name, info->namelen,
info->mode);
if (cmp) if (cmp)
return cmp; return cmp;
} }
@ -884,15 +888,15 @@ static int do_compare_entry_piecewise(const struct cache_entry *ce, const struct
ce_len -= pathlen; ce_len -= pathlen;
ce_name = ce->name + pathlen; ce_name = ce->name + pathlen;
len = tree_entry_len(n); return df_name_compare(ce_name, ce_len, S_IFREG, name, namelen, mode);
return df_name_compare(ce_name, ce_len, S_IFREG, n->path, len, n->mode);
} }
static int do_compare_entry(const struct cache_entry *ce, static int do_compare_entry(const struct cache_entry *ce,
const struct traverse_info *info, const struct traverse_info *info,
const struct name_entry *n) const char *name, size_t namelen,
unsigned mode)
{ {
int len, pathlen, ce_len; int pathlen, ce_len;
const char *ce_name; const char *ce_name;
int cmp; int cmp;
@ -902,7 +906,7 @@ static int do_compare_entry(const struct cache_entry *ce,
* it is quicker to use the precomputed version. * it is quicker to use the precomputed version.
*/ */
if (!info->traverse_path) if (!info->traverse_path)
return do_compare_entry_piecewise(ce, info, n); return do_compare_entry_piecewise(ce, info, name, namelen, mode);
cmp = strncmp(ce->name, info->traverse_path, info->pathlen); cmp = strncmp(ce->name, info->traverse_path, info->pathlen);
if (cmp) if (cmp)
@ -917,13 +921,12 @@ static int do_compare_entry(const struct cache_entry *ce,
ce_len -= pathlen; ce_len -= pathlen;
ce_name = ce->name + pathlen; ce_name = ce->name + pathlen;
len = tree_entry_len(n); return df_name_compare(ce_name, ce_len, S_IFREG, name, namelen, mode);
return df_name_compare(ce_name, ce_len, S_IFREG, n->path, len, n->mode);
} }
static int compare_entry(const struct cache_entry *ce, const struct traverse_info *info, const struct name_entry *n) static int compare_entry(const struct cache_entry *ce, const struct traverse_info *info, const struct name_entry *n)
{ {
int cmp = do_compare_entry(ce, info, n); int cmp = do_compare_entry(ce, info, n->path, n->pathlen, n->mode);
if (cmp) if (cmp)
return cmp; return cmp;
@ -931,7 +934,7 @@ static int compare_entry(const struct cache_entry *ce, const struct traverse_inf
* Even if the beginning compared identically, the ce should * Even if the beginning compared identically, the ce should
* compare as bigger than a directory leading up to it! * compare as bigger than a directory leading up to it!
*/ */
return ce_namelen(ce) > traverse_path_len(info, n); return ce_namelen(ce) > traverse_path_len(info, tree_entry_len(n));
} }
static int ce_in_traverse_path(const struct cache_entry *ce, static int ce_in_traverse_path(const struct cache_entry *ce,
@ -939,7 +942,8 @@ static int ce_in_traverse_path(const struct cache_entry *ce,
{ {
if (!info->prev) if (!info->prev)
return 1; return 1;
if (do_compare_entry(ce, info->prev, &info->name)) if (do_compare_entry(ce, info->prev,
info->name, info->namelen, info->mode))
return 0; return 0;
/* /*
* If ce (blob) is the same name as the path (which is a tree * If ce (blob) is the same name as the path (which is a tree
@ -954,7 +958,7 @@ static struct cache_entry *create_ce_entry(const struct traverse_info *info,
struct index_state *istate, struct index_state *istate,
int is_transient) int is_transient)
{ {
int len = traverse_path_len(info, n); size_t len = traverse_path_len(info, tree_entry_len(n));
struct cache_entry *ce = struct cache_entry *ce =
is_transient ? is_transient ?
make_empty_transient_cache_entry(len) : make_empty_transient_cache_entry(len) :
@ -964,7 +968,8 @@ static struct cache_entry *create_ce_entry(const struct traverse_info *info,
ce->ce_flags = create_ce_flags(stage); ce->ce_flags = create_ce_flags(stage);
ce->ce_namelen = len; ce->ce_namelen = len;
oidcpy(&ce->oid, &n->oid); oidcpy(&ce->oid, &n->oid);
make_traverse_path(ce->name, info, n); /* len+1 because the cache_entry allocates space for NUL */
make_traverse_path(ce->name, len + 1, info, n->path, n->pathlen);
return ce; return ce;
} }
@ -1057,13 +1062,12 @@ static int unpack_failed(struct unpack_trees_options *o, const char *message)
* the directory. * the directory.
*/ */
static int find_cache_pos(struct traverse_info *info, static int find_cache_pos(struct traverse_info *info,
const struct name_entry *p) const char *p, size_t p_len)
{ {
int pos; int pos;
struct unpack_trees_options *o = info->data; struct unpack_trees_options *o = info->data;
struct index_state *index = o->src_index; struct index_state *index = o->src_index;
int pfxlen = info->pathlen; int pfxlen = info->pathlen;
int p_len = tree_entry_len(p);
for (pos = o->cache_bottom; pos < index->cache_nr; pos++) { for (pos = o->cache_bottom; pos < index->cache_nr; pos++) {
const struct cache_entry *ce = index->cache[pos]; const struct cache_entry *ce = index->cache[pos];
@ -1099,7 +1103,7 @@ static int find_cache_pos(struct traverse_info *info,
ce_len = ce_slash - ce_name; ce_len = ce_slash - ce_name;
else else
ce_len = ce_namelen(ce) - pfxlen; ce_len = ce_namelen(ce) - pfxlen;
cmp = name_compare(p->path, p_len, ce_name, ce_len); cmp = name_compare(p, p_len, ce_name, ce_len);
/* /*
* Exact match; if we have a directory we need to * Exact match; if we have a directory we need to
* delay returning it. * delay returning it.
@ -1114,7 +1118,7 @@ static int find_cache_pos(struct traverse_info *info,
* E.g. ce_name == "t-i", and p->path == "t"; we may * E.g. ce_name == "t-i", and p->path == "t"; we may
* have "t/a" in the index. * have "t/a" in the index.
*/ */
if (p_len < ce_len && !memcmp(ce_name, p->path, p_len) && if (p_len < ce_len && !memcmp(ce_name, p, p_len) &&
ce_name[p_len] < '/') ce_name[p_len] < '/')
continue; /* keep looking */ continue; /* keep looking */
break; break;
@ -1125,7 +1129,7 @@ static int find_cache_pos(struct traverse_info *info,
static struct cache_entry *find_cache_entry(struct traverse_info *info, static struct cache_entry *find_cache_entry(struct traverse_info *info,
const struct name_entry *p) const struct name_entry *p)
{ {
int pos = find_cache_pos(info, p); int pos = find_cache_pos(info, p->path, p->pathlen);
struct unpack_trees_options *o = info->data; struct unpack_trees_options *o = info->data;
if (0 <= pos) if (0 <= pos)
@ -1138,10 +1142,10 @@ static void debug_path(struct traverse_info *info)
{ {
if (info->prev) { if (info->prev) {
debug_path(info->prev); debug_path(info->prev);
if (*info->prev->name.path) if (*info->prev->name)
putchar('/'); putchar('/');
} }
printf("%s", info->name.path); printf("%s", info->name);
} }
static void debug_name_entry(int i, struct name_entry *n) static void debug_name_entry(int i, struct name_entry *n)