All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] wt-status: use strncmp() for length-limited string comparison
@ 2015-11-06 22:47 René Scharfe
  2015-11-24 21:36 ` Jeff King
  0 siblings, 1 reply; 8+ messages in thread
From: René Scharfe @ 2015-11-06 22:47 UTC (permalink / raw)
  To: Git List; +Cc: Junio C Hamano, Matthieu Moy

When a branch name is longer than four characters, memcmp() can read
past the end of the string literal "HEAD".  Use strncmp() instead, which
stops at the end of a string.  This fixes the following test failures
with AddressSanitizer:

t3203-branch-output.sh                           (Wstat: 256 Tests: 18 Failed: 4)
  Failed tests:  12, 15-17
  Non-zero exit status: 1
t3412-rebase-root.sh                             (Wstat: 256 Tests: 31 Failed: 3)
  Failed tests:  28-29, 31
  Non-zero exit status: 1
t3507-cherry-pick-conflict.sh                    (Wstat: 256 Tests: 31 Failed: 4)
  Failed tests:  14, 29-31
  Non-zero exit status: 1
t3510-cherry-pick-sequence.sh                    (Wstat: 256 Tests: 39 Failed: 14)
  Failed tests:  17, 22-26, 28-30, 34-35, 37-39
  Non-zero exit status: 1
t3420-rebase-autostash.sh                        (Wstat: 256 Tests: 28 Failed: 4)
  Failed tests:  24-27
  Non-zero exit status: 1
t3404-rebase-interactive.sh                      (Wstat: 256 Tests: 91 Failed: 57)
  Failed tests:  17, 19, 21-42, 44, 46-74, 77, 81-82
  Non-zero exit status: 1
t3900-i18n-commit.sh                             (Wstat: 256 Tests: 34 Failed: 1)
  Failed test:  34
  Non-zero exit status: 1
t5407-post-rewrite-hook.sh                       (Wstat: 256 Tests: 14 Failed: 6)
  Failed tests:  9-14
  Non-zero exit status: 1
t7001-mv.sh                                      (Wstat: 256 Tests: 46 Failed: 5)
  Failed tests:  39-43
  Non-zero exit status: 1
t7509-commit.sh                                  (Wstat: 256 Tests: 12 Failed: 2)
  Failed tests:  11-12
  Non-zero exit status: 1
t7512-status-help.sh                             (Wstat: 256 Tests: 39 Failed: 35)
  Failed tests:  5-39
  Non-zero exit status: 1
t6030-bisect-porcelain.sh                        (Wstat: 256 Tests: 70 Failed: 1)
  Failed test:  13
  Non-zero exit status: 1

Signed-off-by: Rene Scharfe <l.s.r@web.de>
---
 wt-status.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/wt-status.c b/wt-status.c
index 435fc28..8dc281b 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -1319,7 +1319,7 @@ static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1,
 	hashcpy(cb->nsha1, nsha1);
 	for (end = target; *end && *end != '\n'; end++)
 		;
-	if (!memcmp(target, "HEAD", end - target)) {
+	if (!strncmp(target, "HEAD", end - target)) {
 		/* HEAD is relative. Resolve it to the right reflog entry. */
 		strbuf_addstr(&cb->buf,
 			      find_unique_abbrev(nsha1, DEFAULT_ABBREV));
-- 
2.6.3

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH] wt-status: use strncmp() for length-limited string comparison
  2015-11-06 22:47 [PATCH] wt-status: use strncmp() for length-limited string comparison René Scharfe
@ 2015-11-24 21:36 ` Jeff King
  2015-11-25  2:16   ` René Scharfe
  0 siblings, 1 reply; 8+ messages in thread
From: Jeff King @ 2015-11-24 21:36 UTC (permalink / raw)
  To: René Scharfe; +Cc: Git List, Junio C Hamano, Matthieu Moy

On Fri, Nov 06, 2015 at 11:47:03PM +0100, René Scharfe wrote:

> When a branch name is longer than four characters, memcmp() can read
> past the end of the string literal "HEAD".  Use strncmp() instead, which
> stops at the end of a string.  This fixes the following test failures
> with AddressSanitizer:

Hmm. I think this is mostly harmless, as a comparison like:

  memcmp("HEAD and more", "HEAD", strlen("HEAD"))

would yield non-zero when we compare the NUL in the second string to
whatever is in the first. So I assume what is going on is that memcmp is
doing larger compares than byte by byte, and is examining 4 or 8 bytes
starting at that NUL.

The outcome is equivalent, but we do touch memory that is not ours, so I
think this is a positive direction in that sense.

But...

> diff --git a/wt-status.c b/wt-status.c
> index 435fc28..8dc281b 100644
> --- a/wt-status.c
> +++ b/wt-status.c
> @@ -1319,7 +1319,7 @@ static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1,
>  	hashcpy(cb->nsha1, nsha1);
>  	for (end = target; *end && *end != '\n'; end++)
>  		;
> -	if (!memcmp(target, "HEAD", end - target)) {
> +	if (!strncmp(target, "HEAD", end - target)) {

This will match prefixes like "HEA" in the target, won't it?

I think you want something more like:

  if (end - target == 4 && !memcmp(target, "HEAD", 4))

I tried to think of a way that didn't involve a magic number. The best I
came up with is:

  if (skip_prefix(target, "HEAD", &v) && v == end)

but that requires an extra variable, and is arguably more obfuscated.

-Peff

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH] wt-status: use strncmp() for length-limited string comparison
  2015-11-24 21:36 ` Jeff King
@ 2015-11-25  2:16   ` René Scharfe
  2015-11-25  9:15     ` Jeff King
  0 siblings, 1 reply; 8+ messages in thread
From: René Scharfe @ 2015-11-25  2:16 UTC (permalink / raw)
  To: Jeff King; +Cc: Git List, Junio C Hamano, Matthieu Moy

Am 24.11.2015 um 22:36 schrieb Jeff King:
> On Fri, Nov 06, 2015 at 11:47:03PM +0100, René Scharfe wrote:
> 
>> When a branch name is longer than four characters, memcmp() can read
>> past the end of the string literal "HEAD".  Use strncmp() instead, which
>> stops at the end of a string.  This fixes the following test failures
>> with AddressSanitizer:
> 
> Hmm. I think this is mostly harmless, as a comparison like:
> 
>    memcmp("HEAD and more", "HEAD", strlen("HEAD"))
> 
> would yield non-zero when we compare the NUL in the second string to
> whatever is in the first. So I assume what is going on is that memcmp is
> doing larger compares than byte by byte, and is examining 4 or 8 bytes
> starting at that NUL.
> 
> The outcome is equivalent, but we do touch memory that is not ours, so I
> think this is a positive direction in that sense.

Yes, except it should be strlen("HEAD and more") in your example code;
with strlen("HEAD") it would compare just 4 bytes and return 0.

> But...
> 
>> diff --git a/wt-status.c b/wt-status.c
>> index 435fc28..8dc281b 100644
>> --- a/wt-status.c
>> +++ b/wt-status.c
>> @@ -1319,7 +1319,7 @@ static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1,
>>   	hashcpy(cb->nsha1, nsha1);
>>   	for (end = target; *end && *end != '\n'; end++)
>>   		;
>> -	if (!memcmp(target, "HEAD", end - target)) {
>> +	if (!strncmp(target, "HEAD", end - target)) {
> 
> This will match prefixes like "HEA" in the target, won't it?

Oww, yes. :-/  NB: The existing code does the same.

> I think you want something more like:
> 
>    if (end - target == 4 && !memcmp(target, "HEAD", 4))
> 
> I tried to think of a way that didn't involve a magic number. The best I
> came up with is:
> 
>    if (skip_prefix(target, "HEAD", &v) && v == end)
> 
> but that requires an extra variable, and is arguably more obfuscated.

Using one more variable isn't that bad, as long as it gets a fitting
name.  Or we could reuse "end" (I'm not worrying about scanning "HEAD"
twice very much):

diff --git a/wt-status.c b/wt-status.c
index 435fc28..96a731e 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -1317,14 +1317,14 @@ static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1,
 	target += strlen(" to ");
 	strbuf_reset(&cb->buf);
 	hashcpy(cb->nsha1, nsha1);
-	for (end = target; *end && *end != '\n'; end++)
-		;
-	if (!memcmp(target, "HEAD", end - target)) {
+	if (skip_prefix(target, "HEAD", &end) && (!*end || *end == '\n')) {
 		/* HEAD is relative. Resolve it to the right reflog entry. */
 		strbuf_addstr(&cb->buf,
 			      find_unique_abbrev(nsha1, DEFAULT_ABBREV));
 		return 1;
 	}
+	for (end = target; *end && *end != '\n'; end++)
+		;
 	strbuf_add(&cb->buf, target, end - target);
 	return 1;
 }

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH] wt-status: use strncmp() for length-limited string comparison
  2015-11-25  2:16   ` René Scharfe
@ 2015-11-25  9:15     ` Jeff King
  2015-11-25 10:29       ` Matthieu Moy
  2015-11-25 14:10       ` [PATCH v2] wt-status: correct and simplify check for detached HEAD René Scharfe
  0 siblings, 2 replies; 8+ messages in thread
From: Jeff King @ 2015-11-25  9:15 UTC (permalink / raw)
  To: René Scharfe; +Cc: Git List, Junio C Hamano, Matthieu Moy

On Wed, Nov 25, 2015 at 03:16:49AM +0100, René Scharfe wrote:

> > Hmm. I think this is mostly harmless, as a comparison like:
> > 
> >    memcmp("HEAD and more", "HEAD", strlen("HEAD"))
> [...]
> 
> Yes, except it should be strlen("HEAD and more") in your example code;
> with strlen("HEAD") it would compare just 4 bytes and return 0.

Whoops, yeah. Thank you for figuring out what I meant. :)

> Using one more variable isn't that bad, as long as it gets a fitting
> name.  Or we could reuse "end" (I'm not worrying about scanning "HEAD"
> twice very much):
> 
> diff --git a/wt-status.c b/wt-status.c
> index 435fc28..96a731e 100644
> --- a/wt-status.c
> +++ b/wt-status.c
> @@ -1317,14 +1317,14 @@ static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1,
>  	target += strlen(" to ");
>  	strbuf_reset(&cb->buf);
>  	hashcpy(cb->nsha1, nsha1);
> -	for (end = target; *end && *end != '\n'; end++)
> -		;
> -	if (!memcmp(target, "HEAD", end - target)) {
> +	if (skip_prefix(target, "HEAD", &end) && (!*end || *end == '\n')) {
>  		/* HEAD is relative. Resolve it to the right reflog entry. */
>  		strbuf_addstr(&cb->buf,
>  			      find_unique_abbrev(nsha1, DEFAULT_ABBREV));
>  		return 1;
>  	}

Yeah, I think parsing left-to-right like this makes things much more
obvious. And regarding scanning HEAD twice, I think we already do that
(we find the trailing newline first in the current code). Though I agree
that is absurd premature optimization.

> +	for (end = target; *end && *end != '\n'; end++)
> +		;

This loop (which I know you just moved, not wrote) is basically
strchrnul, isn't it? That might be more readable.

-Peff

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH] wt-status: use strncmp() for length-limited string comparison
  2015-11-25  9:15     ` Jeff King
@ 2015-11-25 10:29       ` Matthieu Moy
  2015-11-25 14:10       ` [PATCH v2] wt-status: correct and simplify check for detached HEAD René Scharfe
  1 sibling, 0 replies; 8+ messages in thread
From: Matthieu Moy @ 2015-11-25 10:29 UTC (permalink / raw)
  To: Jeff King; +Cc: René Scharfe, Git List, Junio C Hamano

Jeff King <peff@peff.net> writes:

>> diff --git a/wt-status.c b/wt-status.c
>> index 435fc28..96a731e 100644
>> --- a/wt-status.c
>> +++ b/wt-status.c
>> @@ -1317,14 +1317,14 @@ static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1,
>>  	target += strlen(" to ");
>>  	strbuf_reset(&cb->buf);
>>  	hashcpy(cb->nsha1, nsha1);
>> -	for (end = target; *end && *end != '\n'; end++)
>> -		;
>> -	if (!memcmp(target, "HEAD", end - target)) {
>> +	if (skip_prefix(target, "HEAD", &end) && (!*end || *end == '\n')) {
>>  		/* HEAD is relative. Resolve it to the right reflog entry. */
>>  		strbuf_addstr(&cb->buf,
>>  			      find_unique_abbrev(nsha1, DEFAULT_ABBREV));
>>  		return 1;
>>  	}
>
> Yeah, I think parsing left-to-right like this makes things much more
> obvious.

Agreed.

>> +	for (end = target; *end && *end != '\n'; end++)
>> +		;
>
> This loop (which I know you just moved, not wrote) is basically
> strchrnul, isn't it? That might be more readable.

Agreed too.

-- 
Matthieu Moy
http://www-verimag.imag.fr/~moy/

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v2] wt-status: correct and simplify check for detached HEAD
  2015-11-25  9:15     ` Jeff King
  2015-11-25 10:29       ` Matthieu Moy
@ 2015-11-25 14:10       ` René Scharfe
  2015-11-25 16:21         ` Matthieu Moy
  2015-11-28 17:31         ` Jeff King
  1 sibling, 2 replies; 8+ messages in thread
From: René Scharfe @ 2015-11-25 14:10 UTC (permalink / raw)
  To: Jeff King; +Cc: Git List, Junio C Hamano, Matthieu Moy, Duy Nguyen

If a branch name is longer than four characters then memcmp() reads over
the end of the static string "HEAD".  This causes the following test
failures with AddressSanitizer:

t3203-branch-output.sh                           (Wstat: 256 Tests: 18 Failed: 4)
  Failed tests:  12, 15-17
  Non-zero exit status: 1
t3412-rebase-root.sh                             (Wstat: 256 Tests: 31 Failed: 3)
  Failed tests:  28-29, 31
  Non-zero exit status: 1
t3507-cherry-pick-conflict.sh                    (Wstat: 256 Tests: 31 Failed: 4)
  Failed tests:  14, 29-31
  Non-zero exit status: 1
t3510-cherry-pick-sequence.sh                    (Wstat: 256 Tests: 39 Failed: 14)
  Failed tests:  17, 22-26, 28-30, 34-35, 37-39
  Non-zero exit status: 1
t3420-rebase-autostash.sh                        (Wstat: 256 Tests: 28 Failed: 4)
  Failed tests:  24-27
  Non-zero exit status: 1
t3404-rebase-interactive.sh                      (Wstat: 256 Tests: 91 Failed: 57)
  Failed tests:  17, 19, 21-42, 44, 46-74, 77, 81-82
  Non-zero exit status: 1
t3900-i18n-commit.sh                             (Wstat: 256 Tests: 34 Failed: 1)
  Failed test:  34
  Non-zero exit status: 1
t5407-post-rewrite-hook.sh                       (Wstat: 256 Tests: 14 Failed: 6)
  Failed tests:  9-14
  Non-zero exit status: 1
t7001-mv.sh                                      (Wstat: 256 Tests: 46 Failed: 5)
  Failed tests:  39-43
  Non-zero exit status: 1
t7509-commit.sh                                  (Wstat: 256 Tests: 12 Failed: 2)
  Failed tests:  11-12
  Non-zero exit status: 1
t7512-status-help.sh                             (Wstat: 256 Tests: 39 Failed: 35)
  Failed tests:  5-39
  Non-zero exit status: 1
t6030-bisect-porcelain.sh                        (Wstat: 256 Tests: 70 Failed: 1)
  Failed test:  13
  Non-zero exit status: 1

And if a branch is named "H", "HE", or "HEA" then the current if clause
erroneously considers it as matching "HEAD" because it only compares
up to the end of the branch name.

Fix that by doing the comparison using strcmp() and only after the
branch name is extracted.  This way neither too less nor too many
characters are checked.  While at it call strchrnul() to find the end
of the branch name instead of open-coding it.

Helped-by: Jeff King <peff@peff.net>
Signed-off-by: Rene Scharfe <l.s.r@web.de>
---
We can be more careful when parsing -- or avoid parsing and backtrack
if we found "HEAD" after all.  The latter is simpler, and string
parsing is tricky enough that we better take such opportunities to
simplify the code..

Changes since v1:
* strcmp() instead of strncmp()
* strchrnul() (unrelated cleanup)
* adjusted subject (and commit message)

 wt-status.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/wt-status.c b/wt-status.c
index 435fc28..ced53dd 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -1317,15 +1317,14 @@ static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1,
 	target += strlen(" to ");
 	strbuf_reset(&cb->buf);
 	hashcpy(cb->nsha1, nsha1);
-	for (end = target; *end && *end != '\n'; end++)
-		;
-	if (!memcmp(target, "HEAD", end - target)) {
+	end = strchrnul(target, '\n');
+	strbuf_add(&cb->buf, target, end - target);
+	if (!strcmp(cb->buf.buf, "HEAD")) {
 		/* HEAD is relative. Resolve it to the right reflog entry. */
+		strbuf_reset(&cb->buf);
 		strbuf_addstr(&cb->buf,
 			      find_unique_abbrev(nsha1, DEFAULT_ABBREV));
-		return 1;
 	}
-	strbuf_add(&cb->buf, target, end - target);
 	return 1;
 }
 
-- 
2.6.3

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH v2] wt-status: correct and simplify check for detached HEAD
  2015-11-25 14:10       ` [PATCH v2] wt-status: correct and simplify check for detached HEAD René Scharfe
@ 2015-11-25 16:21         ` Matthieu Moy
  2015-11-28 17:31         ` Jeff King
  1 sibling, 0 replies; 8+ messages in thread
From: Matthieu Moy @ 2015-11-25 16:21 UTC (permalink / raw)
  To: René Scharfe; +Cc: Jeff King, Git List, Junio C Hamano, Duy Nguyen

René Scharfe <l.s.r@web.de> writes:

> diff --git a/wt-status.c b/wt-status.c
> index 435fc28..ced53dd 100644
> --- a/wt-status.c
> +++ b/wt-status.c
> @@ -1317,15 +1317,14 @@ static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1,
>  	target += strlen(" to ");
>  	strbuf_reset(&cb->buf);
>  	hashcpy(cb->nsha1, nsha1);
> -	for (end = target; *end && *end != '\n'; end++)
> -		;
> -	if (!memcmp(target, "HEAD", end - target)) {
> +	end = strchrnul(target, '\n');
> +	strbuf_add(&cb->buf, target, end - target);
> +	if (!strcmp(cb->buf.buf, "HEAD")) {
>  		/* HEAD is relative. Resolve it to the right reflog entry. */
> +		strbuf_reset(&cb->buf);
>  		strbuf_addstr(&cb->buf,
>  			      find_unique_abbrev(nsha1, DEFAULT_ABBREV));
> -		return 1;
>  	}
> -	strbuf_add(&cb->buf, target, end - target);
>  	return 1;
>  }

Indeed, the code is much simpler like this than with the previous
attempts. Looks all right to me.

-- 
Matthieu Moy
http://www-verimag.imag.fr/~moy/

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2] wt-status: correct and simplify check for detached HEAD
  2015-11-25 14:10       ` [PATCH v2] wt-status: correct and simplify check for detached HEAD René Scharfe
  2015-11-25 16:21         ` Matthieu Moy
@ 2015-11-28 17:31         ` Jeff King
  1 sibling, 0 replies; 8+ messages in thread
From: Jeff King @ 2015-11-28 17:31 UTC (permalink / raw)
  To: René Scharfe; +Cc: Git List, Junio C Hamano, Matthieu Moy, Duy Nguyen

On Wed, Nov 25, 2015 at 03:10:18PM +0100, René Scharfe wrote:

> Fix that by doing the comparison using strcmp() and only after the
> branch name is extracted.  This way neither too less nor too many
> characters are checked.  While at it call strchrnul() to find the end
> of the branch name instead of open-coding it.
> 
> Helped-by: Jeff King <peff@peff.net>
> Signed-off-by: Rene Scharfe <l.s.r@web.de>
> ---
> We can be more careful when parsing -- or avoid parsing and backtrack
> if we found "HEAD" after all.  The latter is simpler, and string
> parsing is tricky enough that we better take such opportunities to
> simplify the code..

Yeah, I think this is nicer. We end up allocating either way anyway, so
the only extra effort is copy the string.

-Peff

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2015-11-28 17:32 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-11-06 22:47 [PATCH] wt-status: use strncmp() for length-limited string comparison René Scharfe
2015-11-24 21:36 ` Jeff King
2015-11-25  2:16   ` René Scharfe
2015-11-25  9:15     ` Jeff King
2015-11-25 10:29       ` Matthieu Moy
2015-11-25 14:10       ` [PATCH v2] wt-status: correct and simplify check for detached HEAD René Scharfe
2015-11-25 16:21         ` Matthieu Moy
2015-11-28 17:31         ` Jeff King

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.