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
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 #7313
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
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):
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(): check hunk_from_entry()'s return instead of inserting NULL (either propagate an error, or better — degrade: keep the hunk with final_commit_id set and NULL signature/summary fields, which restores 1.8.x's attribution behaviour for these commits).
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 bygititself (plumbing writes them,git blamehandles them fine) and exist in real-world histories — e.g. rubocop/rubocop has several (3bff9d37116fbe0981fa3f97c1f623397847db45is authored5hun-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 chainfails for a commit whose author signature can't be parsed (empty email), so
hunk_from_entryreturnsNULL. The caller inblame_internal()(~461–464) inserts it unchecked:git_blame_free()then walks the hunks vector and callsfree_hunk(NULL), dereferencing the NULL hunk (EXC_BAD_ACCESS, fault address0x50on 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 viadup_hunk()on a NULLsummary): 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):
Then any libgit2 blame of
a.pyat HEAD crashes on free:Verified through the thin git2-rs binding against vendored v1.9.4 (crashes, with or without
newest_commit/oldest_commitbounds 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 git_blame_buffer() segfault with null summary #7311.Happy to provide more detail; the reproduction above is self-contained.