Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 95570905 authored by Matthew Wilcox's avatar Matthew Wilcox Committed by Greg Kroah-Hartman
Browse files

fs: prevent page refcount overflow in pipe_buf_get



commit 15fab63e1e57be9fdb5eec1bbc5916e9825e9acb upstream.

Change pipe_buf_get() to return a bool indicating whether it succeeded
in raising the refcount of the page (if the thing in the pipe is a page).
This removes another mechanism for overflowing the page refcount.  All
callers converted to handle a failure.

Reported-by: default avatarJann Horn <jannh@google.com>
Signed-off-by: default avatarMatthew Wilcox <willy@infradead.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
[bwh: Backported to 4.9: adjust context]
Signed-off-by: default avatarBen Hutchings <ben.hutchings@codethink.co.uk>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 6f3433c4
Loading
Loading
Loading
Loading
+6 −6
Original line number Diff line number Diff line
@@ -1975,10 +1975,8 @@ static ssize_t fuse_dev_splice_write(struct pipe_inode_info *pipe,
		rem += pipe->bufs[(pipe->curbuf + idx) & (pipe->buffers - 1)].len;

	ret = -EINVAL;
	if (rem < len) {
		pipe_unlock(pipe);
		goto out;
	}
	if (rem < len)
		goto out_free;

	rem = len;
	while (rem) {
@@ -1996,7 +1994,9 @@ static ssize_t fuse_dev_splice_write(struct pipe_inode_info *pipe,
			pipe->curbuf = (pipe->curbuf + 1) & (pipe->buffers - 1);
			pipe->nrbufs--;
		} else {
			pipe_buf_get(pipe, ibuf);
			if (!pipe_buf_get(pipe, ibuf))
				goto out_free;

			*obuf = *ibuf;
			obuf->flags &= ~PIPE_BUF_FLAG_GIFT;
			obuf->len = rem;
@@ -2019,11 +2019,11 @@ static ssize_t fuse_dev_splice_write(struct pipe_inode_info *pipe,
	ret = fuse_dev_do_write(fud, &cs, len);

	pipe_lock(pipe);
out_free:
	for (idx = 0; idx < nbuf; idx++)
		pipe_buf_release(pipe, &bufs[idx]);
	pipe_unlock(pipe);

out:
	kfree(bufs);
	return ret;
}
+2 −2
Original line number Diff line number Diff line
@@ -193,9 +193,9 @@ EXPORT_SYMBOL(generic_pipe_buf_steal);
 *	in the tee() system call, when we duplicate the buffers in one
 *	pipe into another.
 */
void generic_pipe_buf_get(struct pipe_inode_info *pipe, struct pipe_buffer *buf)
bool generic_pipe_buf_get(struct pipe_inode_info *pipe, struct pipe_buffer *buf)
{
	get_page(buf->page);
	return try_get_page(buf->page);
}
EXPORT_SYMBOL(generic_pipe_buf_get);

+10 −2
Original line number Diff line number Diff line
@@ -1585,7 +1585,11 @@ static int splice_pipe_to_pipe(struct pipe_inode_info *ipipe,
			 * Get a reference to this pipe buffer,
			 * so we can copy the contents over.
			 */
			pipe_buf_get(ipipe, ibuf);
			if (!pipe_buf_get(ipipe, ibuf)) {
				if (ret == 0)
					ret = -EFAULT;
				break;
			}
			*obuf = *ibuf;

			/*
@@ -1659,7 +1663,11 @@ static int link_pipe(struct pipe_inode_info *ipipe,
		 * Get a reference to this pipe buffer,
		 * so we can copy the contents over.
		 */
		pipe_buf_get(ipipe, ibuf);
		if (!pipe_buf_get(ipipe, ibuf)) {
			if (ret == 0)
				ret = -EFAULT;
			break;
		}

		obuf = opipe->bufs + nbuf;
		*obuf = *ibuf;
+6 −4
Original line number Diff line number Diff line
@@ -107,18 +107,20 @@ struct pipe_buf_operations {
	/*
	 * Get a reference to the pipe buffer.
	 */
	void (*get)(struct pipe_inode_info *, struct pipe_buffer *);
	bool (*get)(struct pipe_inode_info *, struct pipe_buffer *);
};

/**
 * pipe_buf_get - get a reference to a pipe_buffer
 * @pipe:	the pipe that the buffer belongs to
 * @buf:	the buffer to get a reference to
 *
 * Return: %true if the reference was successfully obtained.
 */
static inline void pipe_buf_get(struct pipe_inode_info *pipe,
static inline __must_check bool pipe_buf_get(struct pipe_inode_info *pipe,
				struct pipe_buffer *buf)
{
	buf->ops->get(pipe, buf);
	return buf->ops->get(pipe, buf);
}

/**
@@ -178,7 +180,7 @@ struct pipe_inode_info *alloc_pipe_info(void);
void free_pipe_info(struct pipe_inode_info *);

/* Generic pipe buffer ops functions */
void generic_pipe_buf_get(struct pipe_inode_info *, struct pipe_buffer *);
bool generic_pipe_buf_get(struct pipe_inode_info *, struct pipe_buffer *);
int generic_pipe_buf_confirm(struct pipe_inode_info *, struct pipe_buffer *);
int generic_pipe_buf_steal(struct pipe_inode_info *, struct pipe_buffer *);
void generic_pipe_buf_release(struct pipe_inode_info *, struct pipe_buffer *);
+5 −1
Original line number Diff line number Diff line
@@ -6145,12 +6145,16 @@ static void buffer_pipe_buf_release(struct pipe_inode_info *pipe,
	buf->private = 0;
}

static void buffer_pipe_buf_get(struct pipe_inode_info *pipe,
static bool buffer_pipe_buf_get(struct pipe_inode_info *pipe,
				struct pipe_buffer *buf)
{
	struct buffer_ref *ref = (struct buffer_ref *)buf->private;

	if (ref->ref > INT_MAX/2)
		return false;

	ref->ref++;
	return true;
}

/* Pipe buffer operations for a buffer. */