Skip to content

Commit 5ba97d2

Browse files
edumazetAl Viro
authored andcommitted
fs/file.c: __fget() and dup2() atomicity rules
__fget() does lockless fetch of pointer from the descriptor table, attempts to grab a reference and treats "it was already zero" as "it's already gone from the table, we just hadn't seen the store, let's fail". Unfortunately, that breaks the atomicity of dup2() - __fget() might see the old pointer, notice that it's been already dropped and treat that as "it's closed". What we should be getting is either the old file or new one, depending whether we come before or after dup2(). Dmitry had following test failing sometimes : int fd; void *Thread(void *x) { char buf; int n = read(fd, &buf, 1); if (n != 1) exit(printf("read failed: n=%d errno=%d\n", n, errno)); return 0; } int main() { fd = open("/dev/urandom", O_RDONLY); int fd2 = open("/dev/urandom", O_RDONLY); if (fd == -1 || fd2 == -1) exit(printf("open failed\n")); pthread_t th; pthread_create(&th, 0, Thread, 0); if (dup2(fd2, fd) == -1) exit(printf("dup2 failed\n")); pthread_join(th, 0); if (close(fd) == -1) exit(printf("close failed\n")); if (close(fd2) == -1) exit(printf("close failed\n")); printf("DONE\n"); return 0; } Signed-off-by: Eric Dumazet <[email protected]> Reported-by: Dmitry Vyukov <[email protected]> Signed-off-by: Al Viro <[email protected]>
1 parent 8a81252 commit 5ba97d2

File tree

1 file changed

+8
-2
lines changed

1 file changed

+8
-2
lines changed

fs/file.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -664,11 +664,17 @@ static struct file *__fget(unsigned int fd, fmode_t mask)
664664
struct file *file;
665665

666666
rcu_read_lock();
667+
loop:
667668
file = fcheck_files(files, fd);
668669
if (file) {
669-
/* File object ref couldn't be taken */
670-
if ((file->f_mode & mask) || !get_file_rcu(file))
670+
/* File object ref couldn't be taken.
671+
* dup2() atomicity guarantee is the reason
672+
* we loop to catch the new file (or NULL pointer)
673+
*/
674+
if (file->f_mode & mask)
671675
file = NULL;
676+
else if (!get_file_rcu(file))
677+
goto loop;
672678
}
673679
rcu_read_unlock();
674680

0 commit comments

Comments
 (0)