Re: [PATCH] printk: fix printk.devkmsg sysctl

From: Petr Mladek
Date: Mon Jan 30 2017 - 08:38:16 EST


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;
}

/*