binary_sysctl(): fix memory leak
[linux-2.6.git] / kernel / sysctl_binary.c
index bf0a4b0..2ce1b30 100644 (file)
@@ -1,6 +1,6 @@
 #include <linux/stat.h>
 #include <linux/sysctl.h>
-#include "../fs/xfs/linux-2.6/xfs_sysctl.h"
+#include "../fs/xfs/xfs_sysctl.h"
 #include <linux/sunrpc/debug.h>
 #include <linux/string.h>
 #include <net/ip_vs.h>
@@ -12,6 +12,9 @@
 #include <linux/pid_namespace.h>
 #include <linux/file.h>
 #include <linux/ctype.h>
+#include <linux/netdevice.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
 
 #ifdef CONFIG_SYSCTL_SYSCALL
 
@@ -133,7 +136,6 @@ static const struct bin_table bin_kern_table[] = {
        { CTL_INT,      KERN_IA64_UNALIGNED,            "ignore-unaligned-usertrap" },
        { CTL_INT,      KERN_COMPAT_LOG,                "compat-log" },
        { CTL_INT,      KERN_MAX_LOCK_DEPTH,            "max_lock_depth" },
-       { CTL_INT,      KERN_NMI_WATCHDOG,              "nmi_watchdog" },
        { CTL_INT,      KERN_PANIC_ON_NMI,              "panic_on_unrecovered_nmi" },
        {}
 };
@@ -222,7 +224,6 @@ static const struct bin_table bin_net_ipv4_route_table[] = {
        { CTL_INT,      NET_IPV4_ROUTE_MTU_EXPIRES,             "mtu_expires" },
        { CTL_INT,      NET_IPV4_ROUTE_MIN_PMTU,                "min_pmtu" },
        { CTL_INT,      NET_IPV4_ROUTE_MIN_ADVMSS,              "min_adv_mss" },
-       { CTL_INT,      NET_IPV4_ROUTE_SECRET_INTERVAL,         "secret_interval" },
        {}
 };
 
@@ -1123,11 +1124,6 @@ out:
        return result;
 }
 
-static unsigned hex_value(int ch)
-{
-       return isdigit(ch) ? ch - '0' : ((ch | 0x20) - 'a') + 10;
-}
-
 static ssize_t bin_uuid(struct file *file,
        void __user *oldval, size_t oldlen, void __user *newval, size_t newlen)
 {
@@ -1155,7 +1151,8 @@ static ssize_t bin_uuid(struct file *file,
                        if (!isxdigit(str[0]) || !isxdigit(str[1]))
                                goto out;
 
-                       uuid[i] = (hex_value(str[0]) << 4) | hex_value(str[1]);
+                       uuid[i] = (hex_to_bin(str[0]) << 4) |
+                                       hex_to_bin(str[1]);
                        str += 2;
                        if (*str == '-')
                                str++;
@@ -1195,7 +1192,7 @@ static ssize_t bin_dn_node_address(struct file *file,
 
                buf[result] = '\0';
 
-               /* Convert the decnet addresss to binary */
+               /* Convert the decnet address to binary */
                result = -EIO;
                nodep = strchr(buf, '.') + 1;
                if (!nodep)
@@ -1250,9 +1247,12 @@ out:
 static const struct bin_table *get_sysctl(const int *name, int nlen, char *path)
 {
        const struct bin_table *table = &bin_root_table[0];
-       struct net *net = current->nsproxy->net_ns;
        int ctl_name;
 
+       /* The binary sysctl tables have a small maximum depth so
+        * there is no danger of overflowing our path as it PATH_MAX
+        * bytes long.
+        */
        memcpy(path, "sys/", 4);
        path += 4;
 
@@ -1263,30 +1263,30 @@ repeat:
        name++;
        nlen--;
        for ( ; table->convert; table++) {
-               struct net_device *dev = NULL;
-               const char *procname = NULL;
-
-               /* Use the well known sysctl number to proc name mapping */
-               if (ctl_name == table->ctl_name)
-                       procname = table->procname;
+               int len = 0;
 
                /*
                 * For a wild card entry map from ifindex to network
                 * device name.
                 */
-               else if (!table->ctl_name) {
+               if (!table->ctl_name) {
+#ifdef CONFIG_NET
+                       struct net *net = current->nsproxy->net_ns;
+                       struct net_device *dev;
                        dev = dev_get_by_index(net, ctl_name);
-                       if (dev)
-                               procname = dev->name;
+                       if (dev) {
+                               len = strlen(dev->name);
+                               memcpy(path, dev->name, len);
+                               dev_put(dev);
+                       }
+#endif
+               /* Use the well known sysctl number to proc name mapping */
+               } else if (ctl_name == table->ctl_name) {
+                       len = strlen(table->procname);
+                       memcpy(path, table->procname, len);
                }
-               if (procname) {
-                       int len;
-
-                       len = strlen(procname);
-                       memcpy(path, procname, len);
+               if (len) {
                        path += len;
-                       if (dev)
-                               dev_put(dev);
                        if (table->child) {
                                *path++ = '/';
                                table = table->child;
@@ -1321,13 +1321,11 @@ static ssize_t binary_sysctl(const int *name, int nlen,
        void __user *oldval, size_t oldlen, void __user *newval, size_t newlen)
 {
        const struct bin_table *table = NULL;
-       struct nameidata nd;
        struct vfsmount *mnt;
        struct file *file;
        ssize_t result;
        char *pathname;
        int flags;
-       int acc_mode, fmode;
 
        pathname = sysctl_getname(name, nlen, &table);
        result = PTR_ERR(pathname);
@@ -1337,31 +1335,17 @@ static ssize_t binary_sysctl(const int *name, int nlen,
        /* How should the sysctl be accessed? */
        if (oldval && oldlen && newval && newlen) {
                flags = O_RDWR;
-               acc_mode = MAY_READ | MAY_WRITE;
-               fmode = FMODE_READ | FMODE_WRITE;
        } else if (newval && newlen) {
                flags = O_WRONLY;
-               acc_mode = MAY_WRITE;
-               fmode = FMODE_WRITE;
        } else if (oldval && oldlen) {
                flags = O_RDONLY;
-               acc_mode = MAY_READ;
-               fmode = FMODE_READ;
        } else {
                result = 0;
                goto out_putname;
        }
 
        mnt = current->nsproxy->pid_ns->proc_mnt;
-       result = vfs_path_lookup(mnt->mnt_root, mnt, pathname, 0, &nd);
-       if (result)
-               goto out_putname;
-
-       result = may_open(&nd.path, acc_mode, fmode);
-       if (result)
-               goto out_putpath;
-
-       file = dentry_open(nd.path.dentry, nd.path.mnt, flags, current_cred());
+       file = file_open_root(mnt->mnt_root, mnt, pathname, flags);
        result = PTR_ERR(file);
        if (IS_ERR(file))
                goto out_putname;
@@ -1370,13 +1354,9 @@ static ssize_t binary_sysctl(const int *name, int nlen,
 
        fput(file);
 out_putname:
-       putname(pathname);
+       __putname(pathname);
 out:
        return result;
-
-out_putpath:
-       path_put(&nd.path);
-       goto out_putname;
 }
 
 
@@ -1395,6 +1375,13 @@ static void deprecated_sysctl_warning(const int *name, int nlen)
 {
        int i;
 
+       /*
+        * CTL_KERN/KERN_VERSION is used by older glibc and cannot
+        * ever go away.
+        */
+       if (name[0] == CTL_KERN && name[1] == KERN_VERSION)
+               return;
+
        if (printk_ratelimit()) {
                printk(KERN_INFO
                        "warning: process `%s' used the deprecated sysctl "
@@ -1406,6 +1393,35 @@ static void deprecated_sysctl_warning(const int *name, int nlen)
        return;
 }
 
+#define WARN_ONCE_HASH_BITS 8
+#define WARN_ONCE_HASH_SIZE (1<<WARN_ONCE_HASH_BITS)
+
+static DECLARE_BITMAP(warn_once_bitmap, WARN_ONCE_HASH_SIZE);
+
+#define FNV32_OFFSET 2166136261U
+#define FNV32_PRIME 0x01000193
+
+/*
+ * Print each legacy sysctl (approximately) only once.
+ * To avoid making the tables non-const use a external
+ * hash-table instead.
+ * Worst case hash collision: 6, but very rarely.
+ * NOTE! We don't use the SMP-safe bit tests. We simply
+ * don't care enough.
+ */
+static void warn_on_bintable(const int *name, int nlen)
+{
+       int i;
+       u32 hash = FNV32_OFFSET;
+
+       for (i = 0; i < nlen; i++)
+               hash = (hash ^ name[i]) * FNV32_PRIME;
+       hash %= WARN_ONCE_HASH_SIZE;
+       if (__test_and_set_bit(hash, warn_once_bitmap))
+               return;
+       deprecated_sysctl_warning(name, nlen);
+}
+
 static ssize_t do_sysctl(int __user *args_name, int nlen,
        void __user *oldval, size_t oldlen, void __user *newval, size_t newlen)
 {
@@ -1420,7 +1436,7 @@ static ssize_t do_sysctl(int __user *args_name, int nlen,
                if (get_user(name[i], args_name + i))
                        return -EFAULT;
 
-       deprecated_sysctl_warning(name, nlen);
+       warn_on_bintable(name, nlen);
 
        return binary_sysctl(name, nlen, oldval, oldlen, newval, newlen);
 }