blame: segfault in git_blame_free() when a commit in the blame graph has an empty author email (NULL hunk inserted by blame_internal) — 1.9 regression
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 58/100
Línea de trabajo
Comienza con la reproducción del issue y, después, inspecciona src/libgit2/blame.c alrededor de hunk_from_entry(), blame_internal(), free_hunk() y dup_hunk(). Traza cómo una conversión fallida de un hunk llega al vector de hunks y verifica el fix con el historial de correo electrónico vacío; se considera terminado cuando blame conserva la atribución y git_blame_free() deja de fallar, incluidos los recorridos de metadatos NULL relacionados.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Summary
git_blame_file() + git_blame_free() segfaults when any commit in the blame graph has an empty author email (author name <> …). Such commits are accepted by git itself (plumbing writes them, git blame handles them fine) and exist in real-world histories — e.g. rubocop/rubocop has several (3bff9d37116fbe0981fa3f97c1f623397847db45 is authored 5hun-s <>).
This is a 1.9.x regression: 1.8.1 blames the same history cleanly. It reproduces on every 1.9.x we tried, including v1.9.4.
Root cause
Since 1.9, blame hunks carry duplicated commit metadata. In hunk_from_entry() (src/libgit2/blame.c, ~382–409 at v1.9.4), the chain
if (git_commit_author_with_mailmap(&h->final_signature, e->suspect->commit, blame->mailmap) < 0 ||
git_commit_committer_with_mailmap(&h->final_committer, ...) < 0 ||
...
(h->summary = git__strdup(summary)) == NULL) {
free_hunk(h);
return NULL;
}
fails for a commit whose author signature can't be parsed (empty email), so hunk_from_entry returns NULL. The caller in blame_internal() (~461–464) inserts it unchecked:
for (ent = blame->ent; ent; ent = ent->next) {
git_blame_hunk *h = hunk_from_entry(ent, blame);
git_vector_insert(&blame->hunks, h); /* h may be NULL */
}
git_blame_free() then walks the hunks vector and calls free_hunk(NULL), dereferencing the NULL hunk (EXC_BAD_ACCESS, fault address 0x50 on arm64 — a field offset off the NULL pointer).
Note this makes the whole blame unusable, not just the metadata: 1.8.x attributed these commits fine (the hunk still carried final_commit_id), so this is also a functional regression for histories that contain such commits, independent of the crash.
Likely the same family as #7311 (git_blame_buffer() segfault via dup_hunk() on a NULL summary): hunk metadata that can legitimately be absent is assumed present in the dup/free paths.
Reproduction
Pure-shell repo construction (the empty-email commit is forged through plumbing, exactly how they appear in the wild):
git init repro && cd repro
printf 'x = 1\n' > a.py && git add a.py && git commit -m base
printf 'x = 1\ny = 2\n' > a.py && git add a.py
TREE=$(git write-tree)
C1=$(printf 'tree %s\nparent %s\nauthor no-email <> 1700000000 +0000\ncommitter no-email <> 1700000000 +0000\n\nempty-email edit\n' \
"$TREE" "$(git rev-parse HEAD)" | git hash-object -t commit -w --stdin --literally)
git update-ref HEAD "$C1"
printf 'x = 1\ny = 2\nz = 3\n' > a.py && git add a.py && git commit -m head
git blame a.py # git itself: fine
Then any libgit2 blame of a.py at HEAD crashes on free:
git_repository *repo; git_blame *blame;
git_libgit2_init();
git_repository_open(&repo, "repro");
git_blame_file(&blame, repo, "a.py", NULL); /* succeeds */
git_blame_free(blame); /* SIGSEGV in free_hunk(NULL) */
Verified through the thin git2-rs binding against vendored v1.9.4 (crashes, with or without newest_commit/oldest_commit bounds set) and against v1.8.1 (passes, correct attribution). Originally hit in production on rubocop/rubocop's real history.
Suggested fix
Two independent hardening points:
blame_internal(): checkhunk_from_entry()'s return instead of inserting NULL (either propagate an error, or better — degrade: keep the hunk withfinal_commit_idset and NULL signature/summary fields, which restores 1.8.x's attribution behaviour for these commits).free_hunk()/dup_hunk(): tolerate NULL hunks and NULL metadata fields, which also covers #7311.
Happy to provide more detail; the reproduction above is self-contained.
- Lenguaje dominante
- C
- Estrellas
- 10.6k
- Forks
- 2.7k
- Métricas de merge de PR
- Sin PR fusionados en 30 d
Guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de libgit2/libgit2
-
hashsig: the similarity heap evicts but never inserts, so the retained set is not the extremes Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 86/100
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
Todos los issues de libgit2/libgit2
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
-
level/task module/gcp type/bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
-
Build failure with newer clang Abierto
Dificultad 1/5 Menos de una hora Aptitud para principiantes 86/100
hapostgres/pg_auto_failover#1190 ·
-
docs
Dificultad 1/5 Menos de una hora Aptitud para principiantes 85/100
-
P3 sonic-vpp
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
sonic-net/sonic-buildimage#29662 ·