From 926a750a702bc47416facd026690cbb5e25cad09 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 2 Jul 2026 03:57:44 -0400 Subject: [PATCH 1/9] csum-file: drop discard_hashfile() Commit c3d034df16 (csum-file: introduce discard_hashfile(), 2024-07-25) added a cleanup function that no longer has any callers. In that commit we adjusted do_write_index() to use the new function. But a similar fix occurred on a parallel branch, making free_hashfile() public, and the merge resolution in 1b6b2bfae5 (Merge branch 'ps/leakfixes-part-4', 2024-08-23) took the free_hashfile() version. So now we have two functions, discard_hashfile() and free_hashfile(), and we only need one. Which one do we want to keep? The only difference between them is that the discard variant also closes the descriptors held in the struct. Let's look at the three callers: 1. In finalize_hashfile() we've either already closed the descriptors (if the CSUM_CLOSE flag is passed) or the caller didn't want them closed (if it didn't pass that flag). So we want the more limited free_hashfile(). 2. In object-file.c:flush_packfile_transaction() we close the descriptor ourselves. So discard_hashfile() could save us a line of code. 3. In do_write_index() we don't close the descriptor. This was the spot for which c3d034df16 added the discard function in the first place, but I'm skeptical that closing the descriptor here is the right thing. It is true that we are done with the descriptor at this point and closing it would be ideal. But we don't really own it! The descriptor comes from a tempfile struct (as part of a lock) and that tempfile will hold on to the descriptor and try to close it when it is deleted. This might happen at the end of the program, in which case the double-close is mostly harmless (we might accidentally close some other open descriptor, but at that point we're just closing and unlinking everything we can). But in theory it could also cause subtle bugs. If do_write_index() fails, we return the error up the stack and would eventually end up in write_locked_index(). There we roll back the lock file on error, which will close the descriptor. So now we get our double close, and we might actually close something else that was opened in the interim. This is probably unlikely in practice (as soon as we see the error we'd mostly be unwinding the stack, not opening new files). But it highlights a potential problem with the discard_hashfile() interface: the hashfile doesn't necessarily own that descriptor. Note that I said "descriptors" plural above. Those callers all care about the "fd" member of the struct. But discard_hashfile() also closes check_fd. That is only used if the struct is initialized with hashfd_check(), and neither of its two callers call either discard or free (they always "finalize" instead). So closing it is irrelevant for the current callers. I think we're better off sticking with the simpler free_hashfile() interface, and the handful of callers can decide how to handle the descriptors themselves. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- csum-file.c | 9 --------- csum-file.h | 1 - 2 files changed, 10 deletions(-) diff --git a/csum-file.c b/csum-file.c index 9558177a11..2bbedfa36e 100644 --- a/csum-file.c +++ b/csum-file.c @@ -101,15 +101,6 @@ int finalize_hashfile(struct hashfile *f, unsigned char *result, return fd; } -void discard_hashfile(struct hashfile *f) -{ - if (0 <= f->check_fd) - close(f->check_fd); - if (0 <= f->fd) - close(f->fd); - free_hashfile(f); -} - void hashwrite(struct hashfile *f, const void *buf, uint32_t count) { while (count) { diff --git a/csum-file.h b/csum-file.h index a9b390d336..1d762a64b0 100644 --- a/csum-file.h +++ b/csum-file.h @@ -74,7 +74,6 @@ void free_hashfile(struct hashfile *f); * Finalize the hashfile by flushing data to disk and free'ing it. */ int finalize_hashfile(struct hashfile *, unsigned char *, enum fsync_component, unsigned int); -void discard_hashfile(struct hashfile *); void hashwrite(struct hashfile *, const void *, uint32_t); void hashflush(struct hashfile *f); void crc32_begin(struct hashfile *); From cdb20e97d8beb5492db4bf308fc04403b0a75d1d Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 2 Jul 2026 03:59:53 -0400 Subject: [PATCH 2/9] hash: add discard primitive The usual life-cycle for a git_hash_ctx is calling git_hash_init(), adding some data, and then using git_hash_final() to get the output digest and free any resources. Sometimes we decide to abort the operation without the final() call (e.g., due to errors or other reasons). In that case we just abandon the hash_ctx completely and let it go out of scope. For most hash implementations this is fine; they were just holding values directly in the struct. But some implementations do allocate memory, and in these cases we leak the memory. Notably OpenSSL >= 3.0 requires us to allocate the digest context on the heap with EVP_MD_CTX_new(). Let's provide a git_hash_discard() function that can be used in these code paths to free any resources. For now we'll implement it by just calling git_hash_final() into a dummy output, relying on its side effect of freeing the resources. Our view of the underlying hash implementation is abstracted behind the platform_SHA_* macros, so that's the best we can do without widening that interface. It's a little inefficient, but probably not noticeably so in practice, especially as we'd usually hit this on an error code path. And by abstracting it in this function, we can later swap it out when the platform_SHA interface lets us do so. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- hash.c | 12 ++++++++++++ hash.h | 1 + 2 files changed, 13 insertions(+) diff --git a/hash.c b/hash.c index e925b9754e..63672a3d22 100644 --- a/hash.c +++ b/hash.c @@ -283,6 +283,18 @@ void git_hash_final_oid(struct object_id *oid, struct git_hash_ctx *ctx) ctx->algop->final_oid_fn(oid, ctx); } +void git_hash_discard(struct git_hash_ctx *ctx) +{ + /* + * XXX Many implementations do not need to do anything here, + * and a dummy final() call is wasteful. But we can't fix + * that unless our implementation API exposes a discard + * primitive. + */ + unsigned char dummy[GIT_MAX_RAWSZ]; + git_hash_final(dummy, ctx); +} + uint32_t hash_algo_by_name(const char *name) { if (!name) diff --git a/hash.h b/hash.h index c082a53c9a..6b2f04e2a4 100644 --- a/hash.h +++ b/hash.h @@ -325,6 +325,7 @@ void git_hash_clone(struct git_hash_ctx *dst, const struct git_hash_ctx *src); void git_hash_update(struct git_hash_ctx *ctx, const void *in, size_t len); void git_hash_final(unsigned char *hash, struct git_hash_ctx *ctx); void git_hash_final_oid(struct object_id *oid, struct git_hash_ctx *ctx); +void git_hash_discard(struct git_hash_ctx *ctx); const struct git_hash_algo *hash_algo_ptr_by_number(uint32_t algo); struct git_hash_ctx *git_hash_alloc(void); void git_hash_free(struct git_hash_ctx *ctx); From 64337aecded9e91a766b585b86bcf5f0342e0b87 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 2 Jul 2026 04:01:30 -0400 Subject: [PATCH 3/9] 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 Signed-off-by: Junio C Hamano --- csum-file.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/csum-file.c b/csum-file.c index 2bbedfa36e..69e3b99254 100644 --- a/csum-file.c +++ b/csum-file.c @@ -55,13 +55,19 @@ void hashflush(struct hashfile *f) } } -void free_hashfile(struct hashfile *f) +static void free_hashfile_memory(struct hashfile *f) { free(f->buffer); free(f->check_buffer); free(f); } +void free_hashfile(struct hashfile *f) +{ + git_hash_discard(&f->ctx); + free_hashfile_memory(f); +} + int finalize_hashfile(struct hashfile *f, unsigned char *result, enum fsync_component component, unsigned int flags) { @@ -69,10 +75,12 @@ int finalize_hashfile(struct hashfile *f, unsigned char *result, hashflush(f); - if (f->skip_hash) + if (f->skip_hash) { + git_hash_discard(&f->ctx); hashclr(f->buffer, f->algop); - else + } else { git_hash_final(f->buffer, &f->ctx); + } if (result) hashcpy(result, f->buffer, f->algop); @@ -97,7 +105,7 @@ int finalize_hashfile(struct hashfile *f, unsigned char *result, if (close(f->check_fd)) die_errno("%s: sha1 file error on close", f->name); } - free_hashfile(f); + free_hashfile_memory(f); return fd; } From 46ba44e1fd6b1a3d71d529f6713821efc26072c9 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 2 Jul 2026 04:03:19 -0400 Subject: [PATCH 4/9] csum-file: provide a function to release checkpoints A hashfile_checkpoint struct is basically just a copy of the hash_ctx state at a given point in the file. As such, it contains its own git_hash_ctx which may (depending on the underlying hash implementation) need to be discarded when we're done with it. Let's add a "release" function which cleans up the hash context it holds. I chose "release" here and not "discard" because you'd use this to clean up every checkpoint, whether you used it or not. As opposed to git_hash_discard(), which is needed only if you didn't call git_hash_final(). There are only two callers which use hashfile_checkpoints, and we can add release calls to both. When built with "SANITIZE=leak OPENSSL_SHA1_UNSAFE=1", this makes both t1050 and t9300 leak-free. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/fast-import.c | 1 + csum-file.c | 5 +++++ csum-file.h | 1 + object-file.c | 2 ++ 4 files changed, 9 insertions(+) diff --git a/builtin/fast-import.c b/builtin/fast-import.c index 82bc6dcc00..a596b0ee2a 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -1214,6 +1214,7 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark) out: free(in_buf); free(out_buf); + hashfile_checkpoint_release(&checkpoint); } /* All calls must be guarded by find_object() or find_mark() to diff --git a/csum-file.c b/csum-file.c index 69e3b99254..19805e580a 100644 --- a/csum-file.c +++ b/csum-file.c @@ -223,6 +223,11 @@ int hashfile_truncate(struct hashfile *f, struct hashfile_checkpoint *checkpoint return 0; } +void hashfile_checkpoint_release(struct hashfile_checkpoint *checkpoint) +{ + git_hash_discard(&checkpoint->ctx); +} + void crc32_begin(struct hashfile *f) { f->crc32 = crc32(0, NULL, 0); diff --git a/csum-file.h b/csum-file.h index 1d762a64b0..b98bf57060 100644 --- a/csum-file.h +++ b/csum-file.h @@ -39,6 +39,7 @@ struct hashfile_checkpoint { void hashfile_checkpoint_init(struct hashfile *, struct hashfile_checkpoint *); void hashfile_checkpoint(struct hashfile *, struct hashfile_checkpoint *); int hashfile_truncate(struct hashfile *, struct hashfile_checkpoint *); +void hashfile_checkpoint_release(struct hashfile_checkpoint *); /* finalize_hashfile flags */ #define CSUM_CLOSE 1 diff --git a/object-file.c b/object-file.c index 2acc9522df..aeb0d89368 100644 --- a/object-file.c +++ b/object-file.c @@ -1639,6 +1639,8 @@ static int index_blob_packfile_transaction(struct odb_transaction_files *transac state->alloc_written); state->written[state->nr_written++] = idx; } + + hashfile_checkpoint_release(&checkpoint); return 0; } From 64b69225620a4119e1ee6e02620c56a58dc442fb Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 2 Jul 2026 04:04:11 -0400 Subject: [PATCH 5/9] patch-id: discard hash when done When computing a patch-id, we have a flush_one_hunk() helper that calls git_hash_final() on our running hunk git_hash_ctx, and then reinitializes that context for the next hunk. When we run out of hunks to look at, we return, discarding the git_hash_ctx. This can cause a leak if the hash implementation we are using allocates any memory during its initialization. This includes OpenSSL >= 3.0, for both SHA-1 and SHA-256. Normally we would not use SHA-1 here at all, as we only recommend using non-DC implementations for the "unsafe" variant (and patch-id, though they probably _could_ use the unsafe variant, were never taught to do so). But it is certainly a problem for SHA-256, which you can see with: make SANITIZE=leak \ OPENSSL_SHA256=1 \ GIT_TEST_DEFAULT_HASH=sha256 \ test That results in leak failures of 60 scripts, 57 of which are fixed by this patch (basically anything which runs rebase will hit this case). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/patch-id.c | 1 + diff.c | 1 + 2 files changed, 2 insertions(+) diff --git a/builtin/patch-id.c b/builtin/patch-id.c index 2781598ede..57d9bd4a65 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -173,6 +173,7 @@ static size_t get_one_patchid(struct object_id *next_oid, struct object_id *resu oidclr(next_oid, the_repository->hash_algo); flush_one_hunk(result, &ctx); + git_hash_discard(&ctx); return patchlen; } diff --git a/diff.c b/diff.c index 397e38b41c..f631bf1e2a 100644 --- a/diff.c +++ b/diff.c @@ -6958,6 +6958,7 @@ static int diff_get_patch_id(struct diff_options *options, struct object_id *oid flush_one_hunk(oid, &ctx); } + git_hash_discard(&ctx); return 0; } From 77f78b802559f19167a1d004d5566da9fbff9e85 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 2 Jul 2026 04:05:03 -0400 Subject: [PATCH 6/9] check_stream_oid(): discard hash on read error The happy path of check_stream_oid() is to initialize a hash, feed the loose object zlib stream into it, and then get the final result. But if we hit a zlib error or see extra cruft we'll bail early with an error. Since we never call git_hash_final() in this cases, any resources held by the git_hash_ctx may be leaked. Our default hash algorithms don't allocate anything in the hash_ctx, but some implementations do. For example, running: make SANITIZE=leak \ OPENSSL_SHA256=1 \ GIT_TEST_DEFAULT_HASH=sha256 \ test will fail t1450, since it feeds corrupted objects that cause us to bail from check_stream_oid(). This patch fixes it by discarding the hash in those early return paths. Trying to jump to a common "out:" label is not worth it here, as we must _not_ discard a hash that was already fed to git_hash_final(). And the hash_ctx itself does not carry any information (so we cannot check for a NULL pointer, etc). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- object-file.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/object-file.c b/object-file.c index aeb0d89368..d593e16015 100644 --- a/object-file.c +++ b/object-file.c @@ -2122,11 +2122,13 @@ static int check_stream_oid(git_zstream *stream, if (status != Z_STREAM_END) { error(_("corrupt loose object '%s'"), oid_to_hex(expected_oid)); + git_hash_discard(&c); return -1; } if (stream->avail_in) { error(_("garbage at end of loose object '%s'"), oid_to_hex(expected_oid)); + git_hash_discard(&c); return -1; } From a2d8ea5a766c7f16afd4294acb7a618b67de75f2 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 2 Jul 2026 04:07:07 -0400 Subject: [PATCH 7/9] http: discard hash in dumb-http http_object_request Usually an object request results in finish_http_object_request() calling git_hash_final_oid(), after we've received all of the data. But if we hit an error, we'll bail early and free the http_object_request, dropping the git_hash_ctx entirely. This can cause a leak for hash implementations that allocate memory in their context, like OpenSSL >= 3.0. The obvious fix is for abort_http_object_request() to call git_hash_discard(), under the assumption that every request is either finished or aborted. But that's not quite true: 1. Not everybody calls the abort function. Sometimes they jump straight to release_http_object_request(). So we'd have to put it there. 2. After the finish function finalizes the hash, we can still encounter errors! In that case we end up aborting or releasing, and they must not discard that hash (since that would be a double-free). So we'll keep a flag marking the validity of the hash_ctx field of the request. The lifetime is simple: it is valid immediately after creation, up until we call finalize. And then our release function can just conditionally discard the hash based on that flag. This fixes test failures in t5550 and t5619 when run with: make SANITIZE=leak \ OPENSSL_SHA256=1 \ GIT_TEST_DEFAULT_HASH=sha256 \ test The flag handling could be removed if the hash-discard function were idempotent. This could be done easily-ish by having the underlying hash functions (like the ones in sha256/openssl.h) set the context pointer to NULL after free-ing. But it's something that every platform implementation would have to remember to do, and the benefit for the callers is not that huge (it would let us shave a few lines here and probably in a few other spots). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- http.c | 4 ++++ http.h | 1 + 2 files changed, 5 insertions(+) diff --git a/http.c b/http.c index 67c9c6fc60..8b0de51984 100644 --- a/http.c +++ b/http.c @@ -2806,6 +2806,7 @@ struct http_object_request *new_http_object_request(const char *base_url, git_inflate_init(&freq->stream); the_hash_algo->init_fn(&freq->c); + freq->hash_ctx_valid = 1; freq->url = get_remote_object_url(base_url, hex, 0); @@ -2913,6 +2914,7 @@ int finish_http_object_request(struct http_object_request *freq) } git_hash_final_oid(&freq->real_oid, &freq->c); + freq->hash_ctx_valid = 0; if (freq->zret != Z_STREAM_END) { unlink_or_warn(freq->tmpfile.buf); return -1; @@ -2953,6 +2955,8 @@ void release_http_object_request(struct http_object_request **freq_p) curl_slist_free_all(freq->headers); strbuf_release(&freq->tmpfile); git_inflate_end(&freq->stream); + if (freq->hash_ctx_valid) + git_hash_discard(&freq->c); free(freq); *freq_p = NULL; diff --git a/http.h b/http.h index f9ee888c3e..dc9700c113 100644 --- a/http.h +++ b/http.h @@ -249,6 +249,7 @@ struct http_object_request { struct object_id oid; struct object_id real_oid; struct git_hash_ctx c; + int hash_ctx_valid; git_zstream stream; int zret; int rename; From a51b54530e1f38dccf77afdc49e0ea16fdc69b16 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 2 Jul 2026 04:09:07 -0400 Subject: [PATCH 8/9] hash: fix memory leak copying sha256 gcrypt handles Our abstracted hash-algorithm API allows for cloning a hash context. By default this just memcpy()s the bytes, but specific implementations can provide a custom clone function. Our API is based around the way that OpenSSL works, which is that you first initialize the destination context, then copy into it. In our code that is this: algo->init_fn(&dst); git_hash_clone(&dst, src); and that translates into OpenSSL calls like: /* init_fn */ dst->ectx = EVP_MD_CTX_new(); EVP_DigestInit_ex(dst->ectx, EVP_sha256()); /* clone */ EVP_MD_CTX_copy_ex(dst->ectx, src->ectx); So the allocation happens in the first step, and then the clone is just copying values (the DigestInit is initializing values that just get overwritten, but that's not wrong, just a little inefficient). But libgcrypt doesn't work like that! Its copy function initializes dst from scratch. So when using the sha256 gcrypt backend, that becomes: /* init_fn; this allocates */ gcry_md_open(&dst, GCRY_MD_SHA256); /* clone; this also allocates, leaking the previous value! */ gcry_md_copy(&dst, src); You can see the leaks in the test suite by running: make \ SANITIZE=leak \ GCRYPT_SHA256=1 \ GIT_TEST_DEFAULT_SHA=256 \ test which has many failures, as opposed to building with OPENSSL_SHA256, which is leak-free. The easy fix here is for the clone function to close the open context we're about to overwrite. It's a little inefficient (we did a pointless open in the init function), but probably not a big deal in practice. If our API went the other way, assuming that we're always cloning into garbage bytes, then we could be more efficient. We'd teach OpenSSL's clone function to do its own new(), skip the DigestInit, and then copy into it. And gcrypt could stick with just the copy() call. But look again at the asymmetry in the very first code example. We call the init function straight from the git_hash_algo struct, and then subsequent calls are dispatched through our git_hash_* wrappers. If you wanted to clone into an uninitialized destination, you'd do something like: algo->clone_fn(&dst, src); instead. That would require changing all of the callers. There's not that many of them, but I don't know that it's worth changing our calling conventions to try to reclaim this tiny bit of efficiency. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- sha256/gcrypt.h | 1 + 1 file changed, 1 insertion(+) diff --git a/sha256/gcrypt.h b/sha256/gcrypt.h index 17a90f1052..694a2b70a1 100644 --- a/sha256/gcrypt.h +++ b/sha256/gcrypt.h @@ -27,6 +27,7 @@ static inline void gcrypt_SHA256_Final(unsigned char *digest, gcrypt_SHA256_CTX static inline void gcrypt_SHA256_Clone(gcrypt_SHA256_CTX *dst, const gcrypt_SHA256_CTX *src) { + gcry_md_close(*dst); gcry_md_copy(dst, *src); } From 600588d2aa4e3311ee476f89cd57a622ed8bf0ec Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 2 Jul 2026 04:13:49 -0400 Subject: [PATCH 9/9] hash: add platform-specific discard functions Our git_hash_discard() is a bit hacky: it just calls git_hash_final() into a dummy result buffer, using the side effect that each implementation's Final() function will also free any resources. This is probably not too terrible, since generating the final hash is not that expensive and we'd mostly call discard on unusual or error code paths. But we can do better by widening the platform API a bit to add an explicit discard function. This requires an annoying amount of boilerplate: - Each algorithm needs a git_$ALGO_discard() wrapper that dereferences the union'd git_hash_ctx into the type-safe field. So sha1 + sha256 + sha1-unsafe, plus a BUG() for the unknown algo. And then these all need to be referenced in the git_hash_algo structs. - Platforms which don't do anything special to discard now need a fallback function which does nothing. And we need this for each algo (sha1, sha256, and sha1-unsafe). - Platforms which do need to discard must define their discard functions. This includes sha1/openssl, sha256/openssl, and sha256/gcrypt (no sha1-unsafe here as it sits atop the sha1/openssl functions). - Algo selection needs to point platform_*_Discard to the appropriate underlying macro, or indicate that the fallback should be used. We have a similar situation for the Clone function (where a straight memcpy() of the context struct is not enough for some platforms). I've tied Discard to the same flag used by Clone here, since they are basically the same problem: is the hash context a sequence of bytes, or does it need smart copying/discarding? It's easy to miss a case here since we don't even compile the implementations we aren't using. I've tested with each of: - no flags, which uses our internal sha1/sha256 implementations, both of which exercise the noop fallback function - OPENSSL_SHA1_UNSAFE=1, which checks that our unsafe macro redirections work - OPENSSL_SHA1=1, though you should not do that in real life! - OPENSSL_SHA256=1, passes tests with GIT_TEST_DEFAULT_HASH=sha256 - GCRYPT_SHA256=1, which likewise passes The other implementations do not set the CLONE_HELPER flag, so they treat the context as bytes and should be fine with the fallback. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- hash.c | 33 +++++++++++++++++++++++++-------- hash.h | 21 +++++++++++++++++++++ sha1/openssl.h | 6 ++++++ sha256/gcrypt.h | 6 ++++++ sha256/openssl.h | 6 ++++++ 5 files changed, 64 insertions(+), 8 deletions(-) diff --git a/hash.c b/hash.c index 63672a3d22..55d1d41770 100644 --- a/hash.c +++ b/hash.c @@ -72,6 +72,11 @@ static void git_hash_sha1_final_oid(struct object_id *oid, struct git_hash_ctx * oid->algo = GIT_HASH_SHA1; } +static void git_hash_sha1_discard(struct git_hash_ctx *ctx) +{ + git_SHA1_Discard(&ctx->state.sha1); +} + static void git_hash_sha1_init_unsafe(struct git_hash_ctx *ctx) { ctx->algop = unsafe_hash_algo(&hash_algos[GIT_HASH_SHA1]); @@ -102,6 +107,11 @@ static void git_hash_sha1_final_oid_unsafe(struct object_id *oid, struct git_has oid->algo = GIT_HASH_SHA1; } +static void git_hash_sha1_discard_unsafe(struct git_hash_ctx *ctx) +{ + git_SHA1_Discard_unsafe(&ctx->state.sha1_unsafe); +} + static void git_hash_sha256_init(struct git_hash_ctx *ctx) { ctx->algop = unsafe_hash_algo(&hash_algos[GIT_HASH_SHA256]); @@ -135,6 +145,11 @@ static void git_hash_sha256_final_oid(struct object_id *oid, struct git_hash_ctx oid->algo = GIT_HASH_SHA256; } +static void git_hash_sha256_discard(struct git_hash_ctx *ctx) +{ + git_SHA256_Discard(&ctx->state.sha256); +} + static void git_hash_unknown_init(struct git_hash_ctx *ctx UNUSED) { BUG("trying to init unknown hash"); @@ -165,6 +180,11 @@ static void git_hash_unknown_final_oid(struct object_id *oid UNUSED, BUG("trying to finalize unknown hash"); } +static void git_hash_unknown_discard(struct git_hash_ctx *ctx UNUSED) +{ + BUG("trying to discard unknown hash"); +} + static const struct git_hash_algo sha1_unsafe_algo = { .name = "sha1", .format_id = GIT_SHA1_FORMAT_ID, @@ -176,6 +196,7 @@ static const struct git_hash_algo sha1_unsafe_algo = { .update_fn = git_hash_sha1_update_unsafe, .final_fn = git_hash_sha1_final_unsafe, .final_oid_fn = git_hash_sha1_final_oid_unsafe, + .discard_fn = git_hash_sha1_discard_unsafe, .empty_tree = &empty_tree_oid, .empty_blob = &empty_blob_oid, .null_oid = &null_oid_sha1, @@ -193,6 +214,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = { .update_fn = git_hash_unknown_update, .final_fn = git_hash_unknown_final, .final_oid_fn = git_hash_unknown_final_oid, + .discard_fn = git_hash_unknown_discard, .empty_tree = NULL, .empty_blob = NULL, .null_oid = NULL, @@ -208,6 +230,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = { .update_fn = git_hash_sha1_update, .final_fn = git_hash_sha1_final, .final_oid_fn = git_hash_sha1_final_oid, + .discard_fn = git_hash_sha1_discard, .unsafe = &sha1_unsafe_algo, .empty_tree = &empty_tree_oid, .empty_blob = &empty_blob_oid, @@ -224,6 +247,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = { .update_fn = git_hash_sha256_update, .final_fn = git_hash_sha256_final, .final_oid_fn = git_hash_sha256_final_oid, + .discard_fn = git_hash_sha256_discard, .empty_tree = &empty_tree_oid_sha256, .empty_blob = &empty_blob_oid_sha256, .null_oid = &null_oid_sha256, @@ -285,14 +309,7 @@ void git_hash_final_oid(struct object_id *oid, struct git_hash_ctx *ctx) void git_hash_discard(struct git_hash_ctx *ctx) { - /* - * XXX Many implementations do not need to do anything here, - * and a dummy final() call is wasteful. But we can't fix - * that unless our implementation API exposes a discard - * primitive. - */ - unsigned char dummy[GIT_MAX_RAWSZ]; - git_hash_final(dummy, ctx); + ctx->algop->discard_fn(ctx); } uint32_t hash_algo_by_name(const char *name) diff --git a/hash.h b/hash.h index 6b2f04e2a4..0a23ef4dfd 100644 --- a/hash.h +++ b/hash.h @@ -37,6 +37,7 @@ # define platform_SHA1_Clone_unsafe openssl_SHA1_Clone # define platform_SHA1_Update_unsafe openssl_SHA1_Update # define platform_SHA1_Final_unsafe openssl_SHA1_Final +# define platform_SHA1_Discard_unsafe openssl_SHA1_Discard # else # define platform_SHA_CTX_unsafe SHA_CTX # define platform_SHA1_Init_unsafe SHA1_Init @@ -92,6 +93,7 @@ # define platform_SHA1_Final_unsafe platform_SHA1_Final # ifdef platform_SHA1_Clone # define platform_SHA1_Clone_unsafe platform_SHA1_Clone +# define platform_SHA1_Discard_unsafe platform_SHA1_Discard # endif # ifdef SHA1_NEEDS_CLONE_HELPER # define SHA1_NEEDS_CLONE_HELPER_UNSAFE @@ -110,9 +112,11 @@ #ifdef platform_SHA1_Clone #define git_SHA1_Clone platform_SHA1_Clone +#define git_SHA1_Discard platform_SHA1_Discard #endif #ifdef platform_SHA1_Clone_unsafe # define git_SHA1_Clone_unsafe platform_SHA1_Clone_unsafe +# define git_SHA1_Discard_unsafe platform_SHA1_Discard_unsafe #endif #ifndef platform_SHA256_CTX @@ -129,6 +133,7 @@ #ifdef platform_SHA256_Clone #define git_SHA256_Clone platform_SHA256_Clone +#define git_SHA256_Discard platform_SHA256_Discard #endif #ifdef SHA1_MAX_BLOCK_SIZE @@ -142,6 +147,10 @@ static inline void git_SHA1_Clone(git_SHA_CTX *dst, const git_SHA_CTX *src) { memcpy(dst, src, sizeof(*dst)); } +static inline void git_SHA1_Discard(git_SHA_CTX *ctx UNUSED) +{ + /* noop */ +} #endif #ifndef SHA1_NEEDS_CLONE_HELPER_UNSAFE static inline void git_SHA1_Clone_unsafe(git_SHA_CTX_unsafe *dst, @@ -149,6 +158,10 @@ static inline void git_SHA1_Clone_unsafe(git_SHA_CTX_unsafe *dst, { memcpy(dst, src, sizeof(*dst)); } +static inline void git_SHA1_Discard_unsafe(git_SHA_CTX_unsafe *ctx UNUSED) +{ + /* noop */ +} #endif #ifndef SHA256_NEEDS_CLONE_HELPER @@ -156,6 +169,10 @@ static inline void git_SHA256_Clone(git_SHA256_CTX *dst, const git_SHA256_CTX *s { memcpy(dst, src, sizeof(*dst)); } +static inline void git_SHA256_Discard(git_SHA256_CTX *ctx UNUSED) +{ + /* noop */ +} #endif /* @@ -271,6 +288,7 @@ typedef void (*git_hash_clone_fn)(struct git_hash_ctx *dst, const struct git_has typedef void (*git_hash_update_fn)(struct git_hash_ctx *ctx, const void *in, size_t len); typedef void (*git_hash_final_fn)(unsigned char *hash, struct git_hash_ctx *ctx); typedef void (*git_hash_final_oid_fn)(struct object_id *oid, struct git_hash_ctx *ctx); +typedef void (*git_hash_discard_fn)(struct git_hash_ctx *ctx); struct git_hash_algo { /* @@ -306,6 +324,9 @@ struct git_hash_algo { /* The hash finalization function for object IDs. */ git_hash_final_oid_fn final_oid_fn; + /* Discard an initialized hash without finalizing. */ + git_hash_discard_fn discard_fn; + /* The OID of the empty tree. */ const struct object_id *empty_tree; diff --git a/sha1/openssl.h b/sha1/openssl.h index 1038af47da..48deeb724a 100644 --- a/sha1/openssl.h +++ b/sha1/openssl.h @@ -40,12 +40,18 @@ static inline void openssl_SHA1_Clone(struct openssl_SHA1_CTX *dst, EVP_MD_CTX_copy_ex(dst->ectx, src->ectx); } +static inline void openssl_SHA1_Discard(struct openssl_SHA1_CTX *ctx) +{ + EVP_MD_CTX_free(ctx->ectx); +} + #ifndef platform_SHA_CTX #define platform_SHA_CTX openssl_SHA1_CTX #define platform_SHA1_Init openssl_SHA1_Init #define platform_SHA1_Clone openssl_SHA1_Clone #define platform_SHA1_Update openssl_SHA1_Update #define platform_SHA1_Final openssl_SHA1_Final +#define platform_SHA1_Discard openssl_SHA1_Discard #endif #endif /* SHA1_OPENSSL_H */ diff --git a/sha256/gcrypt.h b/sha256/gcrypt.h index 694a2b70a1..d91ffe73d3 100644 --- a/sha256/gcrypt.h +++ b/sha256/gcrypt.h @@ -31,10 +31,16 @@ static inline void gcrypt_SHA256_Clone(gcrypt_SHA256_CTX *dst, const gcrypt_SHA2 gcry_md_copy(dst, *src); } +static inline void gcrypt_SHA256_Discard(gcrypt_SHA256_CTX *ctx) +{ + gcry_md_close(*ctx); +} + #define platform_SHA256_CTX gcrypt_SHA256_CTX #define platform_SHA256_Init gcrypt_SHA256_Init #define platform_SHA256_Clone gcrypt_SHA256_Clone #define platform_SHA256_Update gcrypt_SHA256_Update #define platform_SHA256_Final gcrypt_SHA256_Final +#define platform_SHA256_Discard gcrypt_SHA256_Discard #endif diff --git a/sha256/openssl.h b/sha256/openssl.h index c1083d9491..3d457ca99d 100644 --- a/sha256/openssl.h +++ b/sha256/openssl.h @@ -40,10 +40,16 @@ static inline void openssl_SHA256_Clone(struct openssl_SHA256_CTX *dst, EVP_MD_CTX_copy_ex(dst->ectx, src->ectx); } +static inline void openssl_SHA256_Discard(struct openssl_SHA256_CTX *ctx) +{ + EVP_MD_CTX_free(ctx->ectx); +} + #define platform_SHA256_CTX openssl_SHA256_CTX #define platform_SHA256_Init openssl_SHA256_Init #define platform_SHA256_Clone openssl_SHA256_Clone #define platform_SHA256_Update openssl_SHA256_Update #define platform_SHA256_Final openssl_SHA256_Final +#define platform_SHA256_Discard openssl_SHA256_Discard #endif /* SHA256_OPENSSL_H */