Repository navigation
Description
libgit2 commit 07b89d1 src/libgit2/blame.c:454.
Bug
blame_internal stores the return value of index_blob_lines as the line count without checking it:
ent->num_lines = index_blob_lines(blame); /* 454: may be -1 */
ent->lno = blame->options.min_line - 1;
ent->num_lines = ent->num_lines - blame->options.min_line + 1;index_blob_lines returns -1 when a git_array_alloc fails (GIT_ERROR_CHECK_ALLOC).
The -1 becomes the entry's line count and git_blame_file returns 0,
so the caller gets a successful blame whose hunks add up to (size_t)-1 lines instead of an error.
There is no crash.
Reproduce
04.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 with a 400-line file.txt, then runs git_blame_file with the Nth allocation failing, for N = 1..1000, and prints every run that returns 0 with a total line count other than 400. The N values depend on the build, which is why the program scans instead of hard-coding them.
gcc -g -O0 04.c -I"$LIBGIT2_INCLUDE" "$LIBGIT2_LIBDIR/libgit2.a" -lz -lpthread -ldl -o 04_c
./04_creference: git_blame_file -> 0, lines = 400
alloc #30: git_blame_file -> 0, lines = 18446744073709551615 (expected 400)
alloc #31: git_blame_file -> 0, lines = 18446744073709551615 (expected 400)
...
alloc #51: git_blame_file -> 0, lines = 18446744073709551615 (expected 400)
42 clean errors, 22 wrong results reported as success
All 22 wrong results fail inside index_blob_lines. The 42 clean errors come from checked allocations elsewhere in git_blame_file.
04.c
#include <git2.h>
#include <git2/sys/alloc.h>
#include <stdio.h>
#include <stdlib.h>
#define NLINES 400
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 commit of an NLINES-line file.txt. */
static int make_repo(git_repository **repo, const char *dir)
{
char path[4096];
git_signature *sig = NULL;
git_index *idx = NULL;
git_tree *tree = NULL;
git_oid id;
FILE *f;
int i, rc;
snprintf(path, sizeof(path), "%s/file.txt", dir);
if ((rc = git_repository_init(repo, dir, 0)) < 0 || !(f = fopen(path, "w")))
return -1;
for (i = 0; i < NLINES; i++)
fprintf(f, "line %d\n", i);
fclose(f);
if ((rc = git_signature_new(&sig, "r", "r@example.com", 1234567890, 0)) == 0 &&
(rc = git_repository_index(&idx, *repo)) == 0 &&
(rc = git_index_add_bypath(idx, "file.txt")) == 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);
return rc;
}
/* Blame file.txt with the nth allocation failing; returns rc, sets *lines. */
static int blame_file(git_repository *repo, long n, size_t *lines)
{
git_blame *blame;
size_t i;
int rc;
fail_at = n, count = 0;
rc = git_blame_file(&blame, repo, "file.txt", NULL);
fail_at = 0;
if (rc == 0) {
for (*lines = i = 0; i < git_blame_hunkcount(blame); i++)
*lines += git_blame_hunk_byindex(blame, i)->lines_in_hunk;
git_blame_free(blame);
}
return rc;
}
int main(void)
{
git_allocator alloc = { x_malloc, x_realloc, free };
char dir[] = "/tmp/04_c_XXXXXX";
git_repository *repo;
size_t lines;
int wrong = 0, clean = 0;
long n;
setvbuf(stdout, NULL, _IONBF, 0);
if (!mkdtemp(dir) || git_libgit2_init() < 0 ||
git_libgit2_opts(GIT_OPT_SET_ALLOCATOR, &alloc) ||
make_repo(&repo, dir) || blame_file(repo, 0, &lines) || lines != NLINES)
return fprintf(stderr, "04_c: setup failed\n"), 1;
printf("reference: git_blame_file -> 0, lines = %zu\n", lines);
for (n = 1; n <= 1000; n++) {
if (blame_file(repo, n, &lines))
clean++;
else if (lines != NLINES)
wrong++, printf("alloc #%ld: git_blame_file -> 0, lines = %zu (expected %d)\n",
n, lines, NLINES);
}
printf("%d clean errors, %d wrong results reported as success\n", clean, wrong);
return wrong ? 0 : 1;
}
Activity