使用dup2时的竞争状况 [英] Race condition when using dup2

查看:100
本文介绍了使用dup2时的竞争状况的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

针对dup2系统调用的此联机帮助页说:

EBUSY(仅Linux)在执行下列操作时可能由dup2()或dup3()返回 open(2)和dup()的竞争条件.

它讨论什么种族条件?如果dup2给出EBUSY错误,我应该怎么办?我是否应该像EINTR那样重试?

解决方案

fs/file.c中有一个解释,

当要打开的描述符仍处于打开状态(fd_is_open但不存在于fdtable中)时,

看起来像EBUSY会返回.

编辑(更多信息,希望得到赏金)

为了了解!tofree && fd_is_open(fd, fdt)的发生方式,让我们看看如何打开文件.这里是sys_open的简化版本:

long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
    /* ... irrelevant stuff */
    /* allocate the fd, uses a lock */
    fd = get_unused_fd_flags(flags);
    /* HERE the race condition can arise if another thread calls dup2 on fd */
    /* do the real VFS stuff for this fd, also uses a lock */
    fd_install(fd, f);
    /* ... irrelevant stuff again */
    return fd;
}

基本上发生了两个非常重要的事情:分配了文件描述符,然后才由VFS实际打开它.这两个操作修改了该过程的fdt.它们都使用锁,因此在这两个调用中期望没有什么不好.

为了记住已分配了哪个fdsfdt使用了称为open_fds的位向量.在get_unused_fd_flags()之后,已分配fd,并在open_fds中设置了相应的位. fdt的锁定已被释放,但尚未完成真正的VFS作业.

此时,另一个线程(或在共享fdt的情况下,另一个进程)可以调用dup2,因为锁定已被释放,所以它不会阻塞.如果dup2在此处采用其正常路径,则fd将被替换,但是fd_install对于旧文件仍将运行.因此,Ebusy的检查和返回.

我在fd_install()的注释中找到了有关此比赛状况的其他信息,这证实了我的解释:

/* The VFS is full of places where we drop the files lock between
 * setting the open_fds bitmap and installing the file in the file
 * array.  At any such point, we are vulnerable to a dup2() race
 * installing a file in the array before us.  We need to detect this and
 * fput() the struct file we are about to overwrite in this case.
 *
 * It should never happen - if we allow dup2() do it, _really_ bad things
 * will follow. */

This manpage for the dup2 system call says:

EBUSY (Linux only) This may be returned by dup2() or dup3() during a race condition with open(2) and dup().

What race condition does it talk about and what should I do if dup2 gives EBUSY error? Should I retry like in the case of EINTR?

解决方案

There is an explanation in fs/file.c, do_dup2():

/*
 * We need to detect attempts to do dup2() over allocated but still
 * not finished descriptor.  NB: OpenBSD avoids that at the price of
 * extra work in their equivalent of fget() - they insert struct
 * file immediately after grabbing descriptor, mark it larval if
 * more work (e.g. actual opening) is needed and make sure that
 * fget() treats larval files as absent.  Potentially interesting,
 * but while extra work in fget() is trivial, locking implications
 * and amount of surgery on open()-related paths in VFS are not.
 * FreeBSD fails with -EBADF in the same situation, NetBSD "solution"
 * deadlocks in rather amusing ways, AFAICS.  All of that is out of
 * scope of POSIX or SUS, since neither considers shared descriptor
 * tables and this condition does not arise without those.
 */
fdt = files_fdtable(files);
tofree = fdt->fd[fd];
if (!tofree && fd_is_open(fd, fdt))
    goto Ebusy;

Looks like EBUSY is returned when the descriptor to be freed is in some kind of incomplete state when it's still being opened (fd_is_open but not present in fdtable).

EDIT (more info and do want bounty)

In order to understand how !tofree && fd_is_open(fd, fdt) can happen, let's see how files are opened. Here a simplified version of sys_open :

long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
    /* ... irrelevant stuff */
    /* allocate the fd, uses a lock */
    fd = get_unused_fd_flags(flags);
    /* HERE the race condition can arise if another thread calls dup2 on fd */
    /* do the real VFS stuff for this fd, also uses a lock */
    fd_install(fd, f);
    /* ... irrelevant stuff again */
    return fd;
}

Basically two very important things happen: a file descriptor is allocated and only then it is actually opened by the VFS. These two operations modify the fdt of the process. They both use a lock, so nothing bad is to expect inside those two calls.

In order to memorize which fds have been allocated a bit vector called open_fds is used by the fdt. After get_unused_fd_flags(), the fd has been allocated and the corresponding bit set in open_fds. The lock on the fdt has been released, but the real VFS job hasn't been done yet.

At this precise moment, another thread (or another process in the case of shared fdt) can call dup2 which will not block because the locks have been released. If the dup2 took its normal path here, the fd would be replaced, but fd_install would be still run for the old file. Hence the check and return of Ebusy.

I found additional info on this race condition in the comments of fd_install() which confirms my explanation:

/* The VFS is full of places where we drop the files lock between
 * setting the open_fds bitmap and installing the file in the file
 * array.  At any such point, we are vulnerable to a dup2() race
 * installing a file in the array before us.  We need to detect this and
 * fput() the struct file we are about to overwrite in this case.
 *
 * It should never happen - if we allow dup2() do it, _really_ bad things
 * will follow. */

这篇关于使用dup2时的竞争状况的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆