On Fri 2017-01-27 19:19:30, Borislav Petkov wrote:
> + printk folk.
> 
> On Fri, Jan 27, 2017 at 04:42:30PM +0100, Rabin Vincent wrote:
> > proc_dostring() eats the '\n' and stops
> 
> Not a problem, see diff below.
> 
> Please do it this way instead because after a month no one will remember
> what this complex conditional means
> 
> > +             if (err < 0 || (err != *lenp && err + 1 != *lenp) ||
> > +                 err != strlen(devkmsg_log_str)) {
> 
> and why we did it this way.
> 
> Also, having the different parts of the conditional explained with a
> comment makes it much more obvious.
> 
> Also,
> 
> > Before this patch:
> > 
> >  # cat /proc/sys/kernel/printk_devkmsg
> >  ratelimit
> >  # echo off > /proc/sys/kernel/printk_devkmsg
> >  # sysctl -w kernel.printk_devkmsg=off
> >  sysctl: short write
> >  # echo -n off > /proc/sys/kernel/printk_devkmsg
> >  -sh: echo: write error: Invalid argument
> >  # echo -n offX > /proc/sys/kernel/printk_devkmsg
> >  #
> >  # printf "off\nX" >/proc/sys/kernel/printk_devkmsg
> >  -sh: printf: write error: Invalid argument
> > 
> > After this patch:
> > 
> >  # cat /proc/sys/kernel/printk_devkmsg
> >  ratelimit
> >  # echo off > /proc/sys/kernel/printk_devkmsg
> >  # sysctl -w kernel.printk_devkmsg=off
> >  kernel.printk_devkmsg = off
> >  # echo -n off > /proc/sys/kernel/printk_devkmsg
> >  # echo -n offX > /proc/sys/kernel/printk_devkmsg
> >  -sh: echo: write error: Invalid argument
> >  # printf "off\nX" >/proc/sys/kernel/printk_devkmsg
> >  -sh: printf: write error: Invalid argument
> 
> you can leave all those printk examples in the commit message but you
> should also explain why we're doing what we're doing. To allow this and
> that input and disallow others and why we need to "fish out" newline
> before proc_dostring(), yadda yadda.
> 
> Thanks.
> 
> ---
> 
> diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
> index 8b2696420abb..2a1f7c8efb16 100644
> --- a/kernel/printk/printk.c
> +++ b/kernel/printk/printk.c
> @@ -156,14 +156,19 @@ int devkmsg_sysctl_set_loglvl(struct ctl_table *table, 
> int write,
>  {
>       char old_str[DEVKMSG_STR_MAX_SIZE];
>       unsigned int old;
> +     bool newline = false;
>       int err;
>  
>       if (write) {
> +             char __user *b = buffer;
> +
>               if (devkmsg_log & DEVKMSG_LOG_MASK_LOCK)
>                       return -EINVAL;
>  
>               old = devkmsg_log;
>               strncpy(old_str, devkmsg_log_str, DEVKMSG_STR_MAX_SIZE);
> +
> +             newline = b[*lenp - 1] == '\n';

We must not access userspace pointer directly. One solution would be
to use get_user().

But a better solution might be to check also the trailing '\0' in
__control_devkmsg, see below. I has several advantages:

   + we will never assign "invalid" value to @devkmsg_log and
     do not need to revert it. Only devkmsg_log_str must be
     reverted.

   + __control_devkmsg() do not need to return the length of the
     string. We could simply check the error code.

   * the err variable will have the same meaning for both
     __control_devkmsg() and proc_dostring().


Note that

   echo "ratelimitXXXXXX" >/proc/sys/kernel/printk_devkmsg

succeeds because the copied string is limited by DEVKMSG_STR_MAX_SIZE.
We would need to add at least one byte to be able to detect an error
but I am not sure if it is worth it.


Anyway, something like this seems to work:

diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
index 8b2696420abb..d762190c8c00 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -107,17 +107,18 @@ static int __control_devkmsg(char *str)
        if (!str)
                return -EINVAL;
 
-       if (!strncmp(str, "on", 2)) {
+       /* Do exact match by comparing also the trailing '\0'. */
+       if (!strncmp(str, "on", 3)) {
                devkmsg_log = DEVKMSG_LOG_MASK_ON;
-               return 2;
-       } else if (!strncmp(str, "off", 3)) {
+       } else if (!strncmp(str, "off", 4)) {
                devkmsg_log = DEVKMSG_LOG_MASK_OFF;
-               return 3;
-       } else if (!strncmp(str, "ratelimit", 9)) {
+       } else if (!strncmp(str, "ratelimit", 10)) {
                devkmsg_log = DEVKMSG_LOG_MASK_DEFAULT;
-               return 9;
+       } else {
+               return -EINVAL;
        }
-       return -EINVAL;
+
+       return 0;
 }
 
 static int __init control_devkmsg(char *str)
@@ -155,14 +156,12 @@ int devkmsg_sysctl_set_loglvl(struct ctl_table *table, 
int write,
                              void __user *buffer, size_t *lenp, loff_t *ppos)
 {
        char old_str[DEVKMSG_STR_MAX_SIZE];
-       unsigned int old;
        int err;
 
        if (write) {
                if (devkmsg_log & DEVKMSG_LOG_MASK_LOCK)
                        return -EINVAL;
 
-               old = devkmsg_log;
                strncpy(old_str, devkmsg_log_str, DEVKMSG_STR_MAX_SIZE);
        }
 
@@ -173,21 +172,12 @@ int devkmsg_sysctl_set_loglvl(struct ctl_table *table, 
int write,
        if (write) {
                err = __control_devkmsg(devkmsg_log_str);
 
-               /*
-                * Do not accept an unknown string OR a known string with
-                * trailing crap...
-                */
-               if (err < 0 || (err + 1 != *lenp)) {
-
-                       /* ... and restore old setting. */
-                       devkmsg_log = old;
+               /* Restore old setting when unknown parameter was used. */
+               if (err)
                        strncpy(devkmsg_log_str, old_str, DEVKMSG_STR_MAX_SIZE);
-
-                       return -EINVAL;
-               }
        }
 
-       return 0;
+       return err;
 }
 
 /*

Reply via email to