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

From: Waiman Long
Date: Wed Mar 07 2018 - 15:35:35 EST


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@xxxxxxxxxx>
---
fs/proc/proc_sysctl.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++---
include/linux/sysctl.h | 1 +
2 files changed, 95 insertions(+), 5 deletions(-)

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