diff mbox

[v2,39/52] xen: check parameter validity when parsing command line

Message ID 20170814070849.20986-40-jgross@suse.com (mailing list archive)
State New, archived
Headers show

Commit Message

Jürgen Groß Aug. 14, 2017, 7:08 a.m. UTC
Where possible check validity of parameters in _cmdline_parse() and
issue a warning message in case of an error detected.

Cc: Andrew Cooper <andrew.cooper3@citrix.com>
Cc: George Dunlap <George.Dunlap@eu.citrix.com>
Cc: Ian Jackson <ian.jackson@eu.citrix.com>
Cc: Jan Beulich <jbeulich@suse.com>
Cc: Konrad Rzeszutek Wilk <konrad.wilk@oracle.com>
Cc: Stefano Stabellini <sstabellini@kernel.org>
Cc: Tim Deegan <tim@xen.org>
Cc: Wei Liu <wei.liu2@citrix.com>
Signed-off-by: Juergen Gross <jgross@suse.com>
Reviewed-by: Wei Liu <wei.liu2@citrix.com>
---
V2:
- replaced literal 8 by BITS_PER_BYTE (Wei Liu)
- added test for empty string to parse_bool()
---
 xen/common/kernel.c | 47 ++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 36 insertions(+), 11 deletions(-)

Comments

Jan Beulich Aug. 14, 2017, 12:46 p.m. UTC | #1
>>> On 14.08.17 at 09:08, <jgross@suse.com> wrote:
> --- a/xen/common/kernel.c
> +++ b/xen/common/kernel.c
> @@ -23,9 +23,11 @@ enum system_state system_state = SYS_STATE_early_boot;
>  xen_commandline_t saved_cmdline;
>  static const char __initconst opt_builtin_cmdline[] = CONFIG_CMDLINE;
>  
> -static void __init assign_integer_param(
> +static int __init assign_integer_param(
>      const struct kernel_param *param, uint64_t val)
>  {
> +    unsigned int bits = param->len * BITS_PER_BYTE;
> +
>      switch ( param->len )
>      {
>      case sizeof(uint8_t):
> @@ -43,14 +45,17 @@ static void __init assign_integer_param(
>      default:
>          BUG();
>      }
> +
> +    return ( (val & (~0ULL << bits)) && ~(val | (~0ULL >> (65 - bits))) ) ?

The left part has undefined behavior when param->len == 8
(and on x86 I'd expect it to produce just "val"). The right part
I guess is meant to be a sign check, but that's rather obscure.
As iirc it is signed-to-unsigned conversion which has uniformly
defined behavior it may end up being better for the parameter
to be of signed type and to allow values in the range
[<type>_MIN,U<type>_MAX]. Anything more precise would
require signedness to be communicated from the *_param()
users.

Also - stray blanks inside the outermost parentheses.

And finally, wouldn't it be better to check for overflow _before_
assigning to *param->var?

> @@ -97,8 +102,9 @@ static void __init _cmdline_parse(const char *cmdline)
>                       !strncmp(param->name, opt, q + 1 - opt) )
>                  {
>                      optval[-1] = '=';
> -                    ((void (*)(const char *))param->var)(q);
> +                    rc = ((int (*)(const char *))param->var)(q);

Neither here nor in the earlier "let custom parameter parsing
routines return errno" nor in the overview you mention why this
is safe - it is not a given that caller and callee disagreeing on
return type is going to work. Just think of functions returning
aggregates or (on ix86) ones returning floating point values in
st(0).

>                      optval[-1] = '\0';
> +                    break;

Why? Applies to further break-s you add: At least in the past we
had command line options with two handlers, where each of them
needed to be invoked. I don't think we should make such impossible
even if right now there aren't any such examples. Yet if you really
mean to, then the behavioral change needs to be called out in the
description.

> @@ -106,24 +112,34 @@ static void __init _cmdline_parse(const char *cmdline)
>              switch ( param->type )
>              {
>              case OPT_STR:
> +                rc = 0;
>                  strlcpy(param->var, optval, param->len);
>                  break;
>              case OPT_UINT:
> -                assign_integer_param(
> +                rc = assign_integer_param(
>                      param,
> -                    simple_strtoll(optval, NULL, 0));
> +                    simple_strtoll(optval, &s, 0));
> +                if ( *s )
> +                    rc = -EINVAL;
>                  break;
>              case OPT_BOOL:
> -                if ( !parse_bool(optval) )
> +                rc = parse_bool(optval);
> +                if ( rc == -1 )

Maybe "rc < 0"?

> @@ -131,13 +147,21 @@ static void __init _cmdline_parse(const char *cmdline)
>                      safe_strcpy(opt, "no");
>                      optval = opt;
>                  }
> -                ((void (*)(const char *))param->var)(optval);
> +                rc = ((int (*)(const char *))param->var)(optval);
>                  break;
>              default:
>                  BUG();
>                  break;
>              }
> +
> +            break;
>          }
> +
> +        if ( rc )
> +            printk("parameter \"%s\" has invalid value \"%s\"!\n", optkey,
> +                   optval);

With the changes made to optval in OPT_CUSTOM handling this
may end up being confusing.

Jan
Jürgen Groß Aug. 14, 2017, 1:31 p.m. UTC | #2
On 14/08/17 14:46, Jan Beulich wrote:
>>>> On 14.08.17 at 09:08, <jgross@suse.com> wrote:
>> --- a/xen/common/kernel.c
>> +++ b/xen/common/kernel.c
>> @@ -23,9 +23,11 @@ enum system_state system_state = SYS_STATE_early_boot;
>>  xen_commandline_t saved_cmdline;
>>  static const char __initconst opt_builtin_cmdline[] = CONFIG_CMDLINE;
>>  
>> -static void __init assign_integer_param(
>> +static int __init assign_integer_param(
>>      const struct kernel_param *param, uint64_t val)
>>  {
>> +    unsigned int bits = param->len * BITS_PER_BYTE;
>> +
>>      switch ( param->len )
>>      {
>>      case sizeof(uint8_t):
>> @@ -43,14 +45,17 @@ static void __init assign_integer_param(
>>      default:
>>          BUG();
>>      }
>> +
>> +    return ( (val & (~0ULL << bits)) && ~(val | (~0ULL >> (65 - bits))) ) ?
> 
> The left part has undefined behavior when param->len == 8
> (and on x86 I'd expect it to produce just "val"). The right part
> I guess is meant to be a sign check, but that's rather obscure.

Hmm, okay.

> As iirc it is signed-to-unsigned conversion which has uniformly
> defined behavior it may end up being better for the parameter
> to be of signed type and to allow values in the range
> [<type>_MIN,U<type>_MAX]. Anything more precise would
> require signedness to be communicated from the *_param()
> users.

Okay, I'll have a try.

> Also - stray blanks inside the outermost parentheses.
> 
> And finally, wouldn't it be better to check for overflow _before_
> assigning to *param->var?

I didn't want to change existing behavior. OTOH thinking twice you are
right. Better using the default value than an unexpected small one.

> 
>> @@ -97,8 +102,9 @@ static void __init _cmdline_parse(const char *cmdline)
>>                       !strncmp(param->name, opt, q + 1 - opt) )
>>                  {
>>                      optval[-1] = '=';
>> -                    ((void (*)(const char *))param->var)(q);
>> +                    rc = ((int (*)(const char *))param->var)(q);
> 
> Neither here nor in the earlier "let custom parameter parsing
> routines return errno" nor in the overview you mention why this
> is safe - it is not a given that caller and callee disagreeing on
> return type is going to work. Just think of functions returning
> aggregates or (on ix86) ones returning floating point values in
> st(0).

I thought about using a union in struct kernel_param and removing
above type cast. This would require modifying the initialization of
the kernel_param struct via the *_param() macros, though.

The other possibility would be using __builtin_types_compatible_p()
to check the function to be of proper type.

What would you like best?

> 
>>                      optval[-1] = '\0';
>> +                    break;
> 
> Why? Applies to further break-s you add: At least in the past we
> had command line options with two handlers, where each of them
> needed to be invoked. I don't think we should make such impossible
> even if right now there aren't any such examples. Yet if you really
> mean to, then the behavioral change needs to be called out in the
> description.

I wasn't aware of such a usage.

I'm fine for both alternatives. As you seem to prefer to keep support
for multiple handlers I'll modify the patch to allow that.

>> @@ -106,24 +112,34 @@ static void __init _cmdline_parse(const char *cmdline)
>>              switch ( param->type )
>>              {
>>              case OPT_STR:
>> +                rc = 0;
>>                  strlcpy(param->var, optval, param->len);
>>                  break;
>>              case OPT_UINT:
>> -                assign_integer_param(
>> +                rc = assign_integer_param(
>>                      param,
>> -                    simple_strtoll(optval, NULL, 0));
>> +                    simple_strtoll(optval, &s, 0));
>> +                if ( *s )
>> +                    rc = -EINVAL;
>>                  break;
>>              case OPT_BOOL:
>> -                if ( !parse_bool(optval) )
>> +                rc = parse_bool(optval);
>> +                if ( rc == -1 )
> 
> Maybe "rc < 0"?

Okay.

> 
>> @@ -131,13 +147,21 @@ static void __init _cmdline_parse(const char *cmdline)
>>                      safe_strcpy(opt, "no");
>>                      optval = opt;
>>                  }
>> -                ((void (*)(const char *))param->var)(optval);
>> +                rc = ((int (*)(const char *))param->var)(optval);
>>                  break;
>>              default:
>>                  BUG();
>>                  break;
>>              }
>> +
>> +            break;
>>          }
>> +
>> +        if ( rc )
>> +            printk("parameter \"%s\" has invalid value \"%s\"!\n", optkey,
>> +                   optval);
> 
> With the changes made to optval in OPT_CUSTOM handling this
> may end up being confusing.

Oh yes, good catch.


Juergen
Jan Beulich Aug. 14, 2017, 1:54 p.m. UTC | #3
>>> On 14.08.17 at 15:31, <jgross@suse.com> wrote:
> On 14/08/17 14:46, Jan Beulich wrote:
>>>>> On 14.08.17 at 09:08, <jgross@suse.com> wrote:
>>> @@ -97,8 +102,9 @@ static void __init _cmdline_parse(const char *cmdline)
>>>                       !strncmp(param->name, opt, q + 1 - opt) )
>>>                  {
>>>                      optval[-1] = '=';
>>> -                    ((void (*)(const char *))param->var)(q);
>>> +                    rc = ((int (*)(const char *))param->var)(q);
>> 
>> Neither here nor in the earlier "let custom parameter parsing
>> routines return errno" nor in the overview you mention why this
>> is safe - it is not a given that caller and callee disagreeing on
>> return type is going to work. Just think of functions returning
>> aggregates or (on ix86) ones returning floating point values in
>> st(0).
> 
> I thought about using a union in struct kernel_param and removing
> above type cast. This would require modifying the initialization of
> the kernel_param struct via the *_param() macros, though.
> 
> The other possibility would be using __builtin_types_compatible_p()
> to check the function to be of proper type.
> 
> What would you like best?

I'd prefer the union approach; I was actually surprised to see
we still only have a void * there.

Jan
Jürgen Groß Aug. 15, 2017, 12:54 p.m. UTC | #4
On 14/08/17 14:46, Jan Beulich wrote:
>>>> On 14.08.17 at 09:08, <jgross@suse.com> wrote:
>> --- a/xen/common/kernel.c
>> +++ b/xen/common/kernel.c
>>                      optval[-1] = '\0';
>> +                    break;
> 
> Why? Applies to further break-s you add: At least in the past we
> had command line options with two handlers, where each of them
> needed to be invoked. I don't think we should make such impossible
> even if right now there aren't any such examples. Yet if you really
> mean to, then the behavioral change needs to be called out in the
> description.

While working on this I realized that this functionality has been
working only in some cases. The custom parsing functions are being
called with a copy of the option value, which they modify in some
cases. So a second handler being called would see another value as
the first handler, as long as modifying the option value keeps to be
allowed.

I see three possibilities here:

1. don't allow multiple handlers for the same parameter
2. restore the option value before calling each handler (as the
   error message I'm adding with this patch requires access to the
   whole option value this wouldn't be too hard)
3. don't allow a handler to modify the option value (solves my error
   message problem, too)

Any preferences?


Juergen
Jan Beulich Aug. 15, 2017, 1:01 p.m. UTC | #5
>>> On 15.08.17 at 14:54, <jgross@suse.com> wrote:
> On 14/08/17 14:46, Jan Beulich wrote:
>>>>> On 14.08.17 at 09:08, <jgross@suse.com> wrote:
>>> --- a/xen/common/kernel.c
>>> +++ b/xen/common/kernel.c
>>>                      optval[-1] = '\0';
>>> +                    break;
>> 
>> Why? Applies to further break-s you add: At least in the past we
>> had command line options with two handlers, where each of them
>> needed to be invoked. I don't think we should make such impossible
>> even if right now there aren't any such examples. Yet if you really
>> mean to, then the behavioral change needs to be called out in the
>> description.
> 
> While working on this I realized that this functionality has been
> working only in some cases. The custom parsing functions are being
> called with a copy of the option value, which they modify in some
> cases. So a second handler being called would see another value as
> the first handler, as long as modifying the option value keeps to be
> allowed.
> 
> I see three possibilities here:
> 
> 1. don't allow multiple handlers for the same parameter
> 2. restore the option value before calling each handler (as the
>    error message I'm adding with this patch requires access to the
>    whole option value this wouldn't be too hard)
> 3. don't allow a handler to modify the option value (solves my error
>    message problem, too)
> 
> Any preferences?

I have no particular preference between 2 and 3, but both are
better than 1.

Jan
Wei Liu Aug. 15, 2017, 2:56 p.m. UTC | #6
On Tue, Aug 15, 2017 at 02:54:07PM +0200, Juergen Gross wrote:
> On 14/08/17 14:46, Jan Beulich wrote:
> >>>> On 14.08.17 at 09:08, <jgross@suse.com> wrote:
> >> --- a/xen/common/kernel.c
> >> +++ b/xen/common/kernel.c
> >>                      optval[-1] = '\0';
> >> +                    break;
> > 
> > Why? Applies to further break-s you add: At least in the past we
> > had command line options with two handlers, where each of them
> > needed to be invoked. I don't think we should make such impossible
> > even if right now there aren't any such examples. Yet if you really
> > mean to, then the behavioral change needs to be called out in the
> > description.
> 
> While working on this I realized that this functionality has been
> working only in some cases. The custom parsing functions are being
> called with a copy of the option value, which they modify in some
> cases. So a second handler being called would see another value as
> the first handler, as long as modifying the option value keeps to be
> allowed.
> 
> I see three possibilities here:
> 
> 1. don't allow multiple handlers for the same parameter
> 2. restore the option value before calling each handler (as the
>    error message I'm adding with this patch requires access to the
>    whole option value this wouldn't be too hard)
> 3. don't allow a handler to modify the option value (solves my error
>    message problem, too)

Either 2 or 3 please.
diff mbox

Patch

diff --git a/xen/common/kernel.c b/xen/common/kernel.c
index ce7cb8adb5..756380be5b 100644
--- a/xen/common/kernel.c
+++ b/xen/common/kernel.c
@@ -23,9 +23,11 @@  enum system_state system_state = SYS_STATE_early_boot;
 xen_commandline_t saved_cmdline;
 static const char __initconst opt_builtin_cmdline[] = CONFIG_CMDLINE;
 
-static void __init assign_integer_param(
+static int __init assign_integer_param(
     const struct kernel_param *param, uint64_t val)
 {
+    unsigned int bits = param->len * BITS_PER_BYTE;
+
     switch ( param->len )
     {
     case sizeof(uint8_t):
@@ -43,14 +45,17 @@  static void __init assign_integer_param(
     default:
         BUG();
     }
+
+    return ( (val & (~0ULL << bits)) && ~(val | (~0ULL >> (65 - bits))) ) ?
+           -EOVERFLOW : 0;
 }
 
 static void __init _cmdline_parse(const char *cmdline)
 {
     char opt[128], *optval, *optkey, *q;
-    const char *p = cmdline;
+    const char *p = cmdline, *s;
     const struct kernel_param *param;
-    int bool_assert;
+    int bool_assert, rc = 0;
 
     for ( ; ; )
     {
@@ -97,8 +102,9 @@  static void __init _cmdline_parse(const char *cmdline)
                      !strncmp(param->name, opt, q + 1 - opt) )
                 {
                     optval[-1] = '=';
-                    ((void (*)(const char *))param->var)(q);
+                    rc = ((int (*)(const char *))param->var)(q);
                     optval[-1] = '\0';
+                    break;
                 }
                 continue;
             }
@@ -106,24 +112,34 @@  static void __init _cmdline_parse(const char *cmdline)
             switch ( param->type )
             {
             case OPT_STR:
+                rc = 0;
                 strlcpy(param->var, optval, param->len);
                 break;
             case OPT_UINT:
-                assign_integer_param(
+                rc = assign_integer_param(
                     param,
-                    simple_strtoll(optval, NULL, 0));
+                    simple_strtoll(optval, &s, 0));
+                if ( *s )
+                    rc = -EINVAL;
                 break;
             case OPT_BOOL:
-                if ( !parse_bool(optval) )
+                rc = parse_bool(optval);
+                if ( rc == -1 )
+                    break;
+                if ( !rc )
                     bool_assert = !bool_assert;
+                rc = 0;
                 assign_integer_param(param, bool_assert);
                 break;
             case OPT_SIZE:
-                assign_integer_param(
+                rc = assign_integer_param(
                     param,
-                    parse_size_and_unit(optval, NULL));
+                    parse_size_and_unit(optval, &s));
+                if ( *s )
+                    rc = -EINVAL;
                 break;
             case OPT_CUSTOM:
+                rc = -EINVAL;
                 if ( !bool_assert )
                 {
                     if ( *optval )
@@ -131,13 +147,21 @@  static void __init _cmdline_parse(const char *cmdline)
                     safe_strcpy(opt, "no");
                     optval = opt;
                 }
-                ((void (*)(const char *))param->var)(optval);
+                rc = ((int (*)(const char *))param->var)(optval);
                 break;
             default:
                 BUG();
                 break;
             }
+
+            break;
         }
+
+        if ( rc )
+            printk("parameter \"%s\" has invalid value \"%s\"!\n", optkey,
+                   optval);
+        if ( param >= __setup_end )
+            printk("parameter \"%s\" unknown!\n", optkey);
     }
 }
 
@@ -176,7 +200,8 @@  int __init parse_bool(const char *s)
          !strcmp("on", s) ||
          !strcmp("true", s) ||
          !strcmp("enable", s) ||
-         !strcmp("1", s) )
+         !strcmp("1", s) ||
+         !*s )
         return 1;
 
     return -1;