pipe(2) - race-free error recovery
authorAl Viro <viro@zeniv.linux.org.uk>
Sun, 19 Aug 2012 16:17:29 +0000 (12:17 -0400)
committerAl Viro <viro@zeniv.linux.org.uk>
Thu, 27 Sep 2012 01:08:52 +0000 (21:08 -0400)
don't mess with sys_close() if copy_to_user() fails; just postpone
fd_install() until we know it hasn't.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/pipe.c

index 8d85d70..bd3479d 100644 (file)
--- a/fs/pipe.c
+++ b/fs/pipe.c
@@ -1064,9 +1064,8 @@ err_inode:
        return err;
 }
 
-int do_pipe_flags(int *fd, int flags)
+static int __do_pipe_flags(int *fd, struct file **files, int flags)
 {
-       struct file *files[2];
        int error;
        int fdw, fdr;
 
@@ -1088,11 +1087,8 @@ int do_pipe_flags(int *fd, int flags)
        fdw = error;
 
        audit_fd_pair(fdr, fdw);
-       fd_install(fdr, files[0]);
-       fd_install(fdw, files[1]);
        fd[0] = fdr;
        fd[1] = fdw;
-
        return 0;
 
  err_fdr:
@@ -1103,21 +1099,38 @@ int do_pipe_flags(int *fd, int flags)
        return error;
 }
 
+int do_pipe_flags(int *fd, int flags)
+{
+       struct file *files[2];
+       int error = __do_pipe_flags(fd, files, flags);
+       if (!error) {
+               fd_install(fd[0], files[0]);
+               fd_install(fd[1], files[1]);
+       }
+       return error;
+}
+
 /*
  * sys_pipe() is the normal C calling standard for creating
  * a pipe. It's not the way Unix traditionally does this, though.
  */
 SYSCALL_DEFINE2(pipe2, int __user *, fildes, int, flags)
 {
+       struct file *files[2];
        int fd[2];
        int error;
 
-       error = do_pipe_flags(fd, flags);
+       error = __do_pipe_flags(fd, files, flags);
        if (!error) {
-               if (copy_to_user(fildes, fd, sizeof(fd))) {
-                       sys_close(fd[0]);
-                       sys_close(fd[1]);
+               if (unlikely(copy_to_user(fildes, fd, sizeof(fd)))) {
+                       fput(files[0]);
+                       fput(files[1]);
+                       put_unused_fd(fd[0]);
+                       put_unused_fd(fd[1]);
                        error = -EFAULT;
+               } else {
+                       fd_install(fd[0], files[0]);
+                       fd_install(fd[1], files[1]);
                }
        }
        return error;