csum-file: always finalize or discard hash

When a hashfile struct is created, we always initialize the git_hash_ctx inside it. We usually end up in hashfile_finalize(), which passes that ctx to git_hash_final(), cleaning it up. But a few code paths don't do so: 1. If we bail on the hashfile and call free_hashfile() directly rather than finalizing. 2. If the skip_hash flag is set, the hashfile_finalize() call will never call git_hash_final(). (You might think that we should just avoid git_hash_init() entirely in this case, but the skip_hash flag is set by the caller after the hashfile is initialized). For most hash implementations this is OK, but for ones that allocate on initialization it causes a memory leak. You can see many failures by running: make SANITIZE=leak OPENSSL_SHA1_UNSAFE=1 test since OpenSSL >= 3.0 is such an allocating hash implementation (and csum-file uses the "unsafe" algorithm variant). We can solve this by calling git_hash_discard() as appropriate. Note that free_hashfile() is used both directly by callers to abort without finalizing, and by hashfile_finalize() to free memory. In the latter case we _don't_ want to call git_hash_discard(), because we'll already have either finalized or discarded it. So we'll push that to an internal "free_memory" function, and keep free_hashfile() as the public interface to abort a hashfile without finalizing. This fix makes several scripts leak-free with the command above: t1600, t1601, t2107, t7008, t9210, t9211. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed Jul 2, 2026 at 04:01 UTC 64337aecded9e91a766b585b86bcf5f0342e0b87
1 file changed +12 -4
csum-file.c
+12 -4
@@ -55,13 +55,19 @@ void hashflush(struct hashfile *f)
55 }
56 }
57
58 -void free_hashfile(struct hashfile *f)
58 +static void free_hashfile_memory(struct hashfile *f)
59 {
60 free(f->buffer);
61 free(f->check_buffer);
62 free(f);
63 }
64
65 +void free_hashfile(struct hashfile *f)
66 +{
67 + git_hash_discard(&f->ctx);
68 + free_hashfile_memory(f);
69 +}
70 +
71 int finalize_hashfile(struct hashfile *f, unsigned char *result,
72 enum fsync_component component, unsigned int flags)
73 {
@@ -69,10 +75,12 @@ int finalize_hashfile(struct hashfile *f, unsigned char *result,
75
76 hashflush(f);
77
72 - if (f->skip_hash)
78 + if (f->skip_hash) {
79 + git_hash_discard(&f->ctx);
80 hashclr(f->buffer, f->algop);
74 - else
81 + } else {
82 git_hash_final(f->buffer, &f->ctx);
83 + }
84
85 if (result)
86 hashcpy(result, f->buffer, f->algop);
@@ -97,7 +105,7 @@ int finalize_hashfile(struct hashfile *f, unsigned char *result,
105 if (close(f->check_fd))
106 die_errno("%s: sha1 file error on close", f->name);
107 }
100 - free_hashfile(f);
108 + free_hashfile_memory(f);
109 return fd;
110 }
111