{"thread":{"id":"63827","subject":"[PATCH v4] reflog: close leak of reflog expire entry","startedAt":"2025-07-22T23:23:12Z","lastAt":"2025-07-23T01:02:51Z","messageCount":2,"participants":["Jacob Keller","Jeff King"],"isPatch":true,"patchVersion":4,"patchTotal":null},"messages":[{"id":"522511","messageId":"20250722-jk-fix-leak-reflog-expire-config-v4-1-b65a83551020@gmail.com","threadId":"63827","inReplyTo":null,"subject":"[PATCH v4] reflog: close leak of reflog expire entry","fromName":"Jacob Keller","fromEmail":"jacob.e.keller@intel.com","sentAt":"2025-07-22T23:18:26Z","receivedAt":"2025-07-22T23:23:12Z","isPatch":true,"sender":{"key":"jacob.e.keller@intel.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"From: Jacob Keller <jacob.keller@gmail.com>\n\nfind_cfg_ent() allocates a struct reflog_expire_entry_option via\nFLEX_ALLOC_MEM and inserts it into a linked list in the\nreflog_expire_options structure. The entries in this list are never\nfreed, resulting in a leak in cmd_reflog_expire and the gc reflog expire\nmaintenance task:\n\nDirect leak of 39 byte(s) in 1 object(s) allocated from:\n    #0 0x7ff975ee6883 in calloc (/lib64/libasan.so.8+0xe6883)\n    #1 0x0000010edada in xcalloc ../wrapper.c:154\n    #2 0x000000df0898 in find_cfg_ent ../reflog.c:28\n    #3 0x000000df0898 in reflog_expire_config ../reflog.c:70\n    #4 0x00000095c451 in configset_iter ../config.c:2116\n    #5 0x0000006d29e7 in git_config ../config.h:724\n    #6 0x0000006d29e7 in cmd_reflog_expire ../builtin/reflog.c:205\n    #7 0x0000006d504c in cmd_reflog ../builtin/reflog.c:419\n    #8 0x0000007e4054 in run_builtin ../git.c:480\n    #9 0x0000007e4054 in handle_builtin ../git.c:746\n    #10 0x0000007e8a35 in run_argv ../git.c:813\n    #11 0x0000007e8a35 in cmd_main ../git.c:953\n    #12 0x000000441e8f in main ../common-main.c:9\n    #13 0x7ff9754115f4 in __libc_start_call_main (/lib64/libc.so.6+0x35f4)\n    #14 0x7ff9754116a7 in __libc_start_main@@GLIBC_2.34 (/lib64/libc.so.6+0x36a7)\n    #15 0x000000444184 in _start (/home/jekeller/libexec/git-core/git+0x444184)\n\nClose this leak by adding a reflog_clear_expire_config() function which\niterates the linked list and frees its elements. Call it upon exit of\ncmd_reflog_expire() and reflog_expire_condition().\n\nAdd a basic test which covers this leak. While at it, cover the\nfunctionality from commit commit 3cb22b8efe (Per-ref reflog expiry\nconfiguration, 2008-06-15). We've had this support for years, but lacked\nany tests.\n\nCo-developed-by: Jeff King <peff@peff.net>\nSigned-off-by: Jacob Keller <jacob.keller@gmail.com>\n---\nChanges in v4:\n- Pass correct struct in reflog_expire_condition\n- Add a unit test (thanks Jeff!)\n- Link to v3: https://lore.kernel.org/r/20250721-jk-fix-leak-reflog-expire-config-v3-1-c488b0586e80@gmail.com\n\nChanges in v3:\n- Remove the incorrect call in reflog_expiry_cleanup()\n- Add a call in reflog_expire_condition()\n- Link to v2: https://lore.kernel.org/r/20250709-jk-fix-leak-reflog-expire-config-v2-1-f9af934be8c1@gmail.com\n\nChanges in v2:\n- Actually fix the leak properly. (Thanks Jeff for catching my brain fart!)\n- Link to v1: https://lore.kernel.org/r/20250709-jk-fix-leak-reflog-expire-config-v1-1-34d5461cf8f5@gmail.com\n---\n reflog.h          |  2 ++\n builtin/gc.c      |  1 +\n builtin/reflog.c  |  3 +++\n reflog.c          | 14 ++++++++++++++\n t/t1410-reflog.sh | 28 ++++++++++++++++++++++++++++\n 5 files changed, 48 insertions(+)\n\ndiff --git a/reflog.h b/reflog.h\nindex 63bb56280f4e..74b3f3c4f0ac 100644\n--- a/reflog.h\n+++ b/reflog.h\n@@ -34,6 +34,8 @@ struct reflog_expire_options {\n int reflog_expire_config(const char *var, const char *value,\n \t\t\t const struct config_context *ctx, void *cb);\n \n+void reflog_clear_expire_config(struct reflog_expire_options *opts);\n+\n /*\n  * Adapt the options so that they apply to the given refname. This applies any\n  * per-reference reflog expiry configuration that may exist to the options.\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 845876ff0286..459aad0b6d7e 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -346,6 +346,7 @@ static int reflog_expire_condition(struct gc_config *cfg UNUSED)\n \t\t\t\t count_reflog_entries, &data);\n \n \treflog_expiry_cleanup(&data.policy);\n+\treflog_clear_expire_config(&data.policy.opts);\n \treturn data.count >= data.limit;\n }\n \ndiff --git a/builtin/reflog.c b/builtin/reflog.c\nindex 3acaf3e32c27..d4da41aaea73 100644\n--- a/builtin/reflog.c\n+++ b/builtin/reflog.c\n@@ -283,6 +283,9 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix,\n \t\t\t\t\t     &cb);\n \t\tfree(ref);\n \t}\n+\n+\treflog_clear_expire_config(&opts);\n+\n \treturn status;\n }\n \ndiff --git a/reflog.c b/reflog.c\nindex 15d81ebea978..e2a2f3ad3e30 100644\n--- a/reflog.c\n+++ b/reflog.c\n@@ -81,6 +81,20 @@ int reflog_expire_config(const char *var, const char *value,\n \treturn 0;\n }\n \n+void reflog_clear_expire_config(struct reflog_expire_options *opts)\n+{\n+\tstruct reflog_expire_entry_option *ent = opts->entries, *tmp;\n+\n+\twhile (ent) {\n+\t\ttmp = ent;\n+\t\tent = ent->next;\n+\t\tfree(tmp);\n+\t}\n+\n+\topts->entries = NULL;\n+\topts->entries_tail = NULL;\n+}\n+\n void reflog_expire_options_set_refname(struct reflog_expire_options *cb,\n \t\t\t\t       const char *ref)\n {\ndiff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh\nindex 42b501f163ff..e30f87a35812 100755\n--- a/t/t1410-reflog.sh\n+++ b/t/t1410-reflog.sh\n@@ -673,4 +673,32 @@ test_expect_success 'reflog drop --all with reference' '\n \t)\n '\n \n+test_expect_success 'expire with pattern config' '\n+\t# Split refs/heads/ into two roots so we can apply config to each. Make\n+\t# two branches per root to verify that config is applied correctly\n+\t# multiple times.\n+\tgit branch root1/branch1 &&\n+\tgit branch root1/branch2 &&\n+\tgit branch root2/branch1 &&\n+\tgit branch root2/branch2 &&\n+\n+\ttest_config \"gc.reflogexpire\" \"never\" &&\n+\ttest_config \"gc.refs/heads/root2/*.reflogExpire\" \"now\" &&\n+\tgit reflog expire \\\n+\t\troot1/branch1 root1/branch2 \\\n+\t\troot2/branch1 root2/branch2 &&\n+\n+\tcat >expect <<-\\EOF &&\n+\troot1/branch1@{0}\n+\troot1/branch2@{0}\n+\tEOF\n+\tgit log -g --branches=\"root*\" --format=%gD >actual.raw &&\n+\t# The sole reflog entry of each branch points to the same commit, so\n+\t# the order in which they are shown is nondeterministic. We just care\n+\t# about the what was expired (and what was not), so sort to get a known\n+\t# order.\n+\tsort <actual.raw >actual.sorted &&\n+\ttest_cmp expect actual.sorted\n+'\n+\n test_done\n\n---\nbase-commit: a30f80fde927d70950b3b4d1820813480968fb0d\nchange-id: 20250709-jk-fix-leak-reflog-expire-config-712ca6dc685a\n\nBest regards,\n--  \nJacob Keller <jacob.keller@gmail.com>\n\n"},{"id":"522515","messageId":"20250723010249.GA4938@coredump.intra.peff.net","threadId":"63827","inReplyTo":"20250722-jk-fix-leak-reflog-expire-config-v4-1-b65a83551020@gmail.com","subject":"Re: [PATCH v4] reflog: close leak of reflog expire entry","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-07-23T01:02:49Z","receivedAt":"2025-07-23T01:02:51Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jul 22, 2025 at 04:18:26PM -0700, Jacob Keller wrote:\n\n> Changes in v4:\n> - Pass correct struct in reflog_expire_condition\n> - Add a unit test (thanks Jeff!)\n> - Link to v3: https://lore.kernel.org/r/20250721-jk-fix-leak-reflog-expire-config-v3-1-c488b0586e80@gmail.com\n\nFourth time's the charm. :) This one looks good to me.\n\n-Peff\n"}]}