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