threads / patch / 45139

patchshow-branch: fix crash with long ref name

Subject: [PATCH] show-branch: fix crash with long ref name

## tl;dr

15 messages between Feb 14, 2017 and Feb 17, 2017. Diffs are folded; open one to read it.

replies: 14people: 4as markdown or json

Christian Couder· Feb 14, 2017, 15:48 UTC · lore
This is a minimum fix for a crash with a long ref name.

Note that if the refname is too long (for example if you use 100 instead of 50 in the test script) you could get an error like:

fatal: cannot lock ref 'refs/heads/1_2_3_4_ ... _98_99_100_': Unable to create '... /.git/refs/heads/1_2_3_4_ ... _98_99_100_.lock': File name too long

when creating the ref instead of a crash when using show-branch and that would be ok.

So a simpler fix could have been just something like:
-       char head[128];
+       char head[1024];

But if the filesystem ever allows filenames longer than 1024 characters then the crash could appear again.

Reported by: Maxim Kuvyrkov <maxim.kuvyrkov@linaro.org>
Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
---
 builtin/show-branch.c          | 14 +++++++-------
 t/t3204-show-branch-refname.sh | 19 +++++++++++++++++++
 2 files changed, 26 insertions(+), 7 deletions(-)
 create mode 100755 t/t3204-show-branch-refname.sh
Show changes to 2 files +26 −7

builtin/show-branch.c, t/t3204-show-branch-refname.sh

diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index 974f3403ab..3c0fe55eec 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -620,7 +620,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
 	int all_heads = 0, all_remotes = 0;
 	int all_mask, all_revs;
 	enum rev_sort_order sort_order = REV_SORT_IN_GRAPH_ORDER;
-	char head[128];
+	char *head_cpy;
 	const char *head_p;
 	int head_len;
 	struct object_id head_oid;
@@ -791,11 +791,11 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
 				    head_oid.hash, NULL);
 	if (head_p) {
 		head_len = strlen(head_p);
-		memcpy(head, head_p, head_len + 1);
+		head_cpy = xstrdup(head_p);
 	}
 	else {
 		head_len = 0;
-		head[0] = 0;
+		head_cpy = xstrdup("");
 	}
 
 	if (with_current_branch && head_p) {
@@ -804,15 +804,15 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
 			/* We are only interested in adding the branch
 			 * HEAD points at.
 			 */
-			if (rev_is_head(head,
+			if (rev_is_head(head_cpy,
 					head_len,
 					ref_name[i],
 					head_oid.hash, NULL))
 				has_head++;
 		}
 		if (!has_head) {
-			int offset = starts_with(head, "refs/heads/") ? 11 : 0;
-			append_one_rev(head + offset);
+			int offset = starts_with(head_cpy, "refs/heads/") ? 11 : 0;
+			append_one_rev(head_cpy + offset);
 		}
 	}
 
@@ -865,7 +865,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
 	if (1 < num_rev || extra < 0) {
 		for (i = 0; i < num_rev; i++) {
 			int j;
-			int is_head = rev_is_head(head,
+			int is_head = rev_is_head(head_cpy,
 						  head_len,
 						  ref_name[i],
 						  head_oid.hash,
diff --git a/t/t3204-show-branch-refname.sh b/t/t3204-show-branch-refname.sh
new file mode 100755
index 0000000000..83b64e3032
--- /dev/null
+++ b/t/t3204-show-branch-refname.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+test_description='test show-branch with long refname'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+
+	test_commit first &&
+	long_refname=$(printf "%s_" $(seq 1 50)) &&
+	git checkout -b "$long_refname"
+'
+
+test_expect_success 'show-branch with long refname' '
+
+	git show-branch first
+'
+
+test_done
-- 
2.12.0.rc0
Jeff King· Feb 14, 2017, 17:25 UTC · re: Christian Couder · lore

Re: [PATCH] show-branch: fix crash with long ref name

On Tue, Feb 14, 2017 at 04:48:16PM +0100, Christian Couder wrote:
Show 12 quoted lines
> @@ -791,11 +791,11 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
>  				    head_oid.hash, NULL);
>  	if (head_p) {
>  		head_len = strlen(head_p);
> -		memcpy(head, head_p, head_len + 1);
> +		head_cpy = xstrdup(head_p);
>  	}
>  	else {
>  		head_len = 0;
> -		head[0] = 0;
> +		head_cpy = xstrdup("");
>  	}

This fixes the problem, but I think we can simplify it quite a bit by using resolve_refdup(). Here's the patch series I ended up with:

  [1/3]: show-branch: drop head_len variable
  [2/3]: show-branch: store resolved head in heap buffer
  [3/3]: show-branch: use skip_prefix to drop magic numbers
 builtin/show-branch.c | 39 ++++++++++++---------------------------
 1 file changed, 12 insertions(+), 27 deletions(-)
-Peff
Jeff King· Feb 14, 2017, 17:26 UTC · re: Jeff King · lore

[PATCH 1/3] show-branch: drop head_len variable

We copy the result of resolving HEAD into a buffer and keep track of its length. But we never actually use the length for anything besides the copy. Let's stop passing it around.

Signed-off-by: Jeff King <peff@peff.net>
---
 builtin/show-branch.c | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)
Show changes to builtin/show-branch.c +2 −6
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index 974f3403a..e4c488b8c 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -470,7 +470,7 @@ static void snarf_refs(int head, int remotes)
 	}
 }
 
-static int rev_is_head(char *head, int headlen, char *name,
+static int rev_is_head(char *head, char *name,
 		       unsigned char *head_sha1, unsigned char *sha1)
 {
 	if ((!head[0]) ||
@@ -622,7 +622,6 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
 	enum rev_sort_order sort_order = REV_SORT_IN_GRAPH_ORDER;
 	char head[128];
 	const char *head_p;
-	int head_len;
 	struct object_id head_oid;
 	int merge_base = 0;
 	int independent = 0;
@@ -790,11 +789,10 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
 	head_p = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
 				    head_oid.hash, NULL);
 	if (head_p) {
-		head_len = strlen(head_p);
+		size_t head_len = strlen(head_p);
 		memcpy(head, head_p, head_len + 1);
 	}
 	else {
-		head_len = 0;
 		head[0] = 0;
 	}
 
@@ -805,7 +803,6 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
 			 * HEAD points at.
 			 */
 			if (rev_is_head(head,
-					head_len,
 					ref_name[i],
 					head_oid.hash, NULL))
 				has_head++;
@@ -866,7 +863,6 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
 		for (i = 0; i < num_rev; i++) {
 			int j;
 			int is_head = rev_is_head(head,
-						  head_len,
 						  ref_name[i],
 						  head_oid.hash,
 						  rev[i]->object.oid.hash);
-- 
2.12.0.rc1.479.g59880b11e
Jeff King· Feb 14, 2017, 17:27 UTC · re: Jeff King · lore

[PATCH 2/3] show-branch: store resolved head in heap buffer

We resolve HEAD and copy the result to a fixed-size buffer with memcpy, never checking that it actually fits. This bug dates back to 8098a178b (Add git-symbolic-ref, 2005-09-30). Before that we used readlink(), which took a maximum buffer size.

We can fix this by using resolve_refdup(), which duplicates the buffer on the heap. That also lets us just check for a NULL pointer to see if we have resolved HEAD, and drop the extra head_p variable.

Signed-off-by: Jeff King <peff@peff.net>
---
 builtin/show-branch.c | 19 +++++--------------
 1 file changed, 5 insertions(+), 14 deletions(-)
Show changes to builtin/show-branch.c +5 −14
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index e4c488b8c..404c4d09a 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -473,8 +473,7 @@ static void snarf_refs(int head, int remotes)
 static int rev_is_head(char *head, char *name,
 		       unsigned char *head_sha1, unsigned char *sha1)
 {
-	if ((!head[0]) ||
-	    (head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
+	if (!head || (head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
 		return 0;
 	if (starts_with(head, "refs/heads/"))
 		head += 11;
@@ -620,8 +619,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
 	int all_heads = 0, all_remotes = 0;
 	int all_mask, all_revs;
 	enum rev_sort_order sort_order = REV_SORT_IN_GRAPH_ORDER;
-	char head[128];
-	const char *head_p;
+	char *head;
 	struct object_id head_oid;
 	int merge_base = 0;
 	int independent = 0;
@@ -786,17 +784,10 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
 			snarf_refs(all_heads, all_remotes);
 	}
 
-	head_p = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
-				    head_oid.hash, NULL);
-	if (head_p) {
-		size_t head_len = strlen(head_p);
-		memcpy(head, head_p, head_len + 1);
-	}
-	else {
-		head[0] = 0;
-	}
+	head = resolve_refdup("HEAD", RESOLVE_REF_READING,
+			      head_oid.hash, NULL);
 
-	if (with_current_branch && head_p) {
+	if (with_current_branch && head) {
 		int has_head = 0;
 		for (i = 0; !has_head && i < ref_name_cnt; i++) {
 			/* We are only interested in adding the branch
-- 
2.12.0.rc1.479.g59880b11e
Jeff King· Feb 14, 2017, 17:28 UTC · re: Jeff King · lore

[PATCH 3/3] show-branch: use skip_prefix to drop magic numbers

We make several starts_with() calls, only to advance pointers. This is exactly what skip_prefix() is for, which lets us avoid manually-counted magic numbers.

Signed-off-by: Jeff King <peff@peff.net>
---
 builtin/show-branch.c | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)
Show changes to builtin/show-branch.c +7 −9
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index 404c4d09a..c03d3ec7c 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -470,17 +470,14 @@ static void snarf_refs(int head, int remotes)
 	}
 }
 
-static int rev_is_head(char *head, char *name,
+static int rev_is_head(const char *head, const char *name,
 		       unsigned char *head_sha1, unsigned char *sha1)
 {
 	if (!head || (head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
 		return 0;
-	if (starts_with(head, "refs/heads/"))
-		head += 11;
-	if (starts_with(name, "refs/heads/"))
-		name += 11;
-	else if (starts_with(name, "heads/"))
-		name += 6;
+	skip_prefix(head, "refs/heads/", &head);
+	if (!skip_prefix(name, "refs/heads/", &name))
+		skip_prefix(name, "heads/", &name);
 	return !strcmp(head, name);
 }
 
@@ -799,8 +796,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
 				has_head++;
 		}
 		if (!has_head) {
-			int offset = starts_with(head, "refs/heads/") ? 11 : 0;
-			append_one_rev(head + offset);
+			const char *name = head;
+			skip_prefix(name, "refs/heads/", &name);
+			append_one_rev(name);
 		}
 	}
 
-- 
2.12.0.rc1.479.g59880b11e
Pranit Bauva· Feb 14, 2017, 18:53 UTC · re: Jeff King · lore

Re: [PATCH 3/3] show-branch: use skip_prefix to drop magic numbers

Hey Peff,
On Tue, Feb 14, 2017 at 10:58 PM, Jeff King <peff@peff.net> wrote:
Show 47 quoted lines
> We make several starts_with() calls, only to advance
> pointers. This is exactly what skip_prefix() is for, which
> lets us avoid manually-counted magic numbers.
>
> Signed-off-by: Jeff King <peff@peff.net>
> ---
>  builtin/show-branch.c | 16 +++++++---------
>  1 file changed, 7 insertions(+), 9 deletions(-)
>
> diff --git a/builtin/show-branch.c b/builtin/show-branch.c
> index 404c4d09a..c03d3ec7c 100644
> --- a/builtin/show-branch.c
> +++ b/builtin/show-branch.c
> @@ -470,17 +470,14 @@ static void snarf_refs(int head, int remotes)
>         }
>  }
>
> -static int rev_is_head(char *head, char *name,
> +static int rev_is_head(const char *head, const char *name,
>                        unsigned char *head_sha1, unsigned char *sha1)
>  {
>         if (!head || (head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
>                 return 0;
> -       if (starts_with(head, "refs/heads/"))
> -               head += 11;
> -       if (starts_with(name, "refs/heads/"))
> -               name += 11;
> -       else if (starts_with(name, "heads/"))
> -               name += 6;
> +       skip_prefix(head, "refs/heads/", &head);
> +       if (!skip_prefix(name, "refs/heads/", &name))
> +               skip_prefix(name, "heads/", &name);
>         return !strcmp(head, name);
>  }
>
> @@ -799,8 +796,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
>                                 has_head++;
>                 }
>                 if (!has_head) {
> -                       int offset = starts_with(head, "refs/heads/") ? 11 : 0;
> -                       append_one_rev(head + offset);
> +                       const char *name = head;
> +                       skip_prefix(name, "refs/heads/", &name);
> +                       append_one_rev(name);
>                 }
>         }
>

Did you purposely miss the one in line number 278 of builtin/show-branch.c because I think you only touched up the parts which were related to "refs/" but didn't explicitly mention it in the commit message?

    if (starts_with(pretty_str, "[PATCH] "))
        pretty_str += 8;

If not, you can squash this patch attached. Sorry, couldn't send it in mail because of proxy issues.

Regards, Pranit Bauva

From 2e80d4458df659765011450621ee34459dc749f9 Mon Sep 17 00:00:00 2001
From: Pranit Bauva <pranit.bauva@gmail.com>
Date: Tue, 14 Feb 2017 23:53:36 +0530
Subject: [PATCH] !squash: show-branch: use skip_prefix to drop magic numbers
Signed-off-by: Pranit Bauva <pranit.bauva@gmail.com>
---
 builtin/show-branch.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
Show changes to builtin/show-branch.c +1 −2
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index c03d3ec7c..19756595d 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -275,8 +275,7 @@ static void show_one_commit(struct commit *commit, int no_name)
 		pp_commit_easy(CMIT_FMT_ONELINE, commit, &pretty);
 		pretty_str = pretty.buf;
 	}
-	if (starts_with(pretty_str, "[PATCH] "))
-		pretty_str += 8;
+	skip_prefix(pretty_str, "[PATCH] ", &pretty_str);
 
 	if (!no_name) {
 		if (name && name->head_name) {
-- 
2.11.0
Jeff King· Feb 14, 2017, 19:53 UTC · re: Pranit Bauva · lore

Re: [PATCH 3/3] show-branch: use skip_prefix to drop magic numbers

On Wed, Feb 15, 2017 at 12:23:52AM +0530, Pranit Bauva wrote:
Show 7 quoted lines
> Did you purposely miss the one in line number 278 of
> builtin/show-branch.c because I think you only touched up the parts
> which were related to "refs/" but didn't explicitly mention it in the
> commit message?
> 
>     if (starts_with(pretty_str, "[PATCH] "))
>         pretty_str += 8;

Not purposely. I was just fixing up the bits that I had noticed in the earlier patches.

Show 11 quoted lines
> diff --git a/builtin/show-branch.c b/builtin/show-branch.c
> index c03d3ec7c..19756595d 100644
> --- a/builtin/show-branch.c
> +++ b/builtin/show-branch.c
> @@ -275,8 +275,7 @@ static void show_one_commit(struct commit *commit, int no_name)
>  		pp_commit_easy(CMIT_FMT_ONELINE, commit, &pretty);
>  		pretty_str = pretty.buf;
>  	}
> -	if (starts_with(pretty_str, "[PATCH] "))
> -		pretty_str += 8;
> +	skip_prefix(pretty_str, "[PATCH] ", &pretty_str);
Yeah, I agree this the same type of fix and is worth including. Thanks!
-Peff
Junio C Hamano· Feb 14, 2017, 19:35 UTC · re: Jeff King · lore

Re: [PATCH] show-branch: fix crash with long ref name

Jeff King <peff@peff.net> writes:
Show 9 quoted lines
> This fixes the problem, but I think we can simplify it quite a bit by
> using resolve_refdup(). Here's the patch series I ended up with:
>
>   [1/3]: show-branch: drop head_len variable
>   [2/3]: show-branch: store resolved head in heap buffer
>   [3/3]: show-branch: use skip_prefix to drop magic numbers
>
>  builtin/show-branch.c | 39 ++++++++++++---------------------------
>  1 file changed, 12 insertions(+), 27 deletions(-)

Yes, the whole thing is my fault ;-) and I agree with what these patches do.

The second one lacks free(head) but I think that is OK; it is something we allocate in cmd_*() and use pretty much thruout the rest of the program.

Thanks.
Jeff King· Feb 14, 2017, 19:55 UTC · re: Junio C Hamano · lore

Re: [PATCH] show-branch: fix crash with long ref name

On Tue, Feb 14, 2017 at 11:35:48AM -0800, Junio C Hamano wrote:
Show 18 quoted lines
> Jeff King <peff@peff.net> writes:
> 
> > This fixes the problem, but I think we can simplify it quite a bit by
> > using resolve_refdup(). Here's the patch series I ended up with:
> >
> >   [1/3]: show-branch: drop head_len variable
> >   [2/3]: show-branch: store resolved head in heap buffer
> >   [3/3]: show-branch: use skip_prefix to drop magic numbers
> >
> >  builtin/show-branch.c | 39 ++++++++++++---------------------------
> >  1 file changed, 12 insertions(+), 27 deletions(-)
> 
> Yes, the whole thing is my fault ;-) and I agree with what these
> patches do.
> 
> The second one lacks free(head) but I think that is OK; it is
> something we allocate in cmd_*() and use pretty much thruout the
> rest of the program.

Yes, I actually tested the whole thing under ASAN (which was necessary to notice the problem), which complained about the leak. I don't mind adding a free(head), but there are a bunch of similar "leaks" in that function, so I didn't bother.

I notice Christian's patch added a few tests. I don't know if we'd want to squash them in (I didn't mean to override his patch at all; I was about to send mine out when I noticed his, and I wondered if we wanted to combine the two efforts).

-Peff
Christian Couder· Feb 14, 2017, 21:29 UTC · re: Jeff King · lore

Re: [PATCH] show-branch: fix crash with long ref name

On Tue, Feb 14, 2017 at 8:55 PM, Jeff King <peff@peff.net> wrote:
Show 10 quoted lines
> On Tue, Feb 14, 2017 at 11:35:48AM -0800, Junio C Hamano wrote:
>
>> Jeff King <peff@peff.net> writes:
>>
>> > This fixes the problem, but I think we can simplify it quite a bit by
>> > using resolve_refdup(). Here's the patch series I ended up with:
>> >
>> >   [1/3]: show-branch: drop head_len variable
>> >   [2/3]: show-branch: store resolved head in heap buffer
>> >   [3/3]: show-branch: use skip_prefix to drop magic numbers

Yeah, I noticed there were a number of things that could be improved in the area, but I didn't want to spend too much time on this, so thanks for this series.

Show 11 quoted lines
>> Yes, the whole thing is my fault ;-) and I agree with what these
>> patches do.
>>
>> The second one lacks free(head) but I think that is OK; it is
>> something we allocate in cmd_*() and use pretty much thruout the
>> rest of the program.
>
> Yes, I actually tested the whole thing under ASAN (which was necessary
> to notice the problem), which complained about the leak. I don't mind
> adding a free(head), but there are a bunch of similar "leaks" in that
> function, so I didn't bother.
Yeah, I didn't bother either.
> I notice Christian's patch added a few tests. I don't know if we'd want
> to squash them in (I didn't mean to override his patch at all; I was
> about to send mine out when I noticed his, and I wondered if we wanted
> to combine the two efforts).

I think it would be nice to have at least one test. Feel free to squash mine if you want.

Jeff King· Feb 15, 2017, 21:40 UTC · re: Christian Couder · lore

Re: [PATCH] show-branch: fix crash with long ref name

On Tue, Feb 14, 2017 at 10:29:46PM +0100, Christian Couder wrote:
Show 7 quoted lines
> > I notice Christian's patch added a few tests. I don't know if we'd want
> > to squash them in (I didn't mean to override his patch at all; I was
> > about to send mine out when I noticed his, and I wondered if we wanted
> > to combine the two efforts).
> 
> I think it would be nice to have at least one test. Feel free to
> squash mine if you want.

I started to add some tests, but I had second thoughts. It _is_ nice to show off the fix, but as far as regressions go, this specific case is unlikely to come up again. What would be more valuable, I think, is a test script which set up a very long refname (not just 150 bytes or whatever) and ran it through a series of git commands.

But then you run into all sorts of portability annoyances with pathname restrictions (you can hack around creation by writing the refname directly into packed-refs, but most manipulations will want to take the .lock in the filesystem). So I dunno. It seems like being thorough is a lot of hassle for not much gain. Being not-thorough is easy, but is mostly a token that is unlikely to find any real bugs.

So I punted, at least for now.

I see the patches are marked for 'next' in the latest What's Cooking. If it is not too late in today's integration cycle, here is a re-roll of patch 3 that squashes in Pranit's suggestion (if it is too late, then Pranit, you may want to re-send it as a squash on top).

-- >8 --
Subject: [PATCH] show-branch: use skip_prefix to drop magic numbers

We make several starts_with() calls, only to advance pointers. This is exactly what skip_prefix() is for, which lets us avoid manually-counted magic numbers.

Helped-by: Pranit Bauva <pranit.bauva@gmail.com>
Signed-off-by: Jeff King <peff@peff.net>
---
 builtin/show-branch.c | 19 ++++++++-----------
 1 file changed, 8 insertions(+), 11 deletions(-)
Show changes to builtin/show-branch.c +8 −11
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index 404c4d09a..19756595d 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -275,8 +275,7 @@ static void show_one_commit(struct commit *commit, int no_name)
 		pp_commit_easy(CMIT_FMT_ONELINE, commit, &pretty);
 		pretty_str = pretty.buf;
 	}
-	if (starts_with(pretty_str, "[PATCH] "))
-		pretty_str += 8;
+	skip_prefix(pretty_str, "[PATCH] ", &pretty_str);
 
 	if (!no_name) {
 		if (name && name->head_name) {
@@ -470,17 +469,14 @@ static void snarf_refs(int head, int remotes)
 	}
 }
 
-static int rev_is_head(char *head, char *name,
+static int rev_is_head(const char *head, const char *name,
 		       unsigned char *head_sha1, unsigned char *sha1)
 {
 	if (!head || (head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
 		return 0;
-	if (starts_with(head, "refs/heads/"))
-		head += 11;
-	if (starts_with(name, "refs/heads/"))
-		name += 11;
-	else if (starts_with(name, "heads/"))
-		name += 6;
+	skip_prefix(head, "refs/heads/", &head);
+	if (!skip_prefix(name, "refs/heads/", &name))
+		skip_prefix(name, "heads/", &name);
 	return !strcmp(head, name);
 }
 
@@ -799,8 +795,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
 				has_head++;
 		}
 		if (!has_head) {
-			int offset = starts_with(head, "refs/heads/") ? 11 : 0;
-			append_one_rev(head + offset);
+			const char *name = head;
+			skip_prefix(name, "refs/heads/", &name);
+			append_one_rev(name);
 		}
 	}
 
-- 
2.12.0.rc1.541.g3e32dea89
Junio C Hamano· Feb 15, 2017, 21:50 UTC · re: Jeff King · lore

Re: [PATCH] show-branch: fix crash with long ref name

Jeff King <peff@peff.net> writes:
> I see the patches are marked for 'next' in the latest What's Cooking.
> If it is not too late in today's integration cycle, here is a re-roll of
> patch 3 that squashes in Pranit's suggestion (if it is too late, then
> Pranit, you may want to re-send it as a squash on top).
Thanks.  
I think that matches what I queued last night, except for the
Helped-by: line.  Will replace.
Show 60 quoted lines
> -- >8 --
> Subject: [PATCH] show-branch: use skip_prefix to drop magic numbers
>
> We make several starts_with() calls, only to advance
> pointers. This is exactly what skip_prefix() is for, which
> lets us avoid manually-counted magic numbers.
>
> Helped-by: Pranit Bauva <pranit.bauva@gmail.com>
> Signed-off-by: Jeff King <peff@peff.net>
> ---
>  builtin/show-branch.c | 19 ++++++++-----------
>  1 file changed, 8 insertions(+), 11 deletions(-)
>
> diff --git a/builtin/show-branch.c b/builtin/show-branch.c
> index 404c4d09a..19756595d 100644
> --- a/builtin/show-branch.c
> +++ b/builtin/show-branch.c
> @@ -275,8 +275,7 @@ static void show_one_commit(struct commit *commit, int no_name)
>  		pp_commit_easy(CMIT_FMT_ONELINE, commit, &pretty);
>  		pretty_str = pretty.buf;
>  	}
> -	if (starts_with(pretty_str, "[PATCH] "))
> -		pretty_str += 8;
> +	skip_prefix(pretty_str, "[PATCH] ", &pretty_str);
>  
>  	if (!no_name) {
>  		if (name && name->head_name) {
> @@ -470,17 +469,14 @@ static void snarf_refs(int head, int remotes)
>  	}
>  }
>  
> -static int rev_is_head(char *head, char *name,
> +static int rev_is_head(const char *head, const char *name,
>  		       unsigned char *head_sha1, unsigned char *sha1)
>  {
>  	if (!head || (head_sha1 && sha1 && hashcmp(head_sha1, sha1)))
>  		return 0;
> -	if (starts_with(head, "refs/heads/"))
> -		head += 11;
> -	if (starts_with(name, "refs/heads/"))
> -		name += 11;
> -	else if (starts_with(name, "heads/"))
> -		name += 6;
> +	skip_prefix(head, "refs/heads/", &head);
> +	if (!skip_prefix(name, "refs/heads/", &name))
> +		skip_prefix(name, "heads/", &name);
>  	return !strcmp(head, name);
>  }
>  
> @@ -799,8 +795,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
>  				has_head++;
>  		}
>  		if (!has_head) {
> -			int offset = starts_with(head, "refs/heads/") ? 11 : 0;
> -			append_one_rev(head + offset);
> +			const char *name = head;
> +			skip_prefix(name, "refs/heads/", &name);
> +			append_one_rev(name);
>  		}
>  	}
Jeff King· Feb 15, 2017, 21:52 UTC · re: Junio C Hamano · lore

Re: [PATCH] show-branch: fix crash with long ref name

On Wed, Feb 15, 2017 at 01:50:07PM -0800, Junio C Hamano wrote:
Show 11 quoted lines
> Jeff King <peff@peff.net> writes:
> 
> > I see the patches are marked for 'next' in the latest What's Cooking.
> > If it is not too late in today's integration cycle, here is a re-roll of
> > patch 3 that squashes in Pranit's suggestion (if it is too late, then
> > Pranit, you may want to re-send it as a squash on top).
> 
> Thanks.  
> 
> I think that matches what I queued last night, except for the
> Helped-by: line.  Will replace.
Oh, indeed. I should have actually checked what you queued. Thanks.
-Peff
Christian Couder· Feb 16, 2017, 12:40 UTC · re: Jeff King · lore

Re: [PATCH] show-branch: fix crash with long ref name

On Wed, Feb 15, 2017 at 10:40 PM, Jeff King <peff@peff.net> wrote:
Show 15 quoted lines
> On Tue, Feb 14, 2017 at 10:29:46PM +0100, Christian Couder wrote:
>
>> > I notice Christian's patch added a few tests. I don't know if we'd want
>> > to squash them in (I didn't mean to override his patch at all; I was
>> > about to send mine out when I noticed his, and I wondered if we wanted
>> > to combine the two efforts).
>>
>> I think it would be nice to have at least one test. Feel free to
>> squash mine if you want.
>
> I started to add some tests, but I had second thoughts. It _is_ nice
> to show off the fix, but as far as regressions go, this specific case is
> unlikely to come up again. What would be more valuable, I think, is a
> test script which set up a very long refname (not just 150 bytes or
> whatever) and ran it through a series of git commands.

I agree that a test script running through a series of command with long refnames would be great.

But I think the refname should not necesarily be too long. As I wrote in the commit message of my patch, if the ref name had been much longer the crash would not have happened because the ref could not have been created in the first place.

So the best would be to run through a series of commands with a refname ranging from let's say 80 chars to 300 chars.

That would have a chance to catch crashes due to legacy code using for example things like `char stuff[128]` or `char stuff[256]`.

Implementing those tests could have started with something like the test case I sent, but as it would in the end be about many different commands, one can see it as part of a different topic.

> But then you run into all sorts of portability annoyances with pathname
> restrictions (you can hack around creation by writing the refname
> directly into packed-refs, but most manipulations will want to take the
> .lock in the filesystem).

Yeah, but if a crash doesn't happen because we die() as the ref is too long for the file system, we could detect that and make the test succeed.

> So I dunno. It seems like being thorough is a
> lot of hassle for not much gain. Being not-thorough is easy, but is
> mostly a token that is unlikely to find any real bugs.

Yeah, if we really care, it might be better to start using a fuzzer or a property based testing tool instead of bothering with these kind of tests by ourselves, which is also a different topic.

> So I punted, at least for now.
Ok, no problem.
Jeff King· Feb 17, 2017, 05:03 UTC · re: Christian Couder · lore

Re: [PATCH] show-branch: fix crash with long ref name

On Thu, Feb 16, 2017 at 01:40:00PM +0100, Christian Couder wrote:
Show 13 quoted lines
> > I started to add some tests, but I had second thoughts. It _is_ nice
> > to show off the fix, but as far as regressions go, this specific case is
> > unlikely to come up again. What would be more valuable, I think, is a
> > test script which set up a very long refname (not just 150 bytes or
> > whatever) and ran it through a series of git commands.
> 
> I agree that a test script running through a series of command with
> long refnames would be great.
> 
> But I think the refname should not necesarily be too long. As I wrote
> in the commit message of my patch, if the ref name had been much
> longer the crash would not have happened because the ref could not
> have been created in the first place.

Right, I think there's a tension there. Too short and it is not interesting, and too long and things start to fail for uninteresting reasons (e.g., your filesystem can't handle the name).

Show 5 quoted lines
> So the best would be to run through a series of commands with a
> refname ranging from let's say 80 chars to 300 chars.
> 
> That would have a chance to catch crashes due to legacy code using for
> example things like `char stuff[128]` or `char stuff[256]`.

But that doesn't catch `char stuff[512]`. I think you'd really want a range of sizes, and to test all of them. Worse, though, is it's not clear how you decide when a test has failed. Obviously smashing the stack is a bad outcome, but part of the goal would presumably be to flush out unnecessary length limitations elsewhere.

I got about that far in my thinking before saying "wow, this is getting to be complicated for not much gain".

Show 7 quoted lines
> > So I dunno. It seems like being thorough is a
> > lot of hassle for not much gain. Being not-thorough is easy, but is
> > mostly a token that is unlikely to find any real bugs.
> 
> Yeah, if we really care, it might be better to start using a fuzzer or
> a property based testing tool instead of bothering with these kind of
> tests by ourselves, which is also a different topic.

Yes, I'd agree that a fuzzer is probably a better choice. I played with AFL a bit back when it came out, but I never got it to turn up anything useful.

I am disappointed that this obvious memory problem survived for so long. I did quite a bit of auditing for such problems a year or two ago, but I focused on problematic functions like strcpy, sprintf, etc.

It's easy to use memcpy() wrong, too, but it's hard to audit. There are a lot of calls, and you have to match up the copied length with a value computed elsewhere. I traded a lot of them out back then for safer variants (like the FLEX_ALLOC stuff), but many calls just fundamentally need something unsafe like memcpy to get their job done.

-Peff

← back to recent threads