Re: [PATCH] vsprintf: sanely handle NULL passed to %pe

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, Feb 20, 2020 at 1:57 PM Petr Mladek <pmladek@xxxxxxxx> wrote:
>
> On Wed 2020-02-19 16:40:08, Rasmus Villemoes wrote:
> > On 19/02/2020 15.45, Petr Mladek wrote:
> > > On Wed 2020-02-19 14:56:32, Rasmus Villemoes wrote:
> > >> On 19/02/2020 14.48, Petr Mladek wrote:
> > >>> On Wed 2020-02-19 12:53:22, Rasmus Villemoes wrote:
> > >>>> --- a/lib/vsprintf.c
> > >>>> +++ b/lib/vsprintf.c
> > >>> The test should go into null_pointer() instead of errptr().
> > >>
> > >> Eh, no, the behaviour of %pe is tested by errptr(). I'll keep it that
> > >> way. But I should add a #else section that tests how %pe behaves without
> > >> CONFIG_SYMBOLIC_ERRNAME - though that's orthogonal to this patch.
> > >
> > > OK, we should agree on some structure first.
> > >
> > > We already have two top level functions that test how a particular
> > > pointer is printed using different pointer modifiers:
> > >
> > >     null_pointer();     -> NULL with %p, %pX, %pE
> > >     invalid_pointer();  -> random pointer with %p, %pX, %pE
> > >
> > > Following this logic, errptr() should test how a pointer from IS_ERR() range
> > > is printed using different pointer formats.
> >
> > Oh please. I wrote test_printf.c originally and structured it with one
> > helper for each %p<whatever>. How are your additions null_pointer and
> > invalid_pointer good examples for what the existing style is?
>
> I see, I was the one who broke the style. Please, find below a patch
> that tries to fix it. If you agree with the approach then I could
> split it into smaller steps.
>
> Also it would make sense to add checks for NULL and ERR pointer
> into each existing %p modifier check. It will make sure that
> check_pointer() is called in all handlers.
>
>
> > So yeah, I'm going to continue testing the behaviour of %pe in errptr, TYVM.
>
> OK.
>
> > >>>> BTW., your original patch for %p lacks corresponding update of
> > >>>> test_vsprintf.c. Please add appropriate test cases.
> > >>>
> > >>> diff --git a/lib/test_printf.c b/lib/test_printf.c
> > >>> index 2d9f520d2f27..1726a678bccd 100644
> > >>> --- a/lib/test_printf.c
> > >>> +++ b/lib/test_printf.c
> > >>> @@ -333,7 +333,7 @@ test_hashed(const char *fmt, const void *p)
> > >>>  static void __init
> > >>>  null_pointer(void)
> > >>>  {
> > >>> - test_hashed("%p", NULL);
> > >>> + test(ZEROS "00000000", "%p", NULL);
> > >>
> > >> No, it most certainly also needs to check a few "%p", ERR_PTR(-4) cases
> > >> (where one of course has to use explicit integers and not E* constants).
> > >
> > > Yes, it would be great to add checks for %p, %px for IS_ERR() range.
> > > But it is different story. The above change is for the original patch
> > > and it was about NULL pointer handling.
> >
> > Wrong. The original patch (i.e. Ilya's) had subject "vsprintf: don't
> > obfuscate NULL and error pointers" and did
> >
> > +     if (IS_ERR_OR_NULL(ptr))
> >
> > so the tests that should be part of that patch very much need to cover
> > both NULL and ERR_PTRs passed to plain %p.
>
> Grr, I see. I was too fast yesterday. OK, I suggest to fix the
> structure of the tests first. All these patches are for 5.7
> anyway.

My patch fixes a regression introduced by 3e5903eb9cff ("vsprintf:
Prevent crash when dereferencing invalid pointers" in 5.2, which
made debugging based on existing pr_debugs (used extensively in some
subsystems) very annoying.

I would like to see it in 5.6, so that it is backported to 5.4 and 5.5.

>
>
> Here is the proposed clean up:
>
> From 855909f2a1945d3a5bf490ddf4f2cca775ef967b Mon Sep 17 00:00:00 2001
> From: Petr Mladek <pmladek@xxxxxxxx>
> Date: Thu, 20 Feb 2020 12:53:43 +0100
> Subject: [PATCH] lib/test_printf: Clean up basic pointer testing
>
> The pointer testing has been originally split by the %p modifiers,
> for example, the function dentry() tested %pd and %pD handling.
>
> There were recently added tests that do not really fit into
> the existing structure, namely:
>
>   + hashed pointers tested by a maze of functions
>   + null and invalid pointer handling with various modifiers
>
> The hash pointer test is really special because the hash depends
> on a random key that is generated during boot. Though, it is
> still possible to check some aspects:
>
>   + output string length
>   + hash differs from the original pointer value
>   + top half bites are zeroed on 64-bit systems
>
> Let's put all these checks into test_hashed() function that has
> the same behavior as the test() functions for well-defined output.
> It increments the number of tests and eventual failures. It prints
> warnings/errors when some aspects of the output are not as expected.
>
> Most of these checks were there even before. The only addition is
> the check whether hash differs from the original pointer value.
> There is a small chance of a false error. It might be reduced
> by checking more pointers but let's keep it simple for now.
>
> The existing null_pointer() and invalid_pointer() checks are
> newly split per-format modifier. And there is also fixed
> difference between invalid pointer in the IS_ERR() range
> and invalid pointer that looks like a valid one.
>
> The invalid pointer Oxdeaddead00000000 should work on most
> architectures. But I am not able to check it everywhere.
> So there is a small chance of a false error. It might get
> fixed when anyone reports a problem.
>
> Signed-off-by: Petr Mladek <pmladek@xxxxxxxx>
> ---
>  lib/test_printf.c | 162 ++++++++++++++++++++----------------------------------
>  1 file changed, 59 insertions(+), 103 deletions(-)
>
> diff --git a/lib/test_printf.c b/lib/test_printf.c
> index 2d9f520d2f27..4e89b508def6 100644
> --- a/lib/test_printf.c
> +++ b/lib/test_printf.c
> @@ -206,146 +206,101 @@ test_string(void)
>  }
>
>  #define PLAIN_BUF_SIZE 64      /* leave some space so we don't oops */
> +#define PTR_ERROR ERR_PTR(-EFAULT)
> +#define PTR_VAL_ERROR "fffffff2"
>
>  #if BITS_PER_LONG == 64
>
>  #define PTR_WIDTH 16
>  #define PTR ((void *)0xffff0123456789abUL)
>  #define PTR_STR "ffff0123456789ab"
> +#define PTR_INVALID ((void *)0xdeaddead000000ab)
> +#define PTR_VAL_INVALID "deaddead000000ab"
>  #define PTR_VAL_NO_CRNG "(____ptrval____)"
> +#define ONES "ffffffff"                /* hex 32 one bits */
>  #define ZEROS "00000000"       /* hex 32 zero bits */
>
> -static int __init
> -plain_format(void)
> -{
> -       char buf[PLAIN_BUF_SIZE];
> -       int nchars;
> -
> -       nchars = snprintf(buf, PLAIN_BUF_SIZE, "%p", PTR);
> -
> -       if (nchars != PTR_WIDTH)
> -               return -1;
> -
> -       if (strncmp(buf, PTR_VAL_NO_CRNG, PTR_WIDTH) == 0) {
> -               pr_warn("crng possibly not yet initialized. plain 'p' buffer contains \"%s\"",
> -                       PTR_VAL_NO_CRNG);
> -               return 0;
> -       }
> -
> -       if (strncmp(buf, ZEROS, strlen(ZEROS)) != 0)
> -               return -1;
> -
> -       return 0;
> -}
> -
>  #else
>
>  #define PTR_WIDTH 8
>  #define PTR ((void *)0x456789ab)
>  #define PTR_STR "456789ab"
> +#define PTR_INVALID ((void *)0x000000ab)
> +#define PTR_VAL_INVALID "000000ab"
>  #define PTR_VAL_NO_CRNG "(ptrval)"
> +#define ONES ""
>  #define ZEROS ""
>
> -static int __init
> -plain_format(void)
> -{
> -       /* Format is implicitly tested for 32 bit machines by plain_hash() */
> -       return 0;
> -}
> -
>  #endif /* BITS_PER_LONG == 64 */
>
> -static int __init
> -plain_hash_to_buffer(const void *p, char *buf, size_t len)
> +static void __init
> +test_hashed(const char *fmt, const void *p)
>  {
> +       char pointer[PLAIN_BUF_SIZE];
> +       char hash[PLAIN_BUF_SIZE];
>         int nchars;
>
> -       nchars = snprintf(buf, len, "%p", p);
> -
> -       if (nchars != PTR_WIDTH)
> -               return -1;
> +       total_tests++;
>
> -       if (strncmp(buf, PTR_VAL_NO_CRNG, PTR_WIDTH) == 0) {
> -               pr_warn("crng possibly not yet initialized. plain 'p' buffer contains \"%s\"",
> -                       PTR_VAL_NO_CRNG);
> -               return 0;
> +       nchars = snprintf(pointer, sizeof(pointer), "%px", p);
> +       if (nchars != PTR_WIDTH) {
> +               pr_err("error in test suite: vsprintf(\"%%px\", p) returned number of characters %d, expected %d\n",
> +                      nchars, PTR_WIDTH);
> +               goto err;
>         }
>
> -       return 0;
> -}
> -
> -static int __init
> -plain_hash(void)
> -{
> -       char buf[PLAIN_BUF_SIZE];
> -       int ret;
> -
> -       ret = plain_hash_to_buffer(PTR, buf, PLAIN_BUF_SIZE);
> -       if (ret)
> -               return ret;
> -
> -       if (strncmp(buf, PTR_STR, PTR_WIDTH) == 0)
> -               return -1;
> -
> -       return 0;
> -}
> -
> -/*
> - * We can't use test() to test %p because we don't know what output to expect
> - * after an address is hashed.
> - */
> -static void __init
> -plain(void)
> -{
> -       int err;
> -
> -       err = plain_hash();
> -       if (err) {
> -               pr_warn("plain 'p' does not appear to be hashed\n");
> -               failed_tests++;
> -               return;
> +       nchars = snprintf(hash, sizeof(hash), fmt, p);
> +       if (nchars != PTR_WIDTH) {
> +               pr_warn("vsprintf(\"%s\", p) returned number of characters %d, expected %d\n",
> +                       fmt, nchars, PTR_WIDTH);
> +               goto err;
>         }
>
> -       err = plain_format();
> -       if (err) {
> -               pr_warn("hashing plain 'p' has unexpected format\n");
> -               failed_tests++;
> +       if (strncmp(hash, PTR_VAL_NO_CRNG, PTR_WIDTH) == 0) {
> +               pr_warn_once("crng possibly not yet initialized. vsprinf(\"%s\", p) printed \"%s\"",
> +                            fmt, hash);
> +               total_tests--;
> +               return;
>         }
> -}
> -
> -static void __init
> -test_hashed(const char *fmt, const void *p)
> -{
> -       char buf[PLAIN_BUF_SIZE];
> -       int ret;
>
>         /*
> -        * No need to increase failed test counter since this is assumed
> -        * to be called after plain().
> +        * There is a small chance of a false negative on 32-bit systems
> +        * when the hash is the same as the pointer value.
>          */
> -       ret = plain_hash_to_buffer(p, buf, PLAIN_BUF_SIZE);
> -       if (ret)
> -               return;
> +       if (strncmp(hash, pointer, PTR_WIDTH) == 0) {
> +               pr_warn("vsprintf(\"%s\", p) returned %s, expected hashed pointer\n",
> +                       fmt, hash);
> +               goto err;
> +       }
> +
> +#if BITS_PER_LONG == 64
> +       if (strncmp(hash, ZEROS, PTR_WIDTH / 2) != 0) {
> +               pr_warn("vsprintf(\"%s\", p) returned %s, expected %s in the top half bits\n",
> +                       fmt, hash, ZEROS);
> +               goto err;
> +       }
> +#endif
> +       return;
>
> -       test(buf, fmt, p);
> +err:
> +       failed_tests++;
>  }
>
>  static void __init
> -null_pointer(void)
> +plain_pointer(void)
>  {
>         test_hashed("%p", NULL);
> -       test(ZEROS "00000000", "%px", NULL);
> -       test("(null)", "%pE", NULL);
> +       test_hashed("%p", PTR_ERROR);
> +       test_hashed("%p", PTR_INVALID);
>  }
>
> -#define PTR_INVALID ((void *)0x000000ab)
>
>  static void __init
> -invalid_pointer(void)
> +real_pointer(void)
>  {
> -       test_hashed("%p", PTR_INVALID);
> -       test(ZEROS "000000ab", "%px", PTR_INVALID);
> -       test("(efault)", "%pE", PTR_INVALID);
> +       test(ZEROS "00000000", "%px", NULL);
> +       test(ONES PTR_VAL_ERROR, "%px", PTR_ERROR);
> +       test(PTR_VAL_INVALID, "%px", PTR_INVALID);
>  }
>
>  static void __init
> @@ -372,6 +327,8 @@ addr(void)
>  static void __init
>  escaped_str(void)
>  {
> +       test("(null)", "%pE", NULL);
> +       test("(efault)", "%pE", PTR_ERROR);
>  }
>
>  static void __init
> @@ -458,9 +415,9 @@ dentry(void)
>         test("foo", "%pd2", &test_dentry[0]);
>
>         test("(null)", "%pd", NULL);
> -       test("(efault)", "%pd", PTR_INVALID);
> +       test("(efault)", "%pd", PTR_ERROR);
>         test("(null)", "%pD", NULL);
> -       test("(efault)", "%pD", PTR_INVALID);
> +       test("(efault)", "%pD", PTR_ERROR);
>
>         test("romeo", "%pd", &test_dentry[3]);
>         test("alfa/romeo", "%pd2", &test_dentry[3]);
> @@ -647,9 +604,8 @@ errptr(void)
>  static void __init
>  test_pointer(void)
>  {
> -       plain();
> -       null_pointer();
> -       invalid_pointer();
> +       plain_pointer();
> +       real_pointer();
>         symbol_ptr();
>         kernel_ptr();
>         struct_resource();

Please note that I sent v2 of my patch ("[PATCH v2] vsprintf: don't
obfuscate NULL and error pointers"), fixing null_pointer() and adding
error_pointer() test cases, which conflicts with this restructure.

Thanks,

                Ilya



[Index of Archives]     [Kernel Newbies]     [Security]     [Netfilter]     [Bugtraq]     [Linux FS]     [Yosemite Forum]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Device Mapper]     [Linux Resources]

  Powered by Linux