You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[BUG] NULL deref in git_config_list_append on allocation failure #7416
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>#defineMAXN 200
staticlongg_fail_at; /* fail this (1-based) allocation; 0 = pass-through */staticlongg_count;
staticvoid*counting_malloc(size_tn, constchar*file, intline)
{
(void)file; (void)line;
if (g_fail_at&&++g_count==g_fail_at)
returnNULL;
returnmalloc(n ? n : 1);
}
staticvoid*counting_realloc(void*p, size_tn, constchar*file, intline)
{
(void)file; (void)line;
if (g_fail_at&&++g_count==g_fail_at)
returnNULL;
returnrealloc(p, n ? n : 1);
}
staticvoidcounting_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. */staticintrun_once(constchar*path, longfail_at)
{
git_config*cfg=NULL, *snap=NULL;
constchar*val=NULL;
intrc;
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);
return2;
}
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);
return2;
}
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");
return0;
}
intmain(intargc, char**argv)
{
constchar*cfg_path=argc>1 ? argv[1] : "/tmp/00_c.cfg";
longonly_n=argc>2 ? atol(argv[2]) : 0;
git_allocatoralloc= { counting_malloc, counting_realloc, counting_free };
FILE*f;
longn;
setvbuf(stdout, NULL, _IONBF, 0);
f=fopen(cfg_path, "w");
if (!f) { perror("config file"); return1; }
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");
return1;
}
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");
return1;
}
/* Single-index mode: for gdb-based crash attribution. */if (only_n>0) {
printf("00_c: testing only alloc #%ld\n", only_n);
returnrun_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);
return1;
}
changed the title [-]NULL deref in `git_config_list_append` on allocation failure[/-][+][BUG] NULL deref in `git_config_list_append` on allocation failure[/+]on Oct 13, 2026
Activity
[-]NULL deref in `git_config_list_append` on allocation failure[/-][+][BUG] NULL deref in `git_config_list_append` on allocation failure[/+]