string: Add tests for zero length string inputs

Message ID 1410910830-20900-1-git-send-email-will.newton@linaro.org
State Dropped
Headers

Commit Message

Will Newton Sept. 16, 2014, 11:40 p.m. UTC
  For the string functions that take string lengths as an argument we
should ensure that no data is read or written if a length of zero is
specified. Pointers to PROT_NONE memory are used to ensure that any
reads or writes will cause a fault.

ChangeLog:

2014-09-16  Will Newton  <will.newton@linaro.org>

	* string/test-memccpy.c (do_test_zero_length): New function.
	(test_main): Call do_test_zero_length.
	* string/test-memchr.c: Likewise.
	* string/test-memcmp.c: Likewise.
	* string/test-memcpy.c: Likewise.
	* string/test-memmem.c: Likewise.
	* string/test-memmove.c: Likewise.
	* string/test-memrchr.c: Likewise.
	* string/test-memset.c: Likewise.
	* string/test-strncmp.c: Likewise.
	* string/test-strncpy.c: Likewise.
	* string/test-strnlen.c: Likewise.
	* string/test-strncasecmp.c (do_test_zero_length): New function.
	(test_locale): Call do_test_zero_length.
	* string/test-strncat.c (do_test_zero_length): New function.
	(main): Call do_test_zero_length.
---
 string/test-memccpy.c     | 10 ++++++++++
 string/test-memchr.c      |  9 +++++++++
 string/test-memcmp.c      | 11 +++++++++++
 string/test-memcpy.c      | 11 +++++++++++
 string/test-memmem.c      | 10 ++++++++++
 string/test-memmove.c     | 12 ++++++++++++
 string/test-memrchr.c     | 10 ++++++++++
 string/test-memset.c      | 11 +++++++++++
 string/test-strncasecmp.c | 15 +++++++++++++++
 string/test-strncat.c     | 24 ++++++++++++++++++++++++
 string/test-strncmp.c     | 11 +++++++++++
 string/test-strncpy.c     | 11 +++++++++++
 string/test-strnlen.c     | 10 ++++++++++
 13 files changed, 155 insertions(+)
  

Comments

Ondrej Bilka Sept. 19, 2014, 11:23 a.m. UTC | #1
On Tue, Sep 16, 2014 at 04:40:30PM -0700, Will Newton wrote:
> For the string functions that take string lengths as an argument we
> should ensure that no data is read or written if a length of zero is
> specified. Pointers to PROT_NONE memory are used to ensure that any
> reads or writes will cause a fault.
> 
You do not need these. C standard requires arguments to be valid
pointers for most string functions, and they are already marked nonnull
in header.

Just adding size 0 to inputs would suffice.
  
Will Newton Sept. 19, 2014, 5:09 p.m. UTC | #2
On 19 September 2014 04:23, Ondřej Bílka <neleai@seznam.cz> wrote:
> On Tue, Sep 16, 2014 at 04:40:30PM -0700, Will Newton wrote:
>> For the string functions that take string lengths as an argument we
>> should ensure that no data is read or written if a length of zero is
>> specified. Pointers to PROT_NONE memory are used to ensure that any
>> reads or writes will cause a fault.
>>
> You do not need these. C standard requires arguments to be valid
> pointers for most string functions, and they are already marked nonnull
> in header.
>
> Just adding size 0 to inputs would suffice.

These tests are not testing null pointers, they are testing that when
given a zero length the functions actually read/write zero bytes.
Whether the specification demands that behaviour is arguable but I
believe that it is the most sane behaviour.
  
Richard Earnshaw Sept. 22, 2014, 4:09 p.m. UTC | #3
On 19/09/14 18:09, Will Newton wrote:
> On 19 September 2014 04:23, Ondřej Bílka <neleai@seznam.cz> wrote:
>> On Tue, Sep 16, 2014 at 04:40:30PM -0700, Will Newton wrote:
>>> For the string functions that take string lengths as an argument we
>>> should ensure that no data is read or written if a length of zero is
>>> specified. Pointers to PROT_NONE memory are used to ensure that any
>>> reads or writes will cause a fault.
>>>
>> You do not need these. C standard requires arguments to be valid
>> pointers for most string functions, and they are already marked nonnull
>> in header.
>>
>> Just adding size 0 to inputs would suffice.
> 
> These tests are not testing null pointers, they are testing that when
> given a zero length the functions actually read/write zero bytes.
> Whether the specification demands that behaviour is arguable but I
> believe that it is the most sane behaviour.
> 

Valid pointers is more than just non-NULL.  In particular, it implies
that is safe to dereference the addressed byte in a source operand even
when the length parameter is zero.  Thus testing that no bytes are read
would be incorrect.
  
Will Newton Sept. 22, 2014, 4:15 p.m. UTC | #4
On 22 September 2014 09:09, Richard Earnshaw <rearnsha@arm.com> wrote:
> On 19/09/14 18:09, Will Newton wrote:
>> On 19 September 2014 04:23, Ondřej Bílka <neleai@seznam.cz> wrote:
>>> On Tue, Sep 16, 2014 at 04:40:30PM -0700, Will Newton wrote:
>>>> For the string functions that take string lengths as an argument we
>>>> should ensure that no data is read or written if a length of zero is
>>>> specified. Pointers to PROT_NONE memory are used to ensure that any
>>>> reads or writes will cause a fault.
>>>>
>>> You do not need these. C standard requires arguments to be valid
>>> pointers for most string functions, and they are already marked nonnull
>>> in header.
>>>
>>> Just adding size 0 to inputs would suffice.
>>
>> These tests are not testing null pointers, they are testing that when
>> given a zero length the functions actually read/write zero bytes.
>> Whether the specification demands that behaviour is arguable but I
>> believe that it is the most sane behaviour.
>>
>
> Valid pointers is more than just non-NULL.  In particular, it implies
> that is safe to dereference the addressed byte in a source operand even
> when the length parameter is zero.  Thus testing that no bytes are read
> would be incorrect.

If that is the case then I withdraw the patch. Is that requirement
documented anywhere?
  
Richard Earnshaw Sept. 22, 2014, 4:17 p.m. UTC | #5
On 22/09/14 17:15, Will Newton wrote:
> On 22 September 2014 09:09, Richard Earnshaw <rearnsha@arm.com> wrote:
>> On 19/09/14 18:09, Will Newton wrote:
>>> On 19 September 2014 04:23, Ondřej Bílka <neleai@seznam.cz> wrote:
>>>> On Tue, Sep 16, 2014 at 04:40:30PM -0700, Will Newton wrote:
>>>>> For the string functions that take string lengths as an argument we
>>>>> should ensure that no data is read or written if a length of zero is
>>>>> specified. Pointers to PROT_NONE memory are used to ensure that any
>>>>> reads or writes will cause a fault.
>>>>>
>>>> You do not need these. C standard requires arguments to be valid
>>>> pointers for most string functions, and they are already marked nonnull
>>>> in header.
>>>>
>>>> Just adding size 0 to inputs would suffice.
>>>
>>> These tests are not testing null pointers, they are testing that when
>>> given a zero length the functions actually read/write zero bytes.
>>> Whether the specification demands that behaviour is arguable but I
>>> believe that it is the most sane behaviour.
>>>
>>
>> Valid pointers is more than just non-NULL.  In particular, it implies
>> that is safe to dereference the addressed byte in a source operand even
>> when the length parameter is zero.  Thus testing that no bytes are read
>> would be incorrect.
> 
> If that is the case then I withdraw the patch. Is that requirement
> documented anywhere?
> 

C99 $7.21.1 clause 2.

R.
  
Paul Eggert Sept. 22, 2014, 5:48 p.m. UTC | #6
On 09/22/2014 09:09 AM, Richard Earnshaw wrote:
> Valid pointers is more than just non-NULL.  In particular, it implies
> that is safe to dereference the addressed byte in a source operand even
> when the length parameter is zero.

I just reread C99 7.1.4 clause 1 and 7.21.2 clause 2, and I don't see 
that implication.  For example, the following program appears to be 
strictly conforming:

    #include <string.h>

    char src[1];
    char dst[1];

    int
    main (void)
    {
      memcpy (dst, src + 1, 0);
      return 0;
    }

Here, src + 1 is a valid pointer even though one cannot safely 
dereference it.  So it appears to be reasonable to check that memcpy 
doesn't dereference the source when the size is zero.
  
Florian Weimer Sept. 23, 2014, 12:41 p.m. UTC | #7
On 09/22/2014 06:09 PM, Richard Earnshaw wrote:
>> These tests are not testing null pointers, they are testing that when
>> given a zero length the functions actually read/write zero bytes.
>> Whether the specification demands that behaviour is arguable but I
>> believe that it is the most sane behaviour.

> Valid pointers is more than just non-NULL.  In particular, it implies
> that is safe to dereference the addressed byte in a source operand even
> when the length parameter is zero.

Valid pointers can also point one element past the end of an array of 
objects.  Such pointers can occur naturally during the final iteration 
of buffer-processing loops.  I don't think it is reasonable to expect 
that programmers write special code (or at least early loop exits) to 
deal with this corner case.  This has to work, and if the C standard 
does not guarantee it does, it needs fixing.

> Thus testing that no bytes are read would be incorrect.

I disagree, per the above.
  
Richard Earnshaw Sept. 23, 2014, 12:53 p.m. UTC | #8
On 22/09/14 18:48, Paul Eggert wrote:
> On 09/22/2014 09:09 AM, Richard Earnshaw wrote:
>> Valid pointers is more than just non-NULL.  In particular, it implies
>> that is safe to dereference the addressed byte in a source operand even
>> when the length parameter is zero.
> 
> I just reread C99 7.1.4 clause 1 and 7.21.2 clause 2, and I don't see 
> that implication.  For example, the following program appears to be 
> strictly conforming:
> 
>     #include <string.h>
> 
>     char src[1];
>     char dst[1];
> 
>     int
>     main (void)
>     {
>       memcpy (dst, src + 1, 0);
>       return 0;
>     }
> 
> Here, src + 1 is a valid pointer even though one cannot safely 
> dereference it.  So it appears to be reasonable to check that memcpy 
> doesn't dereference the source when the size is zero.
> 

Read clause 1 of 7.1.4 again.  "If an argument to a function has an
invalid value ... or a pointer outside of the address space of the
program... the behaviour is undefined."

Ergo, if src+1 can point outside of the address space of the program,
it's undefined behaviour.  And by corollary, it must be safe to
dereference the precise location passed as the argument (but obviously,
not necessarily bytes either side of it).

My reading of those sections also leads me to believe that memcpy could
legitimately expect to perform "*(char*)dst = *(char*)dst", even if the
length is zero.

R.
  
Richard Earnshaw Sept. 23, 2014, 12:57 p.m. UTC | #9
On 23/09/14 13:41, Florian Weimer wrote:
> On 09/22/2014 06:09 PM, Richard Earnshaw wrote:
>>> These tests are not testing null pointers, they are testing that when
>>> given a zero length the functions actually read/write zero bytes.
>>> Whether the specification demands that behaviour is arguable but I
>>> believe that it is the most sane behaviour.
> 
>> Valid pointers is more than just non-NULL.  In particular, it implies
>> that is safe to dereference the addressed byte in a source operand even
>> when the length parameter is zero.
> 
> Valid pointers can also point one element past the end of an array of 
> objects.  

I don't think such a pointer forms a valid argument for a library
function though.  See my previous reply to Paul.

> Such pointers can occur naturally during the final iteration 
> of buffer-processing loops.  I don't think it is reasonable to expect 
> that programmers write special code (or at least early loop exits) to 
> deal with this corner case.  This has to work, and if the C standard 
> does not guarantee it does, it needs fixing.
> 
>> Thus testing that no bytes are read would be incorrect.
> 
> I disagree, per the above.
> 

R.
  
Richard Earnshaw Sept. 23, 2014, 1:01 p.m. UTC | #10
On 23/09/14 13:57, Richard Earnshaw wrote:
> On 23/09/14 13:41, Florian Weimer wrote:
>> On 09/22/2014 06:09 PM, Richard Earnshaw wrote:
>>>> These tests are not testing null pointers, they are testing that when
>>>> given a zero length the functions actually read/write zero bytes.
>>>> Whether the specification demands that behaviour is arguable but I
>>>> believe that it is the most sane behaviour.
>>
>>> Valid pointers is more than just non-NULL.  In particular, it implies
>>> that is safe to dereference the addressed byte in a source operand even
>>> when the length parameter is zero.
>>
>> Valid pointers can also point one element past the end of an array of 
>> objects.  
> 
> I don't think such a pointer forms a valid argument for a library
> function though.  See my previous reply to Paul.
> 
>> Such pointers can occur naturally during the final iteration 
>> of buffer-processing loops.  I don't think it is reasonable to expect 
>> that programmers write special code (or at least early loop exits) to 
>> deal with this corner case.  This has to work, and if the C standard 
>> does not guarantee it does, it needs fixing.
>>
>>> Thus testing that no bytes are read would be incorrect.
>>
>> I disagree, per the above.
>>
> 
> R.
> 

Furthermore, I can think of no reason why 7.21.1/2 would explicitly
require valid pointers when the length parameter was 0 unless it was
intended that dereferencing could occur.

R.
  
Andreas Schwab Sept. 23, 2014, 1:22 p.m. UTC | #11
Richard Earnshaw <rearnsha@arm.com> writes:

> On 22/09/14 18:48, Paul Eggert wrote:
>> On 09/22/2014 09:09 AM, Richard Earnshaw wrote:
>>> Valid pointers is more than just non-NULL.  In particular, it implies
>>> that is safe to dereference the addressed byte in a source operand even
>>> when the length parameter is zero.
>> 
>> I just reread C99 7.1.4 clause 1 and 7.21.2 clause 2, and I don't see 
>> that implication.  For example, the following program appears to be 
>> strictly conforming:
>> 
>>     #include <string.h>
>> 
>>     char src[1];
>>     char dst[1];
>> 
>>     int
>>     main (void)
>>     {
>>       memcpy (dst, src + 1, 0);
>>       return 0;
>>     }
>> 
>> Here, src + 1 is a valid pointer even though one cannot safely 
>> dereference it.  So it appears to be reasonable to check that memcpy 
>> doesn't dereference the source when the size is zero.
>> 
>
> Read clause 1 of 7.1.4 again.  "If an argument to a function has an
> invalid value ... or a pointer outside of the address space of the
> program... the behaviour is undefined."
>
> Ergo, if src+1 can point outside of the address space of the program,
> it's undefined behaviour.

src+1 is _not_ outside of the address space.  It is a valid pointer
(which you must not dereference).

Andreas.
  
Paul Eggert Sept. 23, 2014, 1:57 p.m. UTC | #12
Richard Earnshaw wrote:

> if src+1 can point outside of the address space of the program

As Andreas points out, src+1 does not point outside the address space of 
the program.  It is a valid pointer.

> My reading of those sections also leads me to believe that memcpy could
> legitimately expect to perform "*(char*)dst = *(char*)dst", even if the
> length is zero.

I'm sorry, but this reading is incorrect.  If the size is zero, memcpy 
cannot store any bytes into the destination.  Any memcpy that does 
otherwise would break a lot of programs.

> I can think of no reason why 7.21.1/2 would explicitly
> require valid pointers when the length parameter was 0 unless it was
> intended that dereferencing could occur.

It caters to (unusual) architectures that require valid pointers in 
address registers even when the pointers are not dereferenced, e.g., 
loading a pointer into an address register will trap if the pointer is 
invalid.
  
Richard Earnshaw Sept. 23, 2014, 2:58 p.m. UTC | #13
On 23/09/14 14:57, Paul Eggert wrote:
> Richard Earnshaw wrote:
> 
>> if src+1 can point outside of the address space of the program
> 
> As Andreas points out, src+1 does not point outside the address space of 
> the program.  It is a valid pointer.
> 

OK, so do we agree that for a valid pointer P, if P is *not*
dereferencable, then P-1 must be?  Put another way, if P  and P-1 are in
the same 'page' then it is safe to dereference them.

R.
  
Rich Felker Sept. 23, 2014, 3 p.m. UTC | #14
On Tue, Sep 23, 2014 at 06:57:53AM -0700, Paul Eggert wrote:
> Richard Earnshaw wrote:
> 
> >if src+1 can point outside of the address space of the program
> 
> As Andreas points out, src+1 does not point outside the address
> space of the program.  It is a valid pointer.

Agree.

> >My reading of those sections also leads me to believe that memcpy could
> >legitimately expect to perform "*(char*)dst = *(char*)dst", even if the
> >length is zero.
> 
> I'm sorry, but this reading is incorrect.  If the size is zero,
> memcpy cannot store any bytes into the destination.  Any memcpy that
> does otherwise would break a lot of programs.

Note that even if the dest pointer can legally be dereferenced, it's
absolutely illegal in C11 for memcpy to store anything there (even
rewriting the value that's already there) since it would introduce a
data race and violate the memory model requirements. The importance of
this point cannot be overstated: as of C11, all extraneous writes that
may have been careless, inconsequential implementation details in the
past are now serious implementation bugs!

> >I can think of no reason why 7.21.1/2 would explicitly
> >require valid pointers when the length parameter was 0 unless it was
> >intended that dereferencing could occur.
> 
> It caters to (unusual) architectures that require valid pointers in
> address registers even when the pointers are not dereferenced, e.g.,
> loading a pointer into an address register will trap if the pointer
> is invalid.

Yes. The C standard is written under the assumption that it's
impossible to work with invalid pointers in any way whatsoever. This
is why the _value_ of _any_ pointer object, wherever it may be stored,
becomes an indeterminate value when the pointed-to object's lifetime
ends, and part of why you can't do things like new=realloc(old,n);
if(new==old)...

Rich
  
Rich Felker Sept. 23, 2014, 3:06 p.m. UTC | #15
On Tue, Sep 23, 2014 at 03:58:27PM +0100, Richard Earnshaw wrote:
> On 23/09/14 14:57, Paul Eggert wrote:
> > Richard Earnshaw wrote:
> > 
> >> if src+1 can point outside of the address space of the program
> > 
> > As Andreas points out, src+1 does not point outside the address space of 
> > the program.  It is a valid pointer.
> > 
> 
> OK, so do we agree that for a valid pointer P, if P is *not*
> dereferencable, then P-1 must be?  Put another way, if P  and P-1 are in
> the same 'page' then it is safe to dereference them.

Nope.

struct foo {
	char a;
	int b[];
}

struct foo *bar = malloc(sizeof *bar);
int *p = (int *)((unsigned char *)bar + offsetof(struct foo, b));

Now neither p nor p-1 is dereferencable, but p is a valid pointer (to
a byte within the representation array of *bar, cast to int*).

Note that the reason I used a flexible array member was to get an
offset that's valid for an object of type int (so that the cast to
int* isn't an alignment violation) but where no object actually
exists.

Rich
  
Will Newton Oct. 15, 2014, 9:41 a.m. UTC | #16
On 17 September 2014 00:40, Will Newton <will.newton@linaro.org> wrote:
> For the string functions that take string lengths as an argument we
> should ensure that no data is read or written if a length of zero is
> specified. Pointers to PROT_NONE memory are used to ensure that any
> reads or writes will cause a fault.
>
> ChangeLog:
>
> 2014-09-16  Will Newton  <will.newton@linaro.org>
>
>         * string/test-memccpy.c (do_test_zero_length): New function.
>         (test_main): Call do_test_zero_length.
>         * string/test-memchr.c: Likewise.
>         * string/test-memcmp.c: Likewise.
>         * string/test-memcpy.c: Likewise.
>         * string/test-memmem.c: Likewise.
>         * string/test-memmove.c: Likewise.
>         * string/test-memrchr.c: Likewise.
>         * string/test-memset.c: Likewise.
>         * string/test-strncmp.c: Likewise.
>         * string/test-strncpy.c: Likewise.
>         * string/test-strnlen.c: Likewise.
>         * string/test-strncasecmp.c (do_test_zero_length): New function.
>         (test_locale): Call do_test_zero_length.
>         * string/test-strncat.c (do_test_zero_length): New function.
>         (main): Call do_test_zero_length.
> ---
>  string/test-memccpy.c     | 10 ++++++++++
>  string/test-memchr.c      |  9 +++++++++
>  string/test-memcmp.c      | 11 +++++++++++
>  string/test-memcpy.c      | 11 +++++++++++
>  string/test-memmem.c      | 10 ++++++++++
>  string/test-memmove.c     | 12 ++++++++++++
>  string/test-memrchr.c     | 10 ++++++++++
>  string/test-memset.c      | 11 +++++++++++
>  string/test-strncasecmp.c | 15 +++++++++++++++
>  string/test-strncat.c     | 24 ++++++++++++++++++++++++
>  string/test-strncmp.c     | 11 +++++++++++
>  string/test-strncpy.c     | 11 +++++++++++
>  string/test-strnlen.c     | 10 ++++++++++
>  13 files changed, 155 insertions(+)

Is there any consensus that this patch is useful or should I drop it?

Thanks,

> diff --git a/string/test-memccpy.c b/string/test-memccpy.c
> index 725d640..6192e5e 100644
> --- a/string/test-memccpy.c
> +++ b/string/test-memccpy.c
> @@ -230,6 +230,15 @@ do_random_tests (void)
>      }
>  }
>
> +static void
> +do_test_zero_length (void)
> +{
> +  /* Test for behaviour with zero length and pointers to PROT_NONE
> +     memory.  */
> +  FOR_EACH_IMPL (impl, 0)
> +    do_one_test (impl, buf2 + page_size, buf1 + BUF1PAGES * page_size, 0, 1, 0);
> +}
> +
>  int
>  test_main (void)
>  {
> @@ -263,6 +272,7 @@ test_main (void)
>      }
>
>    do_random_tests ();
> +  do_test_zero_length ();
>    return ret;
>  }
>
> diff --git a/string/test-memchr.c b/string/test-memchr.c
> index 0ba79b8..29904d1 100644
> --- a/string/test-memchr.c
> +++ b/string/test-memchr.c
> @@ -141,6 +141,14 @@ do_random_tests (void)
>      }
>  }
>
> +static void do_test_zero_length (void)
> +{
> +  /* Test for behaviour with zero length and pointer to PROT_NONE
> +     memory.  */
> +  FOR_EACH_IMPL (impl, 0)
> +    do_one_test (impl, (char *) (buf2 + page_size), 'a', 0, NULL);
> +}
> +
>  int
>  test_main (void)
>  {
> @@ -167,6 +175,7 @@ test_main (void)
>      }
>
>    do_random_tests ();
> +  do_test_zero_length ();
>    return ret;
>  }
>
> diff --git a/string/test-memcmp.c b/string/test-memcmp.c
> index 14090ed..80436c6 100644
> --- a/string/test-memcmp.c
> +++ b/string/test-memcmp.c
> @@ -471,6 +471,16 @@ check2 (void)
>      }
>  }
>
> +static void
> +do_test_zero_length (void)
> +{
> +  /* Test for behaviour with zero length and pointers to PROT_NONE
> +     memory.  */
> +  FOR_EACH_IMPL (impl, 0)
> +    do_one_test (impl, (CHAR *) (buf2 + page_size),
> +                (CHAR *) (buf1 + BUF1PAGES * page_size), 0, 0);
> +}
> +
>  int
>  test_main (void)
>  {
> @@ -519,6 +529,7 @@ test_main (void)
>      }
>
>    do_random_tests ();
> +  do_test_zero_length ();
>    return ret;
>  }
>  #include "../test-skeleton.c"
> diff --git a/string/test-memcpy.c b/string/test-memcpy.c
> index 136c985..4d2f65b 100644
> --- a/string/test-memcpy.c
> +++ b/string/test-memcpy.c
> @@ -206,6 +206,16 @@ do_random_tests (void)
>      }
>  }
>
> +static void
> +do_test_zero_length (void)
> +{
> +  /* Test for behaviour with zero length and pointers to PROT_NONE
> +     memory.  */
> +  FOR_EACH_IMPL (impl, 0)
> +    do_one_test (impl, (char *) (buf2 + page_size),
> +                (char *) (buf1 + BUF1PAGES * page_size), 0);
> +}
> +
>  int
>  test_main (void)
>  {
> @@ -247,6 +257,7 @@ test_main (void)
>    do_test (0, 0, getpagesize ());
>
>    do_random_tests ();
> +  do_test_zero_length ();
>    return ret;
>  }
>
> diff --git a/string/test-memmem.c b/string/test-memmem.c
> index caaa191..6199c22 100644
> --- a/string/test-memmem.c
> +++ b/string/test-memmem.c
> @@ -151,6 +151,15 @@ static const char *const strs[] =
>      "abc0", "aaaa0", "abcabc0"
>    };
>
> +static void
> +do_test_zero_length (void)
> +{
> +  /* Test for behaviour with zero length and pointer to PROT_NONE
> +     memory.  */
> +  FOR_EACH_IMPL (impl, 0)
> +    do_one_test (impl, (char *) (buf2 + page_size), 0,
> +                strs[0], strlen (strs[0]), NULL);
> +}
>
>  int
>  test_main (void)
> @@ -178,6 +187,7 @@ test_main (void)
>        }
>
>    do_random_tests ();
> +  do_test_zero_length ();
>    return ret;
>  }
>
> diff --git a/string/test-memmove.c b/string/test-memmove.c
> index 7e1c41c..25abb57 100644
> --- a/string/test-memmove.c
> +++ b/string/test-memmove.c
> @@ -244,6 +244,17 @@ do_random_tests (void)
>      }
>  }
>
> +static void
> +do_test_zero_length (void)
> +{
> +  /* Test for behaviour with zero length and pointers to PROT_NONE
> +     memory.  */
> +  FOR_EACH_IMPL (impl, 0)
> +    do_one_test (impl, (char *) (buf2 + page_size),
> +                (char *) (buf1 + BUF1PAGES * page_size),
> +                (char *) (buf1 + BUF1PAGES * page_size), 0);
> +}
> +
>  int
>  test_main (void)
>  {
> @@ -283,6 +294,7 @@ test_main (void)
>      }
>
>    do_random_tests ();
> +  do_test_zero_length ();
>    return ret;
>  }
>
> diff --git a/string/test-memrchr.c b/string/test-memrchr.c
> index efe4e9f..23c4a8b 100644
> --- a/string/test-memrchr.c
> +++ b/string/test-memrchr.c
> @@ -137,6 +137,15 @@ do_random_tests (void)
>      }
>  }
>
> +static void
> +do_test_zero_length (void)
> +{
> +  /* Test for behaviour with zero length and pointer to PROT_NONE
> +     memory.  */
> +  FOR_EACH_IMPL (impl, 0)
> +    do_one_test (impl, (char *) (buf2 + page_size), 'a', 0, NULL);
> +}
> +
>  int
>  test_main (void)
>  {
> @@ -163,6 +172,7 @@ test_main (void)
>      }
>
>    do_random_tests ();
> +  do_test_zero_length ();
>    return ret;
>  }
>
> diff --git a/string/test-memset.c b/string/test-memset.c
> index 2171b0d..b5432ce 100644
> --- a/string/test-memset.c
> +++ b/string/test-memset.c
> @@ -192,6 +192,15 @@ do_random_tests (void)
>  }
>  #endif
>
> +static void
> +do_test_zero_length (void)
> +{
> +  /* Test for behaviour with zero length and pointer to PROT_NONE
> +     memory.  */
> +  FOR_EACH_IMPL (impl, 0)
> +    do_one_test (impl, (char *) (buf2 + page_size), 0, 0);
> +}
> +
>  int
>  test_main (void)
>  {
> @@ -227,6 +236,8 @@ test_main (void)
>    do_random_tests ();
>  #endif
>
> +  do_test_zero_length ();
> +
>    return ret;
>  }
>
> diff --git a/string/test-strncasecmp.c b/string/test-strncasecmp.c
> index 6ad54e0..78d2dec 100644
> --- a/string/test-strncasecmp.c
> +++ b/string/test-strncasecmp.c
> @@ -53,9 +53,13 @@ simple_strncasecmp (const char *s1, const char *s2, size_t n)
>  static int
>  stupid_strncasecmp (const char *s1, const char *s2, size_t max)
>  {
> +  if (max == 0)
> +    return 0;
> +
>    size_t ns1 = strlen (s1) + 1;
>    size_t ns2 = strlen (s2) + 1;
>    size_t n = ns1 < ns2 ? ns1 : ns2;
> +
>    if (n > max)
>      n = max;
>    int ret = 0;
> @@ -258,6 +262,16 @@ bz14195 (void)
>  }
>
>  static void
> +do_test_zero_length (void)
> +{
> +  /* Test for behaviour with zero length and pointers to PROT_NONE
> +     memory.  */
> +  FOR_EACH_IMPL (impl, 0)
> +    do_one_test (impl, (char *) (buf2 + page_size),
> +                (char *) (buf1 + BUF1PAGES * page_size), 0, 0);
> +}
> +
> +static void
>  test_locale (const char *locale)
>  {
>    size_t i;
> @@ -270,6 +284,7 @@ test_locale (const char *locale)
>
>    bz12205 ();
>    bz14195 ();
> +  do_test_zero_length ();
>
>    printf ("%23s", locale);
>    FOR_EACH_IMPL (impl, 0)
> diff --git a/string/test-strncat.c b/string/test-strncat.c
> index 4915c59..4eef967 100644
> --- a/string/test-strncat.c
> +++ b/string/test-strncat.c
> @@ -31,6 +31,10 @@ char *
>  stupid_strncat (char *dst, const char *src, size_t n)
>  {
>    char *ret = dst;
> +
> +  if (n == 0)
> +    return ret;
> +
>    while (*dst++ != '\0');
>    --dst;
>    while (n--)
> @@ -232,6 +236,25 @@ do_random_tests (void)
>      }
>  }
>
> +static void
> +do_test_zero_length (void)
> +{
> +  char dst[1];
> +  char *src = (char *) (buf2 + page_size);
> +  dst[0] = '\0';
> +
> +  /* Test for behaviour with zero length and pointer to PROT_NONE
> +     memory.  */
> +  FOR_EACH_IMPL (impl, 0)
> +    if (CALL (impl, dst, src, 0) != dst)
> +      {
> +       error (0, 0, "Wrong result in function %s %p != %p", impl->name,
> +              CALL (impl, dst, src, 0), dst);
> +       ret = 1;
> +       return;
> +      }
> +}
> +
>  int
>  main (void)
>  {
> @@ -269,5 +292,6 @@ main (void)
>      }
>
>    do_random_tests ();
> +  do_test_zero_length ();
>    return ret;
>  }
> diff --git a/string/test-strncmp.c b/string/test-strncmp.c
> index f3b2c68..225bab5 100644
> --- a/string/test-strncmp.c
> +++ b/string/test-strncmp.c
> @@ -317,6 +317,16 @@ check2 (void)
>    free (s2);
>  }
>
> +static void
> +do_test_zero_length (void)
> +{
> +  /* Test for behaviour with zero length and pointers to PROT_NONE
> +     memory.  */
> +  FOR_EACH_IMPL (impl, 0)
> +    do_one_test (impl, (char *) (buf2 + page_size),
> +                (char *) (buf1 + BUF1PAGES * page_size), 0, 0);
> +}
> +
>  int
>  test_main (void)
>  {
> @@ -387,6 +397,7 @@ test_main (void)
>      }
>
>    do_random_tests ();
> +  do_test_zero_length ();
>    return ret;
>  }
>
> diff --git a/string/test-strncpy.c b/string/test-strncpy.c
> index 2326acc..455da43 100644
> --- a/string/test-strncpy.c
> +++ b/string/test-strncpy.c
> @@ -245,6 +245,16 @@ do_random_tests (void)
>      }
>  }
>
> +static void
> +do_test_zero_length (void)
> +{
> +  /* Test for behaviour with zero length and pointers to PROT_NONE
> +     memory.  */
> +  FOR_EACH_IMPL (impl, 0)
> +    do_one_test (impl, (char *) (buf2 + page_size),
> +                (char *) (buf1 + BUF1PAGES * page_size), 0, 0);
> +}
> +
>  int
>  test_main (void)
>  {
> @@ -278,6 +288,7 @@ test_main (void)
>      }
>
>    do_random_tests ();
> +  do_test_zero_length ();
>    return ret;
>  }
>
> diff --git a/string/test-strnlen.c b/string/test-strnlen.c
> index be9edd2..c8fd05a 100644
> --- a/string/test-strnlen.c
> +++ b/string/test-strnlen.c
> @@ -122,6 +122,15 @@ do_random_tests (void)
>      }
>  }
>
> +static void
> +do_test_zero_length (void)
> +{
> +  /* Test for behaviour with zero length and pointer to PROT_NONE
> +     memory.  */
> +  FOR_EACH_IMPL (impl, 0)
> +    do_one_test (impl, (char *) (buf2 + page_size), 0, 0);
> +}
> +
>  int
>  test_main (void)
>  {
> @@ -167,6 +176,7 @@ test_main (void)
>      }
>
>    do_random_tests ();
> +  do_test_zero_length ();
>    return ret;
>  }
>
> --
> 1.9.3
>
  
Florian Weimer Dec. 11, 2014, 11:36 a.m. UTC | #17
On 09/23/2014 02:57 PM, Richard Earnshaw wrote:
> On 23/09/14 13:41, Florian Weimer wrote:
>> On 09/22/2014 06:09 PM, Richard Earnshaw wrote:
>>>> These tests are not testing null pointers, they are testing that when
>>>> given a zero length the functions actually read/write zero bytes.
>>>> Whether the specification demands that behaviour is arguable but I
>>>> believe that it is the most sane behaviour.
>>
>>> Valid pointers is more than just non-NULL.  In particular, it implies
>>> that is safe to dereference the addressed byte in a source operand even
>>> when the length parameter is zero.
>>
>> Valid pointers can also point one element past the end of an array of
>> objects.
>
> I don't think such a pointer forms a valid argument for a library
> function though.  See my previous reply to Paul.

They are a fairly common occurrence with the [first, last) iterator 
ranges in C++.  It's common to compute a pointer/length pair {first, 
last - first} and pass that to C functions, including C library functions.

This pattern is already incorrect in important corner cases (e.g., 
iterators derived from empty vectors), but do we really have to make the 
situation even worse?
  
Florian Weimer Dec. 12, 2014, 9:51 a.m. UTC | #18
On 10/15/2014 11:41 AM, Will Newton wrote:
> On 17 September 2014 00:40, Will Newton <will.newton@linaro.org> wrote:
>> For the string functions that take string lengths as an argument we
>> should ensure that no data is read or written if a length of zero is
>> specified. Pointers to PROT_NONE memory are used to ensure that any
>> reads or writes will cause a fault.

> Is there any consensus that this patch is useful or should I drop it?

I, for one, think the patch is useful.
  

Patch

diff --git a/string/test-memccpy.c b/string/test-memccpy.c
index 725d640..6192e5e 100644
--- a/string/test-memccpy.c
+++ b/string/test-memccpy.c
@@ -230,6 +230,15 @@  do_random_tests (void)
     }
 }
 
+static void
+do_test_zero_length (void)
+{
+  /* Test for behaviour with zero length and pointers to PROT_NONE
+     memory.  */
+  FOR_EACH_IMPL (impl, 0)
+    do_one_test (impl, buf2 + page_size, buf1 + BUF1PAGES * page_size, 0, 1, 0);
+}
+
 int
 test_main (void)
 {
@@ -263,6 +272,7 @@  test_main (void)
     }
 
   do_random_tests ();
+  do_test_zero_length ();
   return ret;
 }
 
diff --git a/string/test-memchr.c b/string/test-memchr.c
index 0ba79b8..29904d1 100644
--- a/string/test-memchr.c
+++ b/string/test-memchr.c
@@ -141,6 +141,14 @@  do_random_tests (void)
     }
 }
 
+static void do_test_zero_length (void)
+{
+  /* Test for behaviour with zero length and pointer to PROT_NONE
+     memory.  */
+  FOR_EACH_IMPL (impl, 0)
+    do_one_test (impl, (char *) (buf2 + page_size), 'a', 0, NULL);
+}
+
 int
 test_main (void)
 {
@@ -167,6 +175,7 @@  test_main (void)
     }
 
   do_random_tests ();
+  do_test_zero_length ();
   return ret;
 }
 
diff --git a/string/test-memcmp.c b/string/test-memcmp.c
index 14090ed..80436c6 100644
--- a/string/test-memcmp.c
+++ b/string/test-memcmp.c
@@ -471,6 +471,16 @@  check2 (void)
     }
 }
 
+static void
+do_test_zero_length (void)
+{
+  /* Test for behaviour with zero length and pointers to PROT_NONE
+     memory.  */
+  FOR_EACH_IMPL (impl, 0)
+    do_one_test (impl, (CHAR *) (buf2 + page_size),
+		 (CHAR *) (buf1 + BUF1PAGES * page_size), 0, 0);
+}
+
 int
 test_main (void)
 {
@@ -519,6 +529,7 @@  test_main (void)
     }
 
   do_random_tests ();
+  do_test_zero_length ();
   return ret;
 }
 #include "../test-skeleton.c"
diff --git a/string/test-memcpy.c b/string/test-memcpy.c
index 136c985..4d2f65b 100644
--- a/string/test-memcpy.c
+++ b/string/test-memcpy.c
@@ -206,6 +206,16 @@  do_random_tests (void)
     }
 }
 
+static void
+do_test_zero_length (void)
+{
+  /* Test for behaviour with zero length and pointers to PROT_NONE
+     memory.  */
+  FOR_EACH_IMPL (impl, 0)
+    do_one_test (impl, (char *) (buf2 + page_size),
+		 (char *) (buf1 + BUF1PAGES * page_size), 0);
+}
+
 int
 test_main (void)
 {
@@ -247,6 +257,7 @@  test_main (void)
   do_test (0, 0, getpagesize ());
 
   do_random_tests ();
+  do_test_zero_length ();
   return ret;
 }
 
diff --git a/string/test-memmem.c b/string/test-memmem.c
index caaa191..6199c22 100644
--- a/string/test-memmem.c
+++ b/string/test-memmem.c
@@ -151,6 +151,15 @@  static const char *const strs[] =
     "abc0", "aaaa0", "abcabc0"
   };
 
+static void
+do_test_zero_length (void)
+{
+  /* Test for behaviour with zero length and pointer to PROT_NONE
+     memory.  */
+  FOR_EACH_IMPL (impl, 0)
+    do_one_test (impl, (char *) (buf2 + page_size), 0,
+		 strs[0], strlen (strs[0]), NULL);
+}
 
 int
 test_main (void)
@@ -178,6 +187,7 @@  test_main (void)
       }
 
   do_random_tests ();
+  do_test_zero_length ();
   return ret;
 }
 
diff --git a/string/test-memmove.c b/string/test-memmove.c
index 7e1c41c..25abb57 100644
--- a/string/test-memmove.c
+++ b/string/test-memmove.c
@@ -244,6 +244,17 @@  do_random_tests (void)
     }
 }
 
+static void
+do_test_zero_length (void)
+{
+  /* Test for behaviour with zero length and pointers to PROT_NONE
+     memory.  */
+  FOR_EACH_IMPL (impl, 0)
+    do_one_test (impl, (char *) (buf2 + page_size),
+		 (char *) (buf1 + BUF1PAGES * page_size),
+		 (char *) (buf1 + BUF1PAGES * page_size), 0);
+}
+
 int
 test_main (void)
 {
@@ -283,6 +294,7 @@  test_main (void)
     }
 
   do_random_tests ();
+  do_test_zero_length ();
   return ret;
 }
 
diff --git a/string/test-memrchr.c b/string/test-memrchr.c
index efe4e9f..23c4a8b 100644
--- a/string/test-memrchr.c
+++ b/string/test-memrchr.c
@@ -137,6 +137,15 @@  do_random_tests (void)
     }
 }
 
+static void
+do_test_zero_length (void)
+{
+  /* Test for behaviour with zero length and pointer to PROT_NONE
+     memory.  */
+  FOR_EACH_IMPL (impl, 0)
+    do_one_test (impl, (char *) (buf2 + page_size), 'a', 0, NULL);
+}
+
 int
 test_main (void)
 {
@@ -163,6 +172,7 @@  test_main (void)
     }
 
   do_random_tests ();
+  do_test_zero_length ();
   return ret;
 }
 
diff --git a/string/test-memset.c b/string/test-memset.c
index 2171b0d..b5432ce 100644
--- a/string/test-memset.c
+++ b/string/test-memset.c
@@ -192,6 +192,15 @@  do_random_tests (void)
 }
 #endif
 
+static void
+do_test_zero_length (void)
+{
+  /* Test for behaviour with zero length and pointer to PROT_NONE
+     memory.  */
+  FOR_EACH_IMPL (impl, 0)
+    do_one_test (impl, (char *) (buf2 + page_size), 0, 0);
+}
+
 int
 test_main (void)
 {
@@ -227,6 +236,8 @@  test_main (void)
   do_random_tests ();
 #endif
 
+  do_test_zero_length ();
+
   return ret;
 }
 
diff --git a/string/test-strncasecmp.c b/string/test-strncasecmp.c
index 6ad54e0..78d2dec 100644
--- a/string/test-strncasecmp.c
+++ b/string/test-strncasecmp.c
@@ -53,9 +53,13 @@  simple_strncasecmp (const char *s1, const char *s2, size_t n)
 static int
 stupid_strncasecmp (const char *s1, const char *s2, size_t max)
 {
+  if (max == 0)
+    return 0;
+
   size_t ns1 = strlen (s1) + 1;
   size_t ns2 = strlen (s2) + 1;
   size_t n = ns1 < ns2 ? ns1 : ns2;
+
   if (n > max)
     n = max;
   int ret = 0;
@@ -258,6 +262,16 @@  bz14195 (void)
 }
 
 static void
+do_test_zero_length (void)
+{
+  /* Test for behaviour with zero length and pointers to PROT_NONE
+     memory.  */
+  FOR_EACH_IMPL (impl, 0)
+    do_one_test (impl, (char *) (buf2 + page_size),
+		 (char *) (buf1 + BUF1PAGES * page_size), 0, 0);
+}
+
+static void
 test_locale (const char *locale)
 {
   size_t i;
@@ -270,6 +284,7 @@  test_locale (const char *locale)
 
   bz12205 ();
   bz14195 ();
+  do_test_zero_length ();
 
   printf ("%23s", locale);
   FOR_EACH_IMPL (impl, 0)
diff --git a/string/test-strncat.c b/string/test-strncat.c
index 4915c59..4eef967 100644
--- a/string/test-strncat.c
+++ b/string/test-strncat.c
@@ -31,6 +31,10 @@  char *
 stupid_strncat (char *dst, const char *src, size_t n)
 {
   char *ret = dst;
+
+  if (n == 0)
+    return ret;
+
   while (*dst++ != '\0');
   --dst;
   while (n--)
@@ -232,6 +236,25 @@  do_random_tests (void)
     }
 }
 
+static void
+do_test_zero_length (void)
+{
+  char dst[1];
+  char *src = (char *) (buf2 + page_size);
+  dst[0] = '\0';
+
+  /* Test for behaviour with zero length and pointer to PROT_NONE
+     memory.  */
+  FOR_EACH_IMPL (impl, 0)
+    if (CALL (impl, dst, src, 0) != dst)
+      {
+	error (0, 0, "Wrong result in function %s %p != %p", impl->name,
+	       CALL (impl, dst, src, 0), dst);
+	ret = 1;
+	return;
+      }
+}
+
 int
 main (void)
 {
@@ -269,5 +292,6 @@  main (void)
     }
 
   do_random_tests ();
+  do_test_zero_length ();
   return ret;
 }
diff --git a/string/test-strncmp.c b/string/test-strncmp.c
index f3b2c68..225bab5 100644
--- a/string/test-strncmp.c
+++ b/string/test-strncmp.c
@@ -317,6 +317,16 @@  check2 (void)
   free (s2);
 }
 
+static void
+do_test_zero_length (void)
+{
+  /* Test for behaviour with zero length and pointers to PROT_NONE
+     memory.  */
+  FOR_EACH_IMPL (impl, 0)
+    do_one_test (impl, (char *) (buf2 + page_size),
+		 (char *) (buf1 + BUF1PAGES * page_size), 0, 0);
+}
+
 int
 test_main (void)
 {
@@ -387,6 +397,7 @@  test_main (void)
     }
 
   do_random_tests ();
+  do_test_zero_length ();
   return ret;
 }
 
diff --git a/string/test-strncpy.c b/string/test-strncpy.c
index 2326acc..455da43 100644
--- a/string/test-strncpy.c
+++ b/string/test-strncpy.c
@@ -245,6 +245,16 @@  do_random_tests (void)
     }
 }
 
+static void
+do_test_zero_length (void)
+{
+  /* Test for behaviour with zero length and pointers to PROT_NONE
+     memory.  */
+  FOR_EACH_IMPL (impl, 0)
+    do_one_test (impl, (char *) (buf2 + page_size),
+		 (char *) (buf1 + BUF1PAGES * page_size), 0, 0);
+}
+
 int
 test_main (void)
 {
@@ -278,6 +288,7 @@  test_main (void)
     }
 
   do_random_tests ();
+  do_test_zero_length ();
   return ret;
 }
 
diff --git a/string/test-strnlen.c b/string/test-strnlen.c
index be9edd2..c8fd05a 100644
--- a/string/test-strnlen.c
+++ b/string/test-strnlen.c
@@ -122,6 +122,15 @@  do_random_tests (void)
     }
 }
 
+static void
+do_test_zero_length (void)
+{
+  /* Test for behaviour with zero length and pointer to PROT_NONE
+     memory.  */
+  FOR_EACH_IMPL (impl, 0)
+    do_one_test (impl, (char *) (buf2 + page_size), 0, 0);
+}
+
 int
 test_main (void)
 {
@@ -167,6 +176,7 @@  test_main (void)
     }
 
   do_random_tests ();
+  do_test_zero_length ();
   return ret;
 }