diff options
author | Joe Lawrence <joe.lawrence@redhat.com> | 2017-11-17 18:29:24 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2017-11-17 19:10:03 -0500 |
commit | 7a8d181949fb2c16be00f8cdb354794a30e46b39 (patch) | |
tree | 4f18ea1b17605e97a2c5a0089ff819eb60435bc9 | |
parent | d3f14c485867cfb2e0c48aa88c41d0ef4bf5209c (diff) |
pipe: add proc_dopipe_max_size() to safely assign pipe_max_size
pipe_max_size is assigned directly via procfs sysctl:
static struct ctl_table fs_table[] = {
...
{
.procname = "pipe-max-size",
.data = &pipe_max_size,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &pipe_proc_fn,
.extra1 = &pipe_min_size,
},
...
int pipe_proc_fn(struct ctl_table *table, int write, void __user *buf,
size_t *lenp, loff_t *ppos)
{
...
ret = proc_dointvec_minmax(table, write, buf, lenp, ppos)
...
and then later rounded in-place a few statements later:
...
pipe_max_size = round_pipe_size(pipe_max_size);
...
This leaves a window of time between initial assignment and rounding
that may be visible to other threads. (For example, one thread sets a
non-rounded value to pipe_max_size while another reads its value.)
Similar reads of pipe_max_size are potentially racy:
pipe.c :: alloc_pipe_info()
pipe.c :: pipe_set_size()
Add a new proc_dopipe_max_size() that consolidates reading the new value
from the user buffer, verifying bounds, and calling round_pipe_size()
with a single assignment to pipe_max_size.
Link: http://lkml.kernel.org/r/1507658689-11669-4-git-send-email-joe.lawrence@redhat.com
Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com>
Reported-by: Mikulas Patocka <mpatocka@redhat.com>
Reviewed-by: Mikulas Patocka <mpatocka@redhat.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Jens Axboe <axboe@kernel.dk>
Cc: Michael Kerrisk <mtk.manpages@gmail.com>
Cc: Randy Dunlap <rdunlap@infradead.org>
Cc: Josh Poimboeuf <jpoimboe@redhat.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | fs/pipe.c | 18 | ||||
-rw-r--r-- | include/linux/pipe_fs_i.h | 1 | ||||
-rw-r--r-- | include/linux/sysctl.h | 3 | ||||
-rw-r--r-- | kernel/sysctl.c | 49 |
4 files changed, 56 insertions, 15 deletions
@@ -1020,7 +1020,7 @@ const struct file_operations pipefifo_fops = { | |||
1020 | * Currently we rely on the pipe array holding a power-of-2 number | 1020 | * Currently we rely on the pipe array holding a power-of-2 number |
1021 | * of pages. Returns 0 on error. | 1021 | * of pages. Returns 0 on error. |
1022 | */ | 1022 | */ |
1023 | static inline unsigned int round_pipe_size(unsigned int size) | 1023 | unsigned int round_pipe_size(unsigned int size) |
1024 | { | 1024 | { |
1025 | unsigned long nr_pages; | 1025 | unsigned long nr_pages; |
1026 | 1026 | ||
@@ -1125,25 +1125,13 @@ out_revert_acct: | |||
1125 | } | 1125 | } |
1126 | 1126 | ||
1127 | /* | 1127 | /* |
1128 | * This should work even if CONFIG_PROC_FS isn't set, as proc_dointvec_minmax | 1128 | * This should work even if CONFIG_PROC_FS isn't set, as proc_dopipe_max_size |
1129 | * will return an error. | 1129 | * will return an error. |
1130 | */ | 1130 | */ |
1131 | int pipe_proc_fn(struct ctl_table *table, int write, void __user *buf, | 1131 | int pipe_proc_fn(struct ctl_table *table, int write, void __user *buf, |
1132 | size_t *lenp, loff_t *ppos) | 1132 | size_t *lenp, loff_t *ppos) |
1133 | { | 1133 | { |
1134 | unsigned int rounded_pipe_max_size; | 1134 | return proc_dopipe_max_size(table, write, buf, lenp, ppos); |
1135 | int ret; | ||
1136 | |||
1137 | ret = proc_douintvec_minmax(table, write, buf, lenp, ppos); | ||
1138 | if (ret < 0 || !write) | ||
1139 | return ret; | ||
1140 | |||
1141 | rounded_pipe_max_size = round_pipe_size(pipe_max_size); | ||
1142 | if (rounded_pipe_max_size == 0) | ||
1143 | return -EINVAL; | ||
1144 | |||
1145 | pipe_max_size = rounded_pipe_max_size; | ||
1146 | return ret; | ||
1147 | } | 1135 | } |
1148 | 1136 | ||
1149 | /* | 1137 | /* |
diff --git a/include/linux/pipe_fs_i.h b/include/linux/pipe_fs_i.h index 6a80cfc63e0c..2dc5e9870fcd 100644 --- a/include/linux/pipe_fs_i.h +++ b/include/linux/pipe_fs_i.h | |||
@@ -191,5 +191,6 @@ long pipe_fcntl(struct file *, unsigned int, unsigned long arg); | |||
191 | struct pipe_inode_info *get_pipe_info(struct file *file); | 191 | struct pipe_inode_info *get_pipe_info(struct file *file); |
192 | 192 | ||
193 | int create_pipe_files(struct file **, int); | 193 | int create_pipe_files(struct file **, int); |
194 | unsigned int round_pipe_size(unsigned int size); | ||
194 | 195 | ||
195 | #endif | 196 | #endif |
diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index b769ecfcc3bd..992bc9948232 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h | |||
@@ -51,6 +51,9 @@ extern int proc_dointvec_minmax(struct ctl_table *, int, | |||
51 | extern int proc_douintvec_minmax(struct ctl_table *table, int write, | 51 | extern int proc_douintvec_minmax(struct ctl_table *table, int write, |
52 | void __user *buffer, size_t *lenp, | 52 | void __user *buffer, size_t *lenp, |
53 | loff_t *ppos); | 53 | loff_t *ppos); |
54 | extern int proc_dopipe_max_size(struct ctl_table *table, int write, | ||
55 | void __user *buffer, size_t *lenp, | ||
56 | loff_t *ppos); | ||
54 | extern int proc_dointvec_jiffies(struct ctl_table *, int, | 57 | extern int proc_dointvec_jiffies(struct ctl_table *, int, |
55 | void __user *, size_t *, loff_t *); | 58 | void __user *, size_t *, loff_t *); |
56 | extern int proc_dointvec_userhz_jiffies(struct ctl_table *, int, | 59 | extern int proc_dointvec_userhz_jiffies(struct ctl_table *, int, |
diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 2d42183b4c98..138b6484f277 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c | |||
@@ -66,6 +66,7 @@ | |||
66 | #include <linux/kexec.h> | 66 | #include <linux/kexec.h> |
67 | #include <linux/bpf.h> | 67 | #include <linux/bpf.h> |
68 | #include <linux/mount.h> | 68 | #include <linux/mount.h> |
69 | #include <linux/pipe_fs_i.h> | ||
69 | 70 | ||
70 | #include <linux/uaccess.h> | 71 | #include <linux/uaccess.h> |
71 | #include <asm/processor.h> | 72 | #include <asm/processor.h> |
@@ -2620,6 +2621,47 @@ int proc_douintvec_minmax(struct ctl_table *table, int write, | |||
2620 | do_proc_douintvec_minmax_conv, ¶m); | 2621 | do_proc_douintvec_minmax_conv, ¶m); |
2621 | } | 2622 | } |
2622 | 2623 | ||
2624 | struct do_proc_dopipe_max_size_conv_param { | ||
2625 | unsigned int *min; | ||
2626 | }; | ||
2627 | |||
2628 | static int do_proc_dopipe_max_size_conv(unsigned long *lvalp, | ||
2629 | unsigned int *valp, | ||
2630 | int write, void *data) | ||
2631 | { | ||
2632 | struct do_proc_dopipe_max_size_conv_param *param = data; | ||
2633 | |||
2634 | if (write) { | ||
2635 | unsigned int val = round_pipe_size(*lvalp); | ||
2636 | |||
2637 | if (val == 0) | ||
2638 | return -EINVAL; | ||
2639 | |||
2640 | if (param->min && *param->min > val) | ||
2641 | return -ERANGE; | ||
2642 | |||
2643 | if (*lvalp > UINT_MAX) | ||
2644 | return -EINVAL; | ||
2645 | |||
2646 | *valp = val; | ||
2647 | } else { | ||
2648 | unsigned int val = *valp; | ||
2649 | *lvalp = (unsigned long) val; | ||
2650 | } | ||
2651 | |||
2652 | return 0; | ||
2653 | } | ||
2654 | |||
2655 | int proc_dopipe_max_size(struct ctl_table *table, int write, | ||
2656 | void __user *buffer, size_t *lenp, loff_t *ppos) | ||
2657 | { | ||
2658 | struct do_proc_dopipe_max_size_conv_param param = { | ||
2659 | .min = (unsigned int *) table->extra1, | ||
2660 | }; | ||
2661 | return do_proc_douintvec(table, write, buffer, lenp, ppos, | ||
2662 | do_proc_dopipe_max_size_conv, ¶m); | ||
2663 | } | ||
2664 | |||
2623 | static void validate_coredump_safety(void) | 2665 | static void validate_coredump_safety(void) |
2624 | { | 2666 | { |
2625 | #ifdef CONFIG_COREDUMP | 2667 | #ifdef CONFIG_COREDUMP |
@@ -3125,6 +3167,12 @@ int proc_douintvec_minmax(struct ctl_table *table, int write, | |||
3125 | return -ENOSYS; | 3167 | return -ENOSYS; |
3126 | } | 3168 | } |
3127 | 3169 | ||
3170 | int proc_dopipe_max_size(struct ctl_table *table, int write, | ||
3171 | void __user *buffer, size_t *lenp, loff_t *ppos) | ||
3172 | { | ||
3173 | return -ENOSYS; | ||
3174 | } | ||
3175 | |||
3128 | int proc_dointvec_jiffies(struct ctl_table *table, int write, | 3176 | int proc_dointvec_jiffies(struct ctl_table *table, int write, |
3129 | void __user *buffer, size_t *lenp, loff_t *ppos) | 3177 | void __user *buffer, size_t *lenp, loff_t *ppos) |
3130 | { | 3178 | { |
@@ -3168,6 +3216,7 @@ EXPORT_SYMBOL(proc_douintvec); | |||
3168 | EXPORT_SYMBOL(proc_dointvec_jiffies); | 3216 | EXPORT_SYMBOL(proc_dointvec_jiffies); |
3169 | EXPORT_SYMBOL(proc_dointvec_minmax); | 3217 | EXPORT_SYMBOL(proc_dointvec_minmax); |
3170 | EXPORT_SYMBOL_GPL(proc_douintvec_minmax); | 3218 | EXPORT_SYMBOL_GPL(proc_douintvec_minmax); |
3219 | EXPORT_SYMBOL_GPL(proc_dopipe_max_size); | ||
3171 | EXPORT_SYMBOL(proc_dointvec_userhz_jiffies); | 3220 | EXPORT_SYMBOL(proc_dointvec_userhz_jiffies); |
3172 | EXPORT_SYMBOL(proc_dointvec_ms_jiffies); | 3221 | EXPORT_SYMBOL(proc_dointvec_ms_jiffies); |
3173 | EXPORT_SYMBOL(proc_dostring); | 3222 | EXPORT_SYMBOL(proc_dostring); |