sysctl: simplify unsigned int support
authorLuis R. Rodriguez <mcgrof@kernel.org>
Wed, 12 Jul 2017 21:33:36 +0000 (14:33 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 12 Jul 2017 23:26:00 +0000 (16:26 -0700)
Commit e7d316a02f68 ("sysctl: handle error writing UINT_MAX to u32
fields") added proc_douintvec() to start help adding support for
unsigned int, this however was only half the work needed.  Two fixes
have come in since then for the following issues:

  o Printing the values shows a negative value, this happens since
    do_proc_dointvec() and this uses proc_put_long()

This was fixed by commit 5380e5644afbba9 ("sysctl: don't print negative
flag for proc_douintvec").

  o We can easily wrap around the int values: UINT_MAX is 4294967295, if
    we echo in 4294967295 + 1 we end up with 0, using 4294967295 + 2 we
    end up with 1.
  o We echo negative values in and they are accepted

This was fixed by commit 425fffd886ba ("sysctl: report EINVAL if value
is larger than UINT_MAX for proc_douintvec").

It still also failed to be added to sysctl_check_table()...  instead of
adding it with the current implementation just provide a proper and
simplified unsigned int support without any array unsigned int support
with no negative support at all.

Historically sysctl proc helpers have supported arrays, due to the
complexity this adds though we've taken a step back to evaluate array
users to determine if its worth upkeeping for unsigned int.  An
evaluation using Coccinelle has been done to perform a grammatical
search to ask ourselves:

  o How many sysctl proc_dointvec() (int) users exist which likely
    should be moved over to proc_douintvec() (unsigned int) ?
Answer: about 8
- Of these how many are array users ?
Answer: Probably only 1
  o How many sysctl array users exist ?
Answer: about 12

This last question gives us an idea just how popular arrays: they are not.
Array support should probably just be kept for strings.

The identified uint ports are:

  drivers/infiniband/core/ucma.c - max_backlog
  drivers/infiniband/core/iwcm.c - default_backlog
  net/core/sysctl_net_core.c - rps_sock_flow_sysctl()
  net/netfilter/nf_conntrack_timestamp.c - nf_conntrack_timestamp -- bool
  net/netfilter/nf_conntrack_acct.c nf_conntrack_acct -- bool
  net/netfilter/nf_conntrack_ecache.c - nf_conntrack_events -- bool
  net/netfilter/nf_conntrack_helper.c - nf_conntrack_helper -- bool
  net/phonet/sysctl.c proc_local_port_range()

The only possible array users is proc_local_port_range() but it does not
seem worth it to add array support just for this given the range support
works just as well.  Unsigned int support should be desirable more for
when you *need* more than INT_MAX or using int min/max support then does
not suffice for your ranges.

If you forget and by mistake happen to register an unsigned int proc
entry with an array, the driver will fail and you will get something as
follows:

sysctl table check failed: debug/test_sysctl//uint_0002 array now allowed
CPU: 2 PID: 1342 Comm: modprobe Tainted: G        W   E <etc>
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS <etc>
Call Trace:
 dump_stack+0x63/0x81
 __register_sysctl_table+0x350/0x650
 ? kmem_cache_alloc_trace+0x107/0x240
 __register_sysctl_paths+0x1b3/0x1e0
 ? 0xffffffffc005f000
 register_sysctl_table+0x1f/0x30
 test_sysctl_init+0x10/0x1000 [test_sysctl]
 do_one_initcall+0x52/0x1a0
 ? kmem_cache_alloc_trace+0x107/0x240
 do_init_module+0x5f/0x200
 load_module+0x1867/0x1bd0
 ? __symbol_put+0x60/0x60
 SYSC_finit_module+0xdf/0x110
 SyS_finit_module+0xe/0x10
 entry_SYSCALL_64_fastpath+0x1e/0xad
RIP: 0033:0x7f042b22d119
<etc>

Fixes: e7d316a02f68 ("sysctl: handle error writing UINT_MAX to u32 fields")
Link: http://lkml.kernel.org/r/20170519033554.18592-5-mcgrof@kernel.org
Signed-off-by: Luis R. Rodriguez <mcgrof@kernel.org>
Suggested-by: Alexey Dobriyan <adobriyan@gmail.com>
Cc: Subash Abhinov Kasiviswanathan <subashab@codeaurora.org>
Cc: Liping Zhang <zlpnobody@gmail.com>
Cc: Alexey Dobriyan <adobriyan@gmail.com>
Cc: Heinrich Schuchardt <xypron.glpk@gmx.de>
Cc: Kees Cook <keescook@chromium.org>
Cc: "David S. Miller" <davem@davemloft.net>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: "Eric W. Biederman" <ebiederm@xmission.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
fs/proc/proc_sysctl.c
kernel/sysctl.c

index 32c9c5630507ca80c138fea3e50b2156fa79001e..ee6feba8b6c0abb911333d68bf9ed229c7428b3a 100644 (file)
@@ -1061,6 +1061,18 @@ static int sysctl_err(const char *path, struct ctl_table *table, char *fmt, ...)
        return -EINVAL;
 }
 
+static int sysctl_check_table_array(const char *path, struct ctl_table *table)
+{
+       int err = 0;
+
+       if (table->proc_handler == proc_douintvec) {
+               if (table->maxlen != sizeof(unsigned int))
+                       err |= sysctl_err(path, table, "array now allowed");
+       }
+
+       return err;
+}
+
 static int sysctl_check_table(const char *path, struct ctl_table *table)
 {
        int err = 0;
@@ -1081,6 +1093,8 @@ static int sysctl_check_table(const char *path, struct ctl_table *table)
                                err |= sysctl_err(path, table, "No data");
                        if (!table->maxlen)
                                err |= sysctl_err(path, table, "No maxlen");
+                       else
+                               err |= sysctl_check_table_array(path, table);
                }
                if (!table->proc_handler)
                        err |= sysctl_err(path, table, "No proc_handler");
index 6f3bb1f099fa9ddb6363083fb9b8b057a93bd88a..d12078fc215fef4a90a1e75586c53cd108b83cb7 100644 (file)
@@ -2175,19 +2175,18 @@ static int do_proc_dointvec_conv(bool *negp, unsigned long *lvalp,
        return 0;
 }
 
-static int do_proc_douintvec_conv(bool *negp, unsigned long *lvalp,
-                                int *valp,
-                                int write, void *data)
+static int do_proc_douintvec_conv(unsigned long *lvalp,
+                                 unsigned int *valp,
+                                 int write, void *data)
 {
        if (write) {
-               if (*negp)
+               if (*lvalp > UINT_MAX)
                        return -EINVAL;
                if (*lvalp > UINT_MAX)
                        return -EINVAL;
                *valp = *lvalp;
        } else {
                unsigned int val = *valp;
-               *negp = false;
                *lvalp = (unsigned long)val;
        }
        return 0;
@@ -2287,6 +2286,146 @@ static int do_proc_dointvec(struct ctl_table *table, int write,
                        buffer, lenp, ppos, conv, data);
 }
 
+static int do_proc_douintvec_w(unsigned int *tbl_data,
+                              struct ctl_table *table,
+                              void __user *buffer,
+                              size_t *lenp, loff_t *ppos,
+                              int (*conv)(unsigned long *lvalp,
+                                          unsigned int *valp,
+                                          int write, void *data),
+                              void *data)
+{
+       unsigned long lval;
+       int err = 0;
+       size_t left;
+       bool neg;
+       char *kbuf = NULL, *p;
+
+       left = *lenp;
+
+       if (proc_first_pos_non_zero_ignore(ppos, table))
+               goto bail_early;
+
+       if (left > PAGE_SIZE - 1)
+               left = PAGE_SIZE - 1;
+
+       p = kbuf = memdup_user_nul(buffer, left);
+       if (IS_ERR(kbuf))
+               return -EINVAL;
+
+       left -= proc_skip_spaces(&p);
+       if (!left) {
+               err = -EINVAL;
+               goto out_free;
+       }
+
+       err = proc_get_long(&p, &left, &lval, &neg,
+                            proc_wspace_sep,
+                            sizeof(proc_wspace_sep), NULL);
+       if (err || neg) {
+               err = -EINVAL;
+               goto out_free;
+       }
+
+       if (conv(&lval, tbl_data, 1, data)) {
+               err = -EINVAL;
+               goto out_free;
+       }
+
+       if (!err && left)
+               left -= proc_skip_spaces(&p);
+
+out_free:
+       kfree(kbuf);
+       if (err)
+               return -EINVAL;
+
+       return 0;
+
+       /* This is in keeping with old __do_proc_dointvec() */
+bail_early:
+       *ppos += *lenp;
+       return err;
+}
+
+static int do_proc_douintvec_r(unsigned int *tbl_data, void __user *buffer,
+                              size_t *lenp, loff_t *ppos,
+                              int (*conv)(unsigned long *lvalp,
+                                          unsigned int *valp,
+                                          int write, void *data),
+                              void *data)
+{
+       unsigned long lval;
+       int err = 0;
+       size_t left;
+
+       left = *lenp;
+
+       if (conv(&lval, tbl_data, 0, data)) {
+               err = -EINVAL;
+               goto out;
+       }
+
+       err = proc_put_long(&buffer, &left, lval, false);
+       if (err || !left)
+               goto out;
+
+       err = proc_put_char(&buffer, &left, '\n');
+
+out:
+       *lenp -= left;
+       *ppos += *lenp;
+
+       return err;
+}
+
+static int __do_proc_douintvec(void *tbl_data, struct ctl_table *table,
+                              int write, void __user *buffer,
+                              size_t *lenp, loff_t *ppos,
+                              int (*conv)(unsigned long *lvalp,
+                                          unsigned int *valp,
+                                          int write, void *data),
+                              void *data)
+{
+       unsigned int *i, vleft;
+
+       if (!tbl_data || !table->maxlen || !*lenp || (*ppos && !write)) {
+               *lenp = 0;
+               return 0;
+       }
+
+       i = (unsigned int *) tbl_data;
+       vleft = table->maxlen / sizeof(*i);
+
+       /*
+        * Arrays are not supported, keep this simple. *Do not* add
+        * support for them.
+        */
+       if (vleft != 1) {
+               *lenp = 0;
+               return -EINVAL;
+       }
+
+       if (!conv)
+               conv = do_proc_douintvec_conv;
+
+       if (write)
+               return do_proc_douintvec_w(i, table, buffer, lenp, ppos,
+                                          conv, data);
+       return do_proc_douintvec_r(i, buffer, lenp, ppos, conv, data);
+}
+
+static int do_proc_douintvec(struct ctl_table *table, int write,
+                            void __user *buffer, size_t *lenp, loff_t *ppos,
+                            int (*conv)(unsigned long *lvalp,
+                                        unsigned int *valp,
+                                        int write, void *data),
+                            void *data)
+{
+       return __do_proc_douintvec(table->data, table, write,
+                                  buffer, lenp, ppos, conv, data);
+}
+
 /**
  * proc_dointvec - read a vector of integers
  * @table: the sysctl table
@@ -2322,8 +2461,8 @@ int proc_dointvec(struct ctl_table *table, int write,
 int proc_douintvec(struct ctl_table *table, int write,
                     void __user *buffer, size_t *lenp, loff_t *ppos)
 {
-       return do_proc_dointvec(table, write, buffer, lenp, ppos,
-                               do_proc_douintvec_conv, NULL);
+       return do_proc_douintvec(table, write, buffer, lenp, ppos,
+                                do_proc_douintvec_conv, NULL);
 }
 
 /*