Re: [PATCH 1/2] worktree: don't read out of bounds
- From
Junio C Hamano <gitster@pobox.com>
- Date
- Jul 25, 2026, 16:51 UTC
- Message-ID
- <xmqqbjbvypv3.fsf@gitster.g>
- In-Reply-To
- <8bc69c6b80ed42888327331b1567cecf7225ea7e.1784978348.git.gitgitgadget@gmail.com>
"Matthias Aßhauer via GitGitGadget" <gitgitgadget@gmail.com> writes:
Show 5 quoted lines
> `worktree_basename` tries to read from memory before the passed `path` > string, if `path` is empty (or only consists of directory separators). > That results in unexpected nonsense data being returned to the caller, > which can lead to issues, such as `git worktree add ""` recursively > deleting the current working directory, including `.git`.
OK, so you do want to handle a case where path is something silly like "///".
Show 5 quoted lines
> Stop reading out of bounds in these cases to avoid that behaviour. > > This leads to `git worktree add ""` consistently exiting with the > message `BUG: How come '' becomes empty after sanitization?`, which is > still undesirable, but at least it doesn't result in data loss anymore.
OK.
Show 15 quoted lines
> diff --git a/builtin/worktree.c b/builtin/worktree.c
> index 4bc7b4f6e7..d8188035db 100644
> --- a/builtin/worktree.c
> +++ b/builtin/worktree.c
> @@ -297,17 +297,21 @@ static void remove_junk_on_signal(int signo)
> static const char *worktree_basename(const char *path, int *olen)
> {
> const char *name;
> - int len;
> + int len, len2;
>
> - len = strlen(path);
> + len2 = len = strlen(path);
> while (len && is_dir_sep(path[len - 1]))
> len--;These two 'len' variables should have clear names to distinguish what each length represents. Rather than introducing a cryptic 'len2', give it a more meaningful name, and rename 'len' as well if necessary.
I suspect that it is to remember the original length of the 'path' before stripping the trailing directory separators?
Show 5 quoted lines
> - for (name = path + len - 1; name > path; name--)
> - if (is_dir_sep(*name)) {
> - name++;
> - break;
> - }When 'len' is 0, the original code sets 'name' to '&path[-1]' and does not enter the loop. However, '*olen' is set to 0, and 'name', pointing before the start of the string, is returned. If left unfixed, callers pass it to xstrndup(), strbuf_add(), and the like, reading memory before the start of the string, which is horrible and worth fixing.
Show 9 quoted lines
> + if(len) {
> + for (name = path + len - 1; name > path; name--)
> + if (is_dir_sep(*name)) {
> + name++;
> + break;
> + }
> + }
> + else
> + name = path + len2;Style:
(1) Missing SP between 'if' and '(len'.
(2) 'else' sits on the same line as '}' that closes the 'if'
clause. (3) When any one branch of an 'if'...'else if'...'else' cascade
needs a pair of braces to group multiple statements, all other
branches must use braces as well.Taken together:
if (len) {
...
} else {
...
}As for what the patch intends to do, setting 'name = path + len2' when 'len' is 0 breaks when 'path' consists only of directory separators (for example, "/" or "///"), no?
In that case, 'len2' is positive (for example, 3) while 'len' is 0. In add_worktree(), 'path + len - name' evaluates to (path + 0) - (path + 3) = -3. Passed as size_t to strbuf_add(), this wraps around to SIZE_MAX - 2 (approx. 18 exabytes), leading to a buffer allocation failure or a crash.
Rather than calculating 'path - 1' out of bounds or introducing 'len2', worktree_basename() can simply keep 'name = path' when 'len' is 0. Using an integer index loop 'for (int i = len - 1; 0 <= i; i--)' avoids pointer arithmetic before the start of the buffer entirely, I would think. Or am I missing something?
Thanks.