]> git.hungrycats.org Git - linux/commitdiff
[PATCH] fork() bug invalidates file descriptors
authorGregory Kurz <gkurz@meiosys.com>
Mon, 18 Oct 2004 15:55:24 +0000 (08:55 -0700)
committerLinus Torvalds <torvalds@ppc970.osdl.org>
Mon, 18 Oct 2004 15:55:24 +0000 (08:55 -0700)
Take a process P1 that spawns a thread T (aka.  a clone with CLONE_FILES).
If P1 forks another process P2 (aka.  not a clone) while T is blocked in a
open() that should return file descriptor FD, then FD will be unusable in
P2.  This leads to strange behaviors in the context of P2: close(FD)
returns EBADF, while dup2(a_valid_fd, FD) returns EBUSY and of course FD is
never returned again by any syscall...

testcase:

#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <signal.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <asm/page.h>

#define FIFO "/tmp/bug_fifo"
#define FD   0

/*
 * This program is meant to show that calling fork() while a clone spawned
 * with CLONE_FILES is blocked in open() makes a fd number unusable in the
 * child.
 *
 *
 *     Parent               Clone                Child
 *        |
 *   clone(CLONE_FILES)-

kernel/fork.c

index 6fd57c2d22cc59618458310693b23da85b7f9a92..bd33d8a507d73976dadd0df58b63f98aff14958b 100644 (file)
@@ -762,8 +762,17 @@ static int copy_files(unsigned long clone_flags, struct task_struct * tsk)
 
        for (i = open_files; i != 0; i--) {
                struct file *f = *old_fds++;
-               if (f)
+               if (f) {
                        get_file(f);
+               } else {
+                       /*
+                        * The fd may be claimed in the fd bitmap but not yet
+                        * instantiated in the files array if a sibling thread
+                        * is partway through open().  So make sure that this
+                        * fd is available to the new process.
+                        */
+                       FD_CLR(open_files - i, newf->open_fds);
+               }
                *new_fds++ = f;
        }
        spin_unlock(&oldf->file_lock);