Repository navigation
Description
libgit2 commit 07b89d1 src/libgit2/refdb.c:42-74. Introduced by 806a006 ("repository: wire up refStorage extension"); not in any release.
Bug
db is never initialized, and git_refdb_new does not write *out when its allocation fails. The error path then frees the garbage pointer, so the process gets SIGSEGV where it should get -1:
git_refdb *db; /* 43: uninitialized */
...
if ((error = git_refdb_new(&db, repo)) < 0) /* db left unset on OOM */
goto out;
...
out:
if (error)
git_refdb_free(db); /* 74: frees garbage */The fix is git_refdb *db = NULL; (git_refdb_free ignores NULL).
Reproduce
06.c installs a counting allocator through GIT_OPT_SET_ALLOCATOR that returns NULL for the Nth allocation, which looks exactly like a real OOM to libgit2. It creates a one-commit repo, opens it, and runs git_reference_lookup("HEAD"), which opens the refdb, with the Nth allocation failing, for N = 1..200. Only allocations made during the lookup can fail. The crashing N depends on the build, which is why the program scans instead of hard-coding it.
gcc -g -O0 06.c -I"$LIBGIT2_INCLUDE" "$LIBGIT2_LIBDIR/libgit2.a" -lz -lpthread -ldl -o 06_c
./06_c # scan
gdb -ex run -ex bt --args ./06_c <N> # single index alloc #0: OK
alloc #1: clean error (handled)
Segmentation fault # N = 2, exit 139
#0 git_atomic32_dec src/util/thread.h:136
#1 git_refdb_free src/libgit2/refdb.c:126
#2 git_refdb_open src/libgit2/refdb.c:74
#3 git_repository_refdb__weakptr src/libgit2/repository.c:1656
#4 git_reference_lookup_resolved src/libgit2/refs.c:252
06.c
#include <git2.h>
#include <git2/sys/alloc.h>
#include <stdio.h>
#include <stdlib.h>
static long fail_at, count; /* fail the fail_at-th allocation; 0 = never */
static int should_fail(void) { return fail_at && ++count == fail_at; }
static void *x_malloc(size_t n, const char *file, int line)
{
(void)file; (void)line;
return should_fail() ? NULL : malloc(n ? n : 1);
}
static void *x_realloc(void *p, size_t n, const char *file, int line)
{
(void)file; (void)line;
return should_fail() ? NULL : realloc(p, n ? n : 1);
}
/* A repo with one (empty-tree) commit. */
static int make_repo(const char *dir)
{
git_repository *repo;
git_signature *sig = NULL;
git_index *idx = NULL;
git_tree *tree = NULL;
git_oid id;
int rc;
if ((rc = git_repository_init(&repo, dir, 0)) < 0)
return rc;
if ((rc = git_signature_new(&sig, "r", "r@example.com", 1234567890, 0)) == 0 &&
(rc = git_repository_index(&idx, repo)) == 0 &&
(rc = git_index_write_tree(&id, idx)) == 0 &&
(rc = git_tree_lookup(&tree, repo, &id)) == 0)
rc = git_commit_create_v(&id, repo, "HEAD", sig, sig, NULL, "init", tree, 0);
git_tree_free(tree);
git_index_free(idx);
git_signature_free(sig);
git_repository_free(repo);
return rc;
}
/* Open the repo, then look up HEAD (which opens the refdb) with the nth
* allocation failing. Returns 0 on success, <0 on a clean error; the bug crashes. */
static int run(const char *dir, long n)
{
git_repository *repo;
git_reference *ref;
int rc;
if ((rc = git_repository_open(&repo, dir)) < 0)
return rc;
fail_at = n, count = 0;
rc = git_reference_lookup(&ref, repo, "HEAD");
fail_at = 0;
if (rc == 0)
git_reference_free(ref);
git_repository_free(repo);
printf(" alloc #%ld: %s\n", n, rc ? "clean error (handled)" : "OK");
return rc;
}
int main(int argc, char **argv)
{
git_allocator alloc = { x_malloc, x_realloc, free };
char dir[] = "/tmp/06_c_XXXXXX";
long only = argc > 1 ? atol(argv[1]) : 0, n;
setvbuf(stdout, NULL, _IONBF, 0);
if (!mkdtemp(dir) || git_libgit2_init() < 0 ||
git_libgit2_opts(GIT_OPT_SET_ALLOCATOR, &alloc) ||
make_repo(dir) || run(dir, 0))
return fprintf(stderr, "06_c: setup failed\n"), 1;
if (only)
return run(dir, only) ? 2 : 0;
for (n = 1; n <= 200; n++)
run(dir, n);
puts("06_c: no crash -- bug did NOT reproduce");
return 1;
}
Activity