sysctl: register only tables of sysctl files

Split the registration of a complex ctl_table array which may have
arbitrary numbers of directories (->child != NULL) and tables of files
into a series of simpler registrations that only register tables of files.

Graphically:

   register('dir', { + file-a
                     + file-b
                     + subdir1
                       + file-c
                     + subdir2
                       + file-d
                       + file-e })

is transformed into:
   wrapper->subheaders[0] = register('dir', {file1-a, file1-b})
   wrapper->subheaders[1] = register('dir/subdir1', {file-c})
   wrapper->subheaders[2] = register('dir/subdir2', {file-d, file-e})
   return wrapper

This guarantees that __register_sysctl_table will only see a simple
ctl_table array with all entries having (->child == NULL).

Care was taken to pass the original simple ctl_table arrays to
__register_sysctl_table whenever possible.

This change is derived from a similar patch written
by Lucrian Grijincu.

Inspired-by: Lucian Adrian Grijincu <lucian.grijincu@gmail.com>
Signed-off-by: Eric W. Biederman <ebiederm@xmission.com>
This commit is contained in:
Eric W. Biederman 2012-01-22 18:22:05 -08:00
parent ec6a52668d
commit f728019bb7
2 changed files with 148 additions and 19 deletions

View File

@ -882,7 +882,7 @@ static int sysctl_check_table(struct nsproxy *namespaces, struct ctl_table *tabl
#endif /* CONFIG_SYSCTL_SYSCALL_CHECK */
/**
* __register_sysctl_table - register a sysctl table
* __register_sysctl_table - register a leaf sysctl table
* @root: List of sysctl headers to register on
* @namespaces: Data to compute which lists of sysctl entries are visible
* @path: The path to the directory the sysctl table is in.
@ -900,29 +900,19 @@ static int sysctl_check_table(struct nsproxy *namespaces, struct ctl_table *tabl
*
* maxlen - the maximum size in bytes of the data
*
* mode - the file permissions for the /proc/sys file, and for sysctl(2)
* mode - the file permissions for the /proc/sys file
*
* child - a pointer to the child sysctl table if this entry is a directory, or
* %NULL.
* child - must be %NULL.
*
* proc_handler - the text handler routine (described below)
*
* de - for internal use by the sysctl routines
*
* extra1, extra2 - extra pointers usable by the proc handler routines
*
* Leaf nodes in the sysctl tree will be represented by a single file
* under /proc; non-leaf nodes will be represented by directories.
*
* sysctl(2) can automatically manage read and write requests through
* the sysctl table. The data and maxlen fields of the ctl_table
* struct enable minimal validation of the values being written to be
* performed, and the mode field allows minimal authentication.
*
* There must be a proc_handler routine for any terminal nodes
* mirrored under /proc/sys (non-terminals are handled by a built-in
* directory handler). Several default handlers are available to
* cover common cases -
* There must be a proc_handler routine for any terminal nodes.
* Several default handlers are available to cover common cases -
*
* proc_dostring(), proc_dointvec(), proc_dointvec_jiffies(),
* proc_dointvec_userhz_jiffies(), proc_dointvec_minmax(),
@ -1059,6 +1049,100 @@ static char *append_path(const char *path, char *pos, const char *name)
return pos;
}
static int count_subheaders(struct ctl_table *table)
{
int has_files = 0;
int nr_subheaders = 0;
struct ctl_table *entry;
/* special case: no directory and empty directory */
if (!table || !table->procname)
return 1;
for (entry = table; entry->procname; entry++) {
if (entry->child)
nr_subheaders += count_subheaders(entry->child);
else
has_files = 1;
}
return nr_subheaders + has_files;
}
static int register_leaf_sysctl_tables(const char *path, char *pos,
struct ctl_table_header ***subheader,
struct ctl_table_root *root, struct nsproxy *namespaces,
struct ctl_table *table)
{
struct ctl_table *ctl_table_arg = NULL;
struct ctl_table *entry, *files;
int nr_files = 0;
int nr_dirs = 0;
int err = -ENOMEM;
for (entry = table; entry->procname; entry++) {
if (entry->child)
nr_dirs++;
else
nr_files++;
}
files = table;
/* If there are mixed files and directories we need a new table */
if (nr_dirs && nr_files) {
struct ctl_table *new;
files = kzalloc(sizeof(struct ctl_table) * (nr_files + 1),
GFP_KERNEL);
if (!files)
goto out;
ctl_table_arg = files;
for (new = files, entry = table; entry->procname; entry++) {
if (entry->child)
continue;
*new = *entry;
new++;
}
}
/* Register everything except a directory full of subdirectories */
if (nr_files || !nr_dirs) {
struct ctl_table_header *header;
header = __register_sysctl_table(root, namespaces, path, files);
if (!header) {
kfree(ctl_table_arg);
goto out;
}
/* Remember if we need to free the file table */
header->ctl_table_arg = ctl_table_arg;
**subheader = header;
(*subheader)++;
}
/* Recurse into the subdirectories. */
for (entry = table; entry->procname; entry++) {
char *child_pos;
if (!entry->child)
continue;
err = -ENAMETOOLONG;
child_pos = append_path(path, pos, entry->procname);
if (!child_pos)
goto out;
err = register_leaf_sysctl_tables(path, child_pos, subheader,
root, namespaces, entry->child);
pos[0] = '\0';
if (err)
goto out;
}
err = 0;
out:
/* On failure our caller will unregister all registered subheaders */
return err;
}
/**
* __register_sysctl_paths - register a sysctl table hierarchy
* @root: List of sysctl headers to register on
@ -1077,7 +1161,8 @@ struct ctl_table_header *__register_sysctl_paths(
const struct ctl_path *path, struct ctl_table *table)
{
struct ctl_table *ctl_table_arg = table;
struct ctl_table_header *header = NULL;
int nr_subheaders = count_subheaders(table);
struct ctl_table_header *header = NULL, **subheaders, **subheader;
const struct ctl_path *component;
char *new_path, *pos;
@ -1097,12 +1182,39 @@ struct ctl_table_header *__register_sysctl_paths(
goto out;
table = table->child;
}
header = __register_sysctl_table(root, namespaces, new_path, table);
if (header)
if (nr_subheaders == 1) {
header = __register_sysctl_table(root, namespaces, new_path, table);
if (header)
header->ctl_table_arg = ctl_table_arg;
} else {
header = kzalloc(sizeof(*header) +
sizeof(*subheaders)*nr_subheaders, GFP_KERNEL);
if (!header)
goto out;
subheaders = (struct ctl_table_header **) (header + 1);
subheader = subheaders;
header->ctl_table_arg = ctl_table_arg;
if (register_leaf_sysctl_tables(new_path, pos, &subheader,
root, namespaces, table))
goto err_register_leaves;
}
out:
kfree(new_path);
return header;
err_register_leaves:
while (subheader > subheaders) {
struct ctl_table_header *subh = *(--subheader);
struct ctl_table *table = subh->ctl_table_arg;
unregister_sysctl_table(subh);
kfree(table);
}
kfree(header);
header = NULL;
goto out;
}
/**
@ -1149,11 +1261,28 @@ EXPORT_SYMBOL(register_sysctl_table);
*/
void unregister_sysctl_table(struct ctl_table_header * header)
{
int nr_subheaders;
might_sleep();
if (header == NULL)
return;
nr_subheaders = count_subheaders(header->ctl_table_arg);
if (unlikely(nr_subheaders > 1)) {
struct ctl_table_header **subheaders;
int i;
subheaders = (struct ctl_table_header **)(header + 1);
for (i = nr_subheaders -1; i >= 0; i--) {
struct ctl_table_header *subh = subheaders[i];
struct ctl_table *table = subh->ctl_table_arg;
unregister_sysctl_table(subh);
kfree(table);
}
kfree(header);
return;
}
spin_lock(&sysctl_lock);
start_unregistering(header);
if (!--header->parent->count) {

View File

@ -1015,7 +1015,7 @@ struct ctl_table
void *data;
int maxlen;
umode_t mode;
struct ctl_table *child;
struct ctl_table *child; /* Deprecated */
struct ctl_table *parent; /* Automatically set */
proc_handler *proc_handler; /* Callback for text formatting */
struct ctl_table_poll *poll;