Re: [RFC v3 1/2] kernel/sysctl: support setting sysctl parameters from kernel command line

From: Vlastimil Babka
Date: Thu Mar 26 2020 - 18:13:13 EST


On 3/26/20 9:24 PM, Kees Cook wrote:
> On Thu, Mar 26, 2020 at 07:16:05PM +0100, Vlastimil Babka wrote:
>> Since the major change, I'm sending another RFC. If this approach is ok, then
>> it probably needs just some tweaks to the various error prints, and then
>> converting the rest of existing on-off aliases (if I come up with an idea how
>> to find them all). Thanks for all the feedback so far.
>
> Yeah, I think you can drop "RFC" from this in the next version -- you're
> well into getting this finalized IMO.

Thanks!

>>
>> .../admin-guide/kernel-parameters.txt | 9 ++
>> fs/proc/proc_sysctl.c | 90 +++++++++++++++++++
>> include/linux/sysctl.h | 4 +
>> init/main.c | 2 +
>> 4 files changed, 105 insertions(+)
>>
>> diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
>> index c07815d230bc..0c7e032e7c2e 100644
>> --- a/Documentation/admin-guide/kernel-parameters.txt
>> +++ b/Documentation/admin-guide/kernel-parameters.txt
>> @@ -4793,6 +4793,15 @@
>>
>> switches= [HW,M68k]
>>
>> + sysctl.*= [KNL]
>> + Set a sysctl parameter, right before loading the init
>> + process, as if the value was written to the respective
>> + /proc/sys/... file. Unrecognized parameters and invalid
>> + values are reported in the kernel log. Sysctls
>> + registered later by a loaded module cannot be set this
>> + way.
>
> Maybe add: "Both '.' and '/' are recognized as separators."

OK

>> +
>> +/* Set sysctl value passed on kernel command line. */
>> +static int process_sysctl_arg(char *param, char *val,
>> + const char *unused, void *arg)
>> +{
>> + char *path;
>> + struct file_system_type *proc_fs_type;
>> + struct file *file;
>> + int len;
>> + int err;
>> + loff_t pos = 0;
>> + ssize_t wret;
>> +
>> + if (strncmp(param, "sysctl", sizeof("sysctl") - 1))
>> + return 0;
>> +
>> + param += sizeof("sysctl") - 1;
>> +
>> + if (param[0] != '/' && param[0] != '.')
>> + return 0;
>> +
>> + param++;
>> +
>> + if (!proc_mnt) {
>> + proc_fs_type = get_fs_type("proc");
>> + if (!proc_fs_type) {
>> + pr_err("Failed to mount procfs to set sysctl from command line");
>> + return 0;
>> + }
>> + proc_mnt = kern_mount(proc_fs_type);
>> + put_filesystem(proc_fs_type);
>> + if (IS_ERR(proc_mnt)) {
>> + pr_err("Failed to mount procfs to set sysctl from command line");
>> + proc_mnt = NULL;
>> + return 0;
>> + }
>> + }
>> +
>> + len = 4 + strlen(param) + 1;
>> + path = kmalloc(len, GFP_KERNEL);
>> + if (!path)
>> + panic("%s: Failed to allocate %d bytes t\n", __func__, len);
>> +
>> + strcpy(path, "sys/");
>> + strcat(path, param);
>> + strreplace(path, '.', '/');
>
> You can do the replacement against the param directly, and also avoid
> all the open-coded string manipulations:
>
> strreplace(param, '.', '/');

I didn't want to modify param for the sake of error prints, but perhaps
the replacements won't confuse system admin too much?

> path = kasprintf(GFP_KERNEL, "sys/%s", param);

Ah yea that's nicer.

>> +
>> + file = file_open_root(proc_mnt->mnt_root, proc_mnt, path, O_WRONLY, 0);
>> + if (IS_ERR(file)) {
>> + err = PTR_ERR(file);
>> + pr_err("Error %d opening proc file %s to set sysctl parameter '%s=%s'",
>> + err, path, param, val);
>> + goto out;
>> + }
>> + len = strlen(val);
>> + wret = kernel_write(file, val, len, &pos);
>> + if (wret < 0) {
>> + err = wret;
>> + pr_err("Error %d writing to proc file %s to set sysctl parameter '%s=%s'",
>> + err, path, param, val);
>> + } else if (wret != len) {
>> + pr_err("Wrote only %ld bytes of %d writing to proc file %s to set sysctl parameter '%s=%s'",
>> + wret, len, path, param, val);
>> + }
>> +
>> + filp_close(file, NULL);
>
> Please check the return value of filp_close() and treat that as an error
> for this function too.

Well I could print it, but not much else? The unmount will probably fail
in that case?

>> +out:
>> + kfree(path);
>> + return 0;
>> +}
>> +
>> +void do_sysctl_args(void)
>> +{
>> + char *command_line;
>> +
>> + command_line = kstrdup(saved_command_line, GFP_KERNEL);
>> + if (!command_line)
>> + panic("%s: Failed to allocate copy of command line\n", __func__);
>> +
>> + parse_args("Setting sysctl args", command_line,
>> + NULL, 0, -1, -1, NULL, process_sysctl_arg);
>> +
>> + if (proc_mnt)
>> + kern_unmount(proc_mnt);
>
> I don't recommend sharing allocation lifetimes between two functions
> (process_sysctl_arg() allocs proc_mnt, and do_sysctl_args() frees it).
> And since you have a scoped lifetime, why allocate it or have it as a
> global at all? It can be stack-allocated and passed to the handler:

So the point was that the mount is only done when an applicable sysctl
parameter is found. On majority of systems there won't be any, at least
for initial X years :)

> void do_sysctl_args(void)
> {
> struct file_system_type *proc_fs_type;
> struct vfsmount *proc_mnt;
> char *command_line;
>
> proc_fs_type = get_fs_type("proc");
> if (!proc_fs_type) {
> pr_err("Failed to mount procfs to set sysctl from command line");
> return;
> }
> proc_mnt = kern_mount(proc_fs_type);
> put_filesystem(proc_fs_type);
> if (IS_ERR(proc_mnt)) {
> pr_err("Failed to mount procfs to set sysctl from command line");
> return;
> }
>
> command_line = kstrdup(saved_command_line, GFP_KERNEL);
> if (!command_line)
> panic("%s: Failed to allocate copy of command line\n",
> __func__);
>
> parse_args("Setting sysctl args", command_line,
> NULL, 0, -1, -1, proc_mnt, process_sysctl_arg);
>
> kfree(command_line);
> kern_unmount(proc_mnt);
> }
>
> And then pull the mount from (the hilariously overloaded name) "arg":

But I guess the "mount on first applicable argument" approach would work
with this scheme as well:

struct vfsmount *proc_mnt = NULL;
parse_args(..., &proc_mnt, ...)

Thanks!