Visitar URL original
[BUG] Uninitialized `db` freed in `git_refdb_open` on allocation failure · Issue #7421 · libgit2/libgit2 · GitHub
Skip to content

[BUG] Uninitialized db freed in git_refdb_open on allocation failure #7421

Open

Description

@bunch-suffice-ivy

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions