aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/sysctl_check.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/sysctl_check.c')
-rw-r--r--kernel/sysctl_check.c160
1 files changed, 160 insertions, 0 deletions
diff --git a/kernel/sysctl_check.c b/kernel/sysctl_check.c
new file mode 100644
index 00000000..4e4932a7
--- /dev/null
+++ b/kernel/sysctl_check.c
@@ -0,0 +1,160 @@
+#include <linux/stat.h>
+#include <linux/sysctl.h>
+#include "../fs/xfs/linux-2.6/xfs_sysctl.h"
+#include <linux/sunrpc/debug.h>
+#include <linux/string.h>
+#include <net/ip_vs.h>
+
+
+static int sysctl_depth(struct ctl_table *table)
+{
+ struct ctl_table *tmp;
+ int depth;
+
+ depth = 0;
+ for (tmp = table; tmp->parent; tmp = tmp->parent)
+ depth++;
+
+ return depth;
+}
+
+static struct ctl_table *sysctl_parent(struct ctl_table *table, int n)
+{
+ int i;
+
+ for (i = 0; table && i < n; i++)
+ table = table->parent;
+
+ return table;
+}
+
+
+static void sysctl_print_path(struct ctl_table *table)
+{
+ struct ctl_table *tmp;
+ int depth, i;
+ depth = sysctl_depth(table);
+ if (table->procname) {
+ for (i = depth; i >= 0; i--) {
+ tmp = sysctl_parent(table, i);
+ printk("/%s", tmp->procname?tmp->procname:"");
+ }
+ }
+ printk(" ");
+}
+
+static struct ctl_table *sysctl_check_lookup(struct nsproxy *namespaces,
+ struct ctl_table *table)
+{
+ struct ctl_table_header *head;
+ struct ctl_table *ref, *test;
+ int depth, cur_depth;
+
+ depth = sysctl_depth(table);
+
+ for (head = __sysctl_head_next(namespaces, NULL); head;
+ head = __sysctl_head_next(namespaces, head)) {
+ cur_depth = depth;
+ ref = head->ctl_table;
+repeat:
+ test = sysctl_parent(table, cur_depth);
+ for (; ref->procname; ref++) {
+ int match = 0;
+ if (cur_depth && !ref->child)
+ continue;
+
+ if (test->procname && ref->procname &&
+ (strcmp(test->procname, ref->procname) == 0))
+ match++;
+
+ if (match) {
+ if (cur_depth != 0) {
+ cur_depth--;
+ ref = ref->child;
+ goto repeat;
+ }
+ goto out;
+ }
+ }
+ }
+ ref = NULL;
+out:
+ sysctl_head_finish(head);
+ return ref;
+}
+
+static void set_fail(const char **fail, struct ctl_table *table, const char *str)
+{
+ if (*fail) {
+ printk(KERN_ERR "sysctl table check failed: ");
+ sysctl_print_path(table);
+ printk(" %s\n", *fail);
+ dump_stack();
+ }
+ *fail = str;
+}
+
+static void sysctl_check_leaf(struct nsproxy *namespaces,
+ struct ctl_table *table, const char **fail)
+{
+ struct ctl_table *ref;
+
+ ref = sysctl_check_lookup(namespaces, table);
+ if (ref && (ref != table))
+ set_fail(fail, table, "Sysctl already exists");
+}
+
+int sysctl_check_table(struct nsproxy *namespaces, struct ctl_table *table)
+{
+ int error = 0;
+ for (; table->procname; table++) {
+ const char *fail = NULL;
+
+ if (table->parent) {
+ if (!table->parent->procname)
+ set_fail(&fail, table, "Parent without procname");
+ }
+ if (table->child) {
+ if (table->data)
+ set_fail(&fail, table, "Directory with data?");
+ if (table->maxlen)
+ set_fail(&fail, table, "Directory with maxlen?");
+ if ((table->mode & (S_IRUGO|S_IXUGO)) != table->mode)
+ set_fail(&fail, table, "Writable sysctl directory");
+ if (table->proc_handler)
+ set_fail(&fail, table, "Directory with proc_handler");
+ if (table->extra1)
+ set_fail(&fail, table, "Directory with extra1");
+ if (table->extra2)
+ set_fail(&fail, table, "Directory with extra2");
+ } else {
+ if ((table->proc_handler == proc_dostring) ||
+ (table->proc_handler == proc_dointvec) ||
+ (table->proc_handler == proc_dointvec_minmax) ||
+ (table->proc_handler == proc_dointvec_jiffies) ||
+ (table->proc_handler == proc_dointvec_userhz_jiffies) ||
+ (table->proc_handler == proc_dointvec_ms_jiffies) ||
+ (table->proc_handler == proc_doulongvec_minmax) ||
+ (table->proc_handler == proc_doulongvec_ms_jiffies_minmax)) {
+ if (!table->data)
+ set_fail(&fail, table, "No data");
+ if (!table->maxlen)
+ set_fail(&fail, table, "No maxlen");
+ }
+#ifdef CONFIG_PROC_SYSCTL
+ if (!table->proc_handler)
+ set_fail(&fail, table, "No proc_handler");
+#endif
+ sysctl_check_leaf(namespaces, table, &fail);
+ }
+ if (table->mode > 0777)
+ set_fail(&fail, table, "bogus .mode");
+ if (fail) {
+ set_fail(&fail, table, NULL);
+ error = -EINVAL;
+ }
+ if (table->child)
+ error |= sysctl_check_table(namespaces, table->child);
+ }
+ return error;
+}