Repository navigation
Description
libgit2 commit 07b89d1 src/libgit2/config_list.c:174-178.
Bug
The git__calloc for a new map_head is unchecked. On OOM, NULL is stored in the hashmap and then written through, so the process gets SIGSEGV where it should get -1:
map_head = git__calloc(1, sizeof(*map_head)); /* 174: may be NULL */
if ((git_config_list_headmap_put(&config_list->map,
entry->base.entry.name, map_head)) < 0)
return -1;
...
map_head->entry = entry; /* 178: NULL deref */Every other allocation in config_list.c uses GIT_ERROR_CHECK_ALLOC.
Reproduce
00.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 then runs open → snapshot → get on a small config for N = 1..200. Failures at checked sites return clean errors. The unchecked site crashes. The crashing N depends on the build, which is why the program scans instead of hard-coding it.
gcc -g -O0 00.c -I"$LIBGIT2_INCLUDE" "$LIBGIT2_LIBDIR/libgit2.a" -lz -lpthread -ldl -o 00_c
./00_c # scan
gdb -ex run -ex bt --args ./00_c <cfg> <N> # single index alloc #1: git_config_open_ondisk -> clean error -1 (handled)
...
alloc #21: git_config_open_ondisk -> clean error -1 (handled)
Segmentation fault # N = 22, exit 139
Program received signal SIGSEGV, Segmentation fault.
git_config_list_append (...) at src/libgit2/config_list.c:178
178 map_head->entry = entry;
00.c
#include <git2.h>
#include <git2/sys/alloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MAXN 200
static long g_fail_at; /* fail this (1-based) allocation; 0 = pass-through */
static long g_count;
static void *counting_malloc(size_t n, const char *file, int line)
{
(void)file; (void)line;
if (g_fail_at && ++g_count == g_fail_at)
return NULL;
return malloc(n ? n : 1);
}
static void *counting_realloc(void *p, size_t n, const char *file, int line)
{
(void)file; (void)line;
if (g_fail_at && ++g_count == g_fail_at)
return NULL;
return realloc(p, n ? n : 1);
}
static void counting_free(void *p)
{
free(p);
}
/* One run: open config, snapshot, read a value, with the Nth libgit2
* allocation failed. Returns 0 ok, 2 clean error (handled), or dies by
* SIGSEGV when the failed allocation is the unchecked site. */
static int run_once(const char *path, long fail_at)
{
git_config *cfg = NULL, *snap = NULL;
const char *val = NULL;
int rc;
g_fail_at = fail_at; g_count = 0;
rc = git_config_open_ondisk(&cfg, path);
if (rc < 0) {
printf(" alloc #%ld: git_config_open_ondisk -> clean error %d (handled)\n", fail_at, rc);
return 2;
}
rc = git_config_snapshot(&snap, cfg);
git_config_free(cfg);
if (rc < 0) {
printf(" alloc #%ld: git_config_snapshot -> clean error %d (handled)\n", fail_at, rc);
return 2;
}
rc = git_config_get_string(&val, snap, "core.filemode");
git_config_free(snap);
printf(" alloc #%ld: open+snapshot+get -> %s\n", fail_at,
(rc == 0 && val && strcmp(val, "true") == 0) ? "OK" : "WRONG RESULT");
return 0;
}
int main(int argc, char **argv)
{
const char *cfg_path = argc > 1 ? argv[1] : "/tmp/00_c.cfg";
long only_n = argc > 2 ? atol(argv[2]) : 0;
git_allocator alloc = { counting_malloc, counting_realloc, counting_free };
FILE *f;
long n;
setvbuf(stdout, NULL, _IONBF, 0);
f = fopen(cfg_path, "w");
if (!f) { perror("config file"); return 1; }
fputs("[core]\n\tfilemode = true\n\trepositoryformatversion = 0\n", f);
fclose(f);
if (git_libgit2_init() < 0 ||
git_libgit2_opts(GIT_OPT_SET_ALLOCATOR, &alloc) != 0) {
fprintf(stderr, "00_c: libgit2 init / allocator setup failed\n");
return 1;
}
printf("00_c: libgit2 %d.%d.%d, pure C (no git2-rs), config = %s\n",
LIBGIT2_VERSION_MAJOR, LIBGIT2_VERSION_MINOR, LIBGIT2_VERSION_REVISION,
cfg_path);
if (run_once(cfg_path, 0) != 0) { /* sanity: no-failure run */
fprintf(stderr, "00_c: no-failure run failed -- setup broken\n");
return 1;
}
/* Single-index mode: for gdb-based crash attribution. */
if (only_n > 0) {
printf("00_c: testing only alloc #%ld\n", only_n);
return run_once(cfg_path, only_n) == 2 ? 2 : 0;
}
printf("00_c: scanning: fail the Nth libgit2 allocation, N = 1..%d;\n"
" the process stops (SIGSEGV) at the first crashing index\n", MAXN);
for (n = 1; n <= MAXN; n++) {
if (run_once(cfg_path, n) == 0)
continue;
g_fail_at = 0; /* handled: reset and try the next N */
}
printf("00_c: reached N=%ld without a crash -- bug did NOT reproduce\n", n);
return 1;
}Metadata
Metadata
Assignees
Labels
No labels
Activity
[-]NULL deref in `git_config_list_append` on allocation failure[/-][+][BUG] NULL deref in `git_config_list_append` on allocation failure[/+]