Git development
 help / color / mirror / Atom feed
From: "René Scharfe" <l.s.r@web.de>
To: Shlok Kulshreshtha <diy2903@gmail.com>, git@vger.kernel.org
Cc: gitster@pobox.com, Johannes.Schindelin@gmx.de
Subject: Re: [PATCH] object-name: avoid use-after-free in get_oid_with_context_1()
Date: Sat, 8 Aug 2026 18:23:33 +0200	[thread overview]
Message-ID: <506880fc-5557-46a1-b26d-63349781849b@web.de> (raw)
In-Reply-To: <20260807195941.99473-1-diy2903@gmail.com>

On 8/7/26 9:59 PM, Shlok Kulshreshtha wrote:
> When a ":<path>" argument names a relative path, resolve_relative_path()
> returns a newly allocated string and "cp" is pointed at it:
> 
> 	new_path = resolve_relative_path(repo, cp);
> 	if (!new_path) {
> 		namelen = namelen - (cp - name);
> 	} else {
> 		cp = new_path;
> 		namelen = strlen(cp);
> 	}
> 
> From there on "cp" and "new_path" name the same allocation. Later the
> memory location that "new_path" points to is freed.
> 
> 	free(new_path);
> 	if (reject_tree_in_index(repo, only_to_die, ce, stage, prefix, cp))
> 
> But here the reject_tree_in_index() passes "cp" to
> diagnose_invalid_index_path(), which calls strlen() on it, looks it up
> in the index, and formats it into its messages, allocating as it goes.
> All of this reads memory that has already been freed.
> 
> Collapse the two exits into one to ensure a single free() that happens
> after the last use.
> 
> Three things have to coincide to reach this:
> 
> 1. The path has to be relative, or nothing is allocated and "cp"
> still points into the argument.
> 
> 2. The entry found has to be a sparse
> directory, which needs a sparse index.
> 
> 3. The argument has to get past the check in die_verify_filename() that
> skips a leading ':' followed by a non-alphanumeric, so ":0:./dir/"
> arrives here where ":./dir/" does not.
> 
> Add a test to t1092 that covers the combination. It fails under
> SANITIZE=address without the change to object-name.c.
> 
> This was reported in [1], and the shape used here was suggested in
> review [2], but that series was not rerolled and the fix never landed.
> 
> [1] https://lore.kernel.org/git/cf6bcdb43e5b4abab464c30a914d64dc8e7a9925.1655336146.git.gitgitgadget@gmail.com/
> [2] https://lore.kernel.org/git/xmqqy1xxw7rc.fsf@gitster.g/

Oh, from 2022, good find.

> Reported-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>

Original-patch-by even, no?

> Helped-by: Junio C Hamano <gitster@pobox.com>
> Signed-off-by: Shlok Kulshreshtha <diy2903@gmail.com>
> ---
> The three conditions make this awkward to reach by hand, so here is the
> recipe:
> 
> 	git init sparse && cd sparse &&
> 	mkdir folder1 folder2 &&
> 	echo a >folder1/a && echo b >folder2/b &&
> 	git add -A && git commit -m init &&
> 	git sparse-checkout init --cone --sparse-index &&
> 	git sparse-checkout set folder1 &&
> 	git show :0:./folder2/
> 
> Without the change below, no sanitizer is needed to see it. On 2.52.0
> the buffer has already been reused by the time the message is formatted,
> so the path printed is whatever now sits in that memory, and it differs
> from run to run:
> 
> 	fatal: path '' does not exist (neither on disk nor in the index)
> 	fatal: path 'M-6?:xM-@M-:M-L??X' does not exist (neither on disk nor in the index)
> 	fatal: path '?M-*JM-^M->M-YM-tn?H' does not exist (neither on disk nor in the index)
> 
> Still without the change, built with SANITIZE=address, the same command
> reports
> 
> 	ERROR: AddressSanitizer: heap-use-after-free
> 	READ of size 3 at 0x607000002a20
> 	    #1 diagnose_invalid_index_path object-name.c:1653
> 	    #2 get_oid_with_context_1      object-name.c:1807
> 	    #3 maybe_die_on_misspelt_object_name
> 	    #4 die_verify_filename         setup.c:216
> 	    #6 setup_revisions             revision.c:3103
> 	    #8 cmd_show                    log.c:694
> 	freed by thread T0 here:
> 	    #1 get_oid_with_context_1      object-name.c:1806
> 	previously allocated by thread T0 here:
> 	    #5 prefix_path                 setup.c:149
> 	    #6 get_oid_with_context_1      object-name.c:1784
> 
> With the change, the message reads "folder2/" every time and the
> sanitizer stays quiet.
> 
> "git diff" and "git rev-parse" reach it the same way, and so does "../"
> from a subdirectory.
> 
> The new t1092 test fails without the object-name.c hunk and passes with
> it, under SANITIZE=address.
>  object-name.c                            | 15 +++++++++------
>  t/t1092-sparse-checkout-compatibility.sh | 11 +++++++++++
>  2 files changed, 20 insertions(+), 6 deletions(-)
> 
> diff --git a/object-name.c b/object-name.c
> index 83efba0ba6..bffe795830 100644
> --- a/object-name.c
> +++ b/object-name.c
> @@ -1803,13 +1803,16 @@ static enum get_oid_result get_oid_with_context_1(struct repository *repo,
>  			    memcmp(ce->name, cp, namelen))
>  				break;
>  			if (ce_stage(ce) == stage) {
> +				int ret = -1;
> +
> +				if (!reject_tree_in_index(repo, only_to_die, ce,
> +							  stage, prefix, cp)) {
> +					oidcpy(oid, &ce->oid);
> +					oc->mode = ce->ce_mode;
> +					ret = 0;
> +				}
>  				free(new_path);
> -				if (reject_tree_in_index(repo, only_to_die, ce,
> -							 stage, prefix, cp))
> -					return -1;
> -				oidcpy(oid, &ce->oid);
> -				oc->mode = ce->ce_mode;
> -				return 0;
> +				return ret;

OK

>  			}
>  			pos++;
>  		}
> diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh
> index 4140c4d8ef..e88946c254 100755
> --- a/t/t1092-sparse-checkout-compatibility.sh
> +++ b/t/t1092-sparse-checkout-compatibility.sh
> @@ -1357,6 +1357,17 @@ do
>  	"
>  done
>  
> +test_expect_success 'relative path to a sparse directory' '
> +	init_repos &&
> +
> +	# A ":<stage>:<path>" argument whose path is relative is resolved
> +	# into a heap-allocated buffer, and a sparse directory found at that
> +	# path is reported through it.  Cover that combination, so that the
> +	# reporting does not read the buffer after it has been released.
> +	test_sparse_match test_must_fail git show :0:./folder1/ &&
> +	test_sparse_match test_must_fail git rev-parse :0:./folder1/
> +'
> +
>  test_expect_success 'submodule handling' '
>  	init_repos &&
>  

Good idea to add a test.

René


  reply	other threads:[~2026-08-08 16:23 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-07 19:59 [PATCH] object-name: avoid use-after-free in get_oid_with_context_1() Shlok Kulshreshtha
2026-08-08 16:23 ` René Scharfe [this message]
2026-08-08 20:08   ` Shlok Kulshreshtha

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=506880fc-5557-46a1-b26d-63349781849b@web.de \
    --to=l.s.r@web.de \
    --cc=Johannes.Schindelin@gmx.de \
    --cc=diy2903@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=gitster@pobox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox