Skip to content

Commit

Permalink
splice: don't merge into linked buffers
Browse files Browse the repository at this point in the history
Before this patch, it was possible for two pipes to affect each other after
data had been transferred between them with tee():

============
$ cat tee_test.c

int main(void) {
  int pipe_a[2];
  if (pipe(pipe_a)) err(1, "pipe");
  int pipe_b[2];
  if (pipe(pipe_b)) err(1, "pipe");
  if (write(pipe_a[1], "abcd", 4) != 4) err(1, "write");
  if (tee(pipe_a[0], pipe_b[1], 2, 0) != 2) err(1, "tee");
  if (write(pipe_b[1], "xx", 2) != 2) err(1, "write");

  char buf[5];
  if (read(pipe_a[0], buf, 4) != 4) err(1, "read");
  buf[4] = 0;
  printf("got back: '%s'\n", buf);
}
$ gcc -o tee_test tee_test.c
$ ./tee_test
got back: 'abxx'
$
============

As suggested by Al Viro, fix it by creating a separate type for
non-mergeable pipe buffers, then changing the types of buffers in
splice_pipe_to_pipe() and link_pipe().

Cc: <[email protected]>
Fixes: 7c77f0b ("splice: implement pipe to pipe splicing")
Fixes: 7052449 ("[PATCH] splice: add support for sys_tee()")
Suggested-by: Al Viro <[email protected]>
Signed-off-by: Jann Horn <[email protected]>
Signed-off-by: Al Viro <[email protected]>
  • Loading branch information
thejh authored and Al Viro committed Feb 1, 2019
1 parent 801e523 commit a0ce2f0
Show file tree
Hide file tree
Showing 3 changed files with 19 additions and 0 deletions.
14 changes: 14 additions & 0 deletions fs/pipe.c
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,14 @@ static const struct pipe_buf_operations anon_pipe_buf_ops = {
.get = generic_pipe_buf_get,
};

static const struct pipe_buf_operations anon_pipe_buf_nomerge_ops = {
.can_merge = 0,
.confirm = generic_pipe_buf_confirm,
.release = anon_pipe_buf_release,
.steal = anon_pipe_buf_steal,
.get = generic_pipe_buf_get,
};

static const struct pipe_buf_operations packet_pipe_buf_ops = {
.can_merge = 0,
.confirm = generic_pipe_buf_confirm,
Expand All @@ -242,6 +250,12 @@ static const struct pipe_buf_operations packet_pipe_buf_ops = {
.get = generic_pipe_buf_get,
};

void pipe_buf_mark_unmergeable(struct pipe_buffer *buf)
{
if (buf->ops == &anon_pipe_buf_ops)
buf->ops = &anon_pipe_buf_nomerge_ops;
}

static ssize_t
pipe_read(struct kiocb *iocb, struct iov_iter *to)
{
Expand Down
4 changes: 4 additions & 0 deletions fs/splice.c
Original file line number Diff line number Diff line change
Expand Up @@ -1597,6 +1597,8 @@ static int splice_pipe_to_pipe(struct pipe_inode_info *ipipe,
*/
obuf->flags &= ~PIPE_BUF_FLAG_GIFT;

pipe_buf_mark_unmergeable(obuf);

obuf->len = len;
opipe->nrbufs++;
ibuf->offset += obuf->len;
Expand Down Expand Up @@ -1671,6 +1673,8 @@ static int link_pipe(struct pipe_inode_info *ipipe,
*/
obuf->flags &= ~PIPE_BUF_FLAG_GIFT;

pipe_buf_mark_unmergeable(obuf);

if (obuf->len > len)
obuf->len = len;

Expand Down
1 change: 1 addition & 0 deletions include/linux/pipe_fs_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ void 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 *);
void pipe_buf_mark_unmergeable(struct pipe_buffer *buf);

extern const struct pipe_buf_operations nosteal_pipe_buf_ops;

Expand Down

0 comments on commit a0ce2f0

Please sign in to comment.