diff mbox

[4/5] proc/sysctl: Handle CTL_FLAGS_SHOW_RANGE ctl_table flag

Message ID 1520454869-13871-5-git-send-email-longman@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Waiman Long March 7, 2018, 8:34 p.m. UTC
When registering ctl_table entries with CTL_FLAGS_SHOW_RANGE flag set,
it will populate the reserved range table entries with the proper
sysctl parameters to show the range the those ctl_table entries.

When unregistering the ctl_table, we also need to clear the reserved
range table entries to avoid referencing memory that will be freed.

Signed-off-by: Waiman Long <longman@redhat.com>
---
 fs/proc/proc_sysctl.c  | 99 +++++++++++++++++++++++++++++++++++++++++++++++---
 include/linux/sysctl.h |  1 +
 2 files changed, 95 insertions(+), 5 deletions(-)
diff mbox

Patch

diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index 493c975..5f8fde97 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -304,6 +304,16 @@  static void proc_sys_prune_dcache(struct ctl_table_header *head)
 static void start_unregistering(struct ctl_table_header *p)
 {
 	/*
+	 * Clear reserved range table entries before freeing.
+	 */
+	if (p->roffset) {
+		struct ctl_table *entry = p->ctl_table + p->roffset;
+
+		for (; entry->proc_handler == proc_show_minmax; entry++)
+			entry->procname = NULL;
+	}
+
+	/*
 	 * if p->used is 0, nobody will ever touch that entry again;
 	 * we'll eliminate all paths to it before dropping sysctl_lock
 	 */
@@ -1206,7 +1216,6 @@  static int insert_links(struct ctl_table_header *head)
 
 	if (head->set == root_set)
 		return 0;
-
 	core_parent = xlate_dir(root_set, head->parent);
 	if (IS_ERR(core_parent))
 		return 0;
@@ -1238,6 +1247,19 @@  static int insert_links(struct ctl_table_header *head)
 	return err;
 }
 
+static bool sysctl_show_range(struct ctl_table *entry)
+{
+	if (!(entry->flags & CTL_FLAGS_SHOW_RANGE))
+		return false;
+
+	if ((entry->maxlen == sizeof(int)) || (entry->maxlen == sizeof(long)))
+		return true;
+
+	pr_warn("Warning: ctl_table entry \"%s\" doesn't support CTL_FLAGS_SHOW_RANGE\n",
+		entry->procname);
+	return false;
+}
+
 /**
  * __register_sysctl_table - register a leaf sysctl table
  * @set: Sysctl tree to register on
@@ -1291,16 +1313,67 @@  struct ctl_table_header *__register_sysctl_table(
 	struct ctl_table *entry;
 	struct ctl_node *node;
 	int nr_entries = 0;
+	int nr_ranges = 0;	/* # of entries with CTL_FLAGS_SHOW_RANGE */
+	int namelen = 0;
+	int i, j;
 
-	for (entry = table; entry->procname; entry++)
+	for (entry = table; entry->procname; entry++) {
 		nr_entries++;
+		if (sysctl_show_range(entry)) {
+			nr_ranges++;
+			/* procname + "_range\0" suffix */
+			namelen += strlen(entry->procname) + 7;
+		}
+	}
+
+	if (nr_ranges) {
+		for (i = 0; entry->proc_handler == proc_show_minmax; entry++)
+			i++;
+
+		if (i < nr_ranges) {
+			pr_err("Error: Insufficient reserved ctl_table range entries (\"%s\")!\n",
+				table->procname);
+			return NULL;
+		} else if (i > nr_ranges) {
+			pr_warn("Warning: Too many reserved ctl_table range entries (\"%s\")!\n",
+				table->procname);
+		}
+	}
 
 	header = kzalloc(sizeof(struct ctl_table_header) +
-			 sizeof(struct ctl_node)*nr_entries, GFP_KERNEL);
+			 sizeof(struct ctl_node)*(nr_entries + nr_ranges) +
+			 namelen, GFP_KERNEL);
 	if (!header)
 		return NULL;
-
 	node = (struct ctl_node *)(header + 1);
+
+	/*
+	 * Fill up reserved range entries for showing the ranges of those
+	 * sysctl parameters that have the CTL_FLAGS_SHOW_RANGE flag set.
+	 */
+	if (nr_ranges) {
+		int len;
+		char *namebuf = (char *)(node + nr_entries + nr_ranges);
+
+		header->roffset = nr_entries;
+		for (i = 0, j = nr_entries ; i < nr_entries; i++) {
+			if (!sysctl_show_range(&table[i]))
+				continue;
+
+			len = strlen(table[i].procname);
+			memcpy(namebuf, table[i].procname, len);
+			memcpy(namebuf + len, "_range\0", 7);
+			table[j].procname = namebuf;
+			table[j].data = (void *)&table[i];
+			table[j].mode = table[i].mode & 0444;	/* Read only */
+
+			namebuf += len + 7;
+			namelen -= len + 7;
+			j++;
+		}
+		WARN_ON((j != nr_entries + nr_ranges) || namelen);
+	}
+
 	init_header(header, root, set, node, table);
 	if (sysctl_check_table(path, table))
 		goto fail;
@@ -1313,7 +1386,6 @@  struct ctl_table_header *__register_sysctl_table(
 
 	/* Find the directory for the ctl_table */
 	for (name = path; name; name = nextname) {
-		int namelen;
 		nextname = strchr(name, '/');
 		if (nextname) {
 			namelen = nextname - name;
@@ -1342,6 +1414,13 @@  struct ctl_table_header *__register_sysctl_table(
 	drop_sysctl_table(&dir->header);
 	spin_unlock(&sysctl_lock);
 fail:
+	if (nr_ranges) {
+		/*
+		 * Clear procname of the reserved range table entries.
+		 */
+		for (i = nr_entries; i < nr_entries + nr_ranges; i++)
+			table[i].procname = NULL;
+	}
 	kfree(header);
 	dump_stack();
 	return NULL;
@@ -1654,6 +1733,16 @@  void unregister_sysctl_table(struct ctl_table_header * header)
 			unregister_sysctl_table(subh);
 			kfree(table);
 		}
+		/*
+		 * Clear reserved range table entries before freeing.
+		 */
+		if (header->roffset) {
+			struct ctl_table *entry = header->ctl_table +
+						  header->roffset;
+
+			for (; entry->proc_handler == proc_show_minmax; entry++)
+				entry->procname = NULL;
+		}
 		kfree(header);
 		return;
 	}
diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h
index ca64d66..e922ee3 100644
--- a/include/linux/sysctl.h
+++ b/include/linux/sysctl.h
@@ -202,6 +202,7 @@  struct ctl_table_header
 			int used;
 			int count;
 			int nreg;
+			int roffset;	/* Offset of reserved range entries */
 		};
 		struct rcu_head rcu;
 	};