[PATCH] wt-status: use strncmp() for length-limited string comparison

classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

[PATCH] wt-status: use strncmp() for length-limited string comparison

René Scharfe-2
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 <[hidden email]>
---
 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


--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to [hidden email]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Reply | Threaded
Open this post in threaded view
|

Re: [PATCH] wt-status: use strncmp() for length-limited string comparison

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.

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
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to [hidden email]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Reply | Threaded
Open this post in threaded view
|

Re: [PATCH] wt-status: use strncmp() for length-limited string comparison

René Scharfe-2
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;
 }

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to [hidden email]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Reply | Threaded
Open this post in threaded view
|

Re: [PATCH] wt-status: use strncmp() for length-limited string comparison

Jeff King
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
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to [hidden email]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Reply | Threaded
Open this post in threaded view
|

Re: [PATCH] wt-status: use strncmp() for length-limited string comparison

Matthieu Moy-2
Jeff King <[hidden email]> 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/
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to [hidden email]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Reply | Threaded
Open this post in threaded view
|

[PATCH v2] wt-status: correct and simplify check for detached HEAD

René Scharfe-2
In reply to this post by Jeff King
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 <[hidden email]>
Signed-off-by: Rene Scharfe <[hidden email]>
---
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

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to [hidden email]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Reply | Threaded
Open this post in threaded view
|

Re: [PATCH v2] wt-status: correct and simplify check for detached HEAD

Matthieu Moy-2
René Scharfe <[hidden email]> 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/
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to [hidden email]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Reply | Threaded
Open this post in threaded view
|

Re: [PATCH v2] wt-status: correct and simplify check for detached HEAD

Jeff King
In reply to this post by René Scharfe-2
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 <[hidden email]>
> Signed-off-by: Rene Scharfe <[hidden email]>
> ---
> 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
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to [hidden email]
More majordomo info at  http://vger.kernel.org/majordomo-info.html