sysctl: pass kernel pointers to ->proc_handler
[linux-block.git] / fs / proc / proc_sysctl.c
index b6f5d459b087de348a1f71a9ab08a7809552e36a..df2143e05c571e830f71640961a21b1e455fe311 100644 (file)
@@ -539,13 +539,13 @@ out:
        return err;
 }
 
-static ssize_t proc_sys_call_handler(struct file *filp, void __user *buf,
+static ssize_t proc_sys_call_handler(struct file *filp, void __user *ubuf,
                size_t count, loff_t *ppos, int write)
 {
        struct inode *inode = file_inode(filp);
        struct ctl_table_header *head = grab_header(inode);
        struct ctl_table *table = PROC_I(inode)->sysctl_entry;
-       void *new_buf = NULL;
+       void *kbuf;
        ssize_t error;
 
        if (IS_ERR(head))
@@ -564,27 +564,38 @@ static ssize_t proc_sys_call_handler(struct file *filp, void __user *buf,
        if (!table->proc_handler)
                goto out;
 
-       error = BPF_CGROUP_RUN_PROG_SYSCTL(head, table, write, buf, &count,
-                                          ppos, &new_buf);
+       if (write) {
+               kbuf = memdup_user_nul(ubuf, count);
+               if (IS_ERR(kbuf)) {
+                       error = PTR_ERR(kbuf);
+                       goto out;
+               }
+       } else {
+               error = -ENOMEM;
+               kbuf = kzalloc(count, GFP_KERNEL);
+               if (!kbuf)
+                       goto out;
+       }
+
+       error = BPF_CGROUP_RUN_PROG_SYSCTL(head, table, write, &kbuf, &count,
+                                          ppos);
        if (error)
-               goto out;
+               goto out_free_buf;
 
        /* careful: calling conventions are nasty here */
-       if (new_buf) {
-               mm_segment_t old_fs;
-
-               old_fs = get_fs();
-               set_fs(KERNEL_DS);
-               error = table->proc_handler(table, write, (void __user *)new_buf,
-                                           &count, ppos);
-               set_fs(old_fs);
-               kfree(new_buf);
-       } else {
-               error = table->proc_handler(table, write, buf, &count, ppos);
+       error = table->proc_handler(table, write, kbuf, &count, ppos);
+       if (error)
+               goto out_free_buf;
+
+       if (!write) {
+               error = -EFAULT;
+               if (copy_to_user(ubuf, kbuf, count))
+                       goto out_free_buf;
        }
 
-       if (!error)
-               error = count;
+       error = count;
+out_free_buf:
+       kfree(kbuf);
 out:
        sysctl_head_finish(head);