clinuxlinux-kernelrhelprintk

Why the printk console_loglevel can be lower than minimum_console_loglevel?


My Linux distro is RHEL7, and kernel version is 3.10.0.

Form the printk document, I know minimum_console_loglevel definition:

  • minimum_console_loglevel: minimum (highest) value to which console_loglevel can be set

Querying current log level of printk:

[root@localhost kernel]# cat /proc/sys/kernel/printk
7       4       1       7

Modify current console log level:

[root@localhost kernel]# echo 0 > /proc/sys/kernel/printk
[root@localhost kernel]# cat /proc/sys/kernel/printk
0       4       1       7

Per my understanding, the minimum_console_loglevel is 1, so modifying console_loglevel to 0 should be failed. But from the cat output, it seems success.

From printk.c code:

case SYSLOG_ACTION_CONSOLE_LEVEL:
        error = -EINVAL;
        if (len < 1 || len > 8)
            goto out;
        if (len < minimum_console_loglevel)
            len = minimum_console_loglevel;
        console_loglevel = len;
        /* Implicitly re-enable logging to console */
        saved_console_loglevel = -1;
        error = 0;
        break;

I also think console_loglevel values shouldn't be modified.


Solution

  • You're looking at the wrong code. /proc/sys/kernel/printk is provided by kernel/sysctl.c. The definition for printk looks like this:

    {
        .procname       = "printk",
        .data           = &console_loglevel,
        .maxlen         = 4*sizeof(int),
        .mode           = 0644,
        .proc_handler   = proc_dointvec,
    },
    

    This defines a file called printk which controls an int array of 4 elements. Crucially, proc_dointvec, the handler function, is a generic function that operates on an array of integers, and it doesn't validate its arguments.

    As root, you can therefore write whatever garbage numbers you want to /proc/sys/kernel/printk and the kernel will happily accept them. Demo:

    root@ubuntu:/proc/sys/kernel# echo '-989897 42 -2147483648 0' > printk
    root@ubuntu:/proc/sys/kernel# cat printk
    -989897 42  -18446744071562067968   0
    

    (That 3rd one is actually an amusing and probably harmless bug I've just discovered...)

    From printk.h you can see that the four log level parameters are derived straight from this four-element array:

    extern int console_printk[];
    
    #define console_loglevel (console_printk[0])
    #define default_message_loglevel (console_printk[1])
    #define minimum_console_loglevel (console_printk[2])
    #define default_console_loglevel (console_printk[3])
    

    So, then, what does the code you posted do then? It implements the syslog syscall, as noted by the name of the function (do_syslog). As noted by the man page:

       SYSLOG_ACTION_CONSOLE_LEVEL (8)
              The call sets console_loglevel to the value given in len,
              which must be an integer between 1 and 8 (inclusive).  The
              kernel silently enforces a minimum value of
              minimum_console_loglevel for len.  See the log level section
              for details.  The bufp argument is ignored.
    

    We can see that the syslog system call also lets you set the console log level, but it actually checks that the values are valid. Let's test with a simple test program (note that glibc calls the function klogctl instead of syslog, since the user-space syslog function does something else):

    #include <sys/klog.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <stdlib.h>
    
    #define SYSLOG_ACTION_CONSOLE_LEVEL 8
    
    int main(int argc, char **argv) {
        if(klogctl(SYSLOG_ACTION_CONSOLE_LEVEL, NULL, atoi(argv[1])) < 0) {
            perror("klogctl");
        } else {
            printf("klogctl succeeded\n");
        }
        return 0;
    }
    

    Run it (after resetting printk back to 7 4 1 7):

    root@ubuntu:/tmp# ./test 1 ; cat /proc/sys/kernel/printk
    klogctl succeeded
    1   4   1   7
    root@ubuntu:/tmp# ./test 0 ; cat /proc/sys/kernel/printk
    klogctl: Invalid argument
    1   4   1   7
    

    and so you can see that syslog (called via klogctl) does actually check the validity of your parameters.