[PATCH] fs/pipe: simplify pipe size handling a bit

From: Vegard Nossum
Date: Fri Aug 12 2016 - 07:08:55 EST


Pipe sizes are converted back and forth between pages and bytes. To make
things a bit simpler we can do most of it in pages only. The exception
is /proc/sys/fs/pipe-max-size which must be in bytes for backwards
compatibility.

There are two related races in the original code when a value is written
to /proc/sys/fs/pipe-max-size:

1) reading /proc/sys/fs/pipe-max-size can yield the non-adjusted value

2) F_SETPIPE_SZ compares the argument to the non-adjusted value

These races are harmless, however, because although somebody can see a
lower value than they should, the value is never lower than what was
written to /proc/sys/fs/pipe-max-size in the first place.

Nevertheless, the patch should fix race #2.

Signed-off-by: Vegard Nossum <vegard.nossum@xxxxxxxxxx>
---
fs/pipe.c | 20 +++++++++++---------
1 file changed, 11 insertions(+), 9 deletions(-)

diff --git a/fs/pipe.c b/fs/pipe.c
index 42ea89f..841f5bd 100644
--- a/fs/pipe.c
+++ b/fs/pipe.c
@@ -32,7 +32,9 @@
* The max size that a non-root user is allowed to grow the pipe. Can
* be set by root in /proc/sys/fs/pipe-max-size
*/
-unsigned int pipe_max_size = 1048576;
+#define PIPE_MAX_PAGES 256
+unsigned int pipe_max_pages = PIPE_MAX_PAGES;
+unsigned int pipe_max_size = PIPE_MAX_PAGES << PAGE_SHIFT;

/*
* Minimum pipe size, as required by POSIX
@@ -1065,12 +1067,12 @@ static long pipe_set_size(struct pipe_inode_info *pipe, unsigned long nr_pages)
* Currently we rely on the pipe array holding a power-of-2 number
* of pages.
*/
-static inline unsigned int round_pipe_size(unsigned int size)
+static inline unsigned int round_pipe_pages(unsigned int size)
{
unsigned long nr_pages;

nr_pages = (size + PAGE_SIZE - 1) >> PAGE_SHIFT;
- return roundup_pow_of_two(nr_pages) << PAGE_SHIFT;
+ return roundup_pow_of_two(nr_pages);
}

/*
@@ -1086,7 +1088,9 @@ int pipe_proc_fn(struct ctl_table *table, int write, void __user *buf,
if (ret < 0 || !write)
return ret;

- pipe_max_size = round_pipe_size(pipe_max_size);
+ /* Slightly racy; a reader may see the non-sounded pipe_max_size */
+ pipe_max_pages = round_pipe_pages(pipe_max_size);
+ pipe_max_size = pipe_max_pages << PAGE_SHIFT;
return ret;
}

@@ -1113,16 +1117,14 @@ long pipe_fcntl(struct file *file, unsigned int cmd, unsigned long arg)

switch (cmd) {
case F_SETPIPE_SZ: {
- unsigned int size, nr_pages;
+ unsigned int nr_pages;

ret = -EINVAL;
if (!arg || arg > INT_MAX)
goto out;

- size = round_pipe_size(arg);
- nr_pages = size >> PAGE_SHIFT;
-
- if (!capable(CAP_SYS_RESOURCE) && size > pipe_max_size) {
+ nr_pages = round_pipe_pages(arg);
+ if (!capable(CAP_SYS_RESOURCE) && nr_pages > pipe_max_pages) {
ret = -EPERM;
goto out;
} else if ((too_many_pipe_buffers_hard(pipe->user) ||
--
1.9.1


--------------040002000500030909040306--