Thread (7 messages) 7 messages, 3 authors, 2020-03-27

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

From: Vlastimil Babka <hidden>
Date: 2020-03-26 22:13:12
Also in: linux-mm, lkml

On 3/26/20 9:24 PM, Kees Cook wrote:
On Thu, Mar 26, 2020 at 07:16:05PM +0100, Vlastimil Babka wrote:
quoted
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!
quoted
 .../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
quoted
+
+/* 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.
quoted
+
+	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?
quoted
+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!
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help