Merge branch 'jk/hash-algo-leak-fixes'

Various code paths that initialize a cryptographic hash context but
bail out or finish without calling 'git_hash_final()' have been taught
to call 'git_hash_discard()' to release allocated resources, fixing
memory leaks when Git is built with non-default backends like
'OpenSSL' or 'libgcrypt'.

* jk/hash-algo-leak-fixes:
  hash: add platform-specific discard functions
  hash: fix memory leak copying sha256 gcrypt handles
  http: discard hash in dumb-http http_object_request
  check_stream_oid(): discard hash on read error
  patch-id: discard hash when done
  csum-file: provide a function to release checkpoints
  csum-file: always finalize or discard hash
  hash: add discard primitive
  csum-file: drop discard_hashfile()
main
Junio C Hamano 2026-07-16 23:05:46 -07:00
commit e47f7de7de
13 changed files with 100 additions and 14 deletions

View File

@ -1216,6 +1216,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

View File

@ -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;
}

View File

@ -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,19 +105,10 @@ 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;
}

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) {
@ -224,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);

View File

@ -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
@ -74,7 +75,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 *);

1
diff.c
View File

@ -6987,6 +6987,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;
}


29
hash.c
View File

@ -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,
@ -283,6 +307,11 @@ 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)
{
ctx->algop->discard_fn(ctx);
}

uint32_t hash_algo_by_name(const char *name)
{
if (!name)

22
hash.h
View File

@ -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;

@ -325,6 +346,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);

4
http.c
View File

@ -2880,6 +2880,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);

@ -2988,6 +2989,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;
@ -3028,6 +3030,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;

1
http.h
View File

@ -255,6 +255,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;

View File

@ -1352,6 +1352,8 @@ static int odb_transaction_files_write_object_stream(struct odb_transaction *bas
state->alloc_written);
state->written[state->nr_written++] = idx;
}

hashfile_checkpoint_release(&checkpoint);
return 0;
}

@ -1585,11 +1587,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;
}


View File

@ -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 */

View File

@ -27,13 +27,20 @@ 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);
}

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

View File

@ -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 */