Re: Very bad advice in man 2 dup2

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Rich, Ping!

On Fri, May 30, 2014 at 11:15 AM, Michael Kerrisk (man-pages)
<mtk.manpages@xxxxxxxxx> wrote:
> Hi Rich,
>
> For discussions like this, it really is very important to CC the list,
> so that there's an archived record of the reasons for the change.
> I've concatenated your two mails below.
>
> I agree that the page needs to be fixed; thanks for the report!
> I am mulling over what the best fix is. One proposal below.
>
> On 05/29/2014 11:02 PM, Rich Felker wrote:
>> Hi,
>>
>> The following text appears in the man page for dup2 and dup3:
>>
>>     If newfd was open, any errors that would have been reported at
>>     close(2) time are lost. A careful programmer will not use dup2()
>>     or dup3() without closing newfd first.
>>
>> Such use of close is very bad advice, as it introduces a race
>> condition during which the file descriptor could be re-assigned and
>> subsequently clobbered by dup2/dup3.
>
> Agreed. A more fundamental problem in the man page is that
> it does not mention the atomicity of dup2() (and dup3()),
> and why that is needed to avoid the race condition.
> I'll add some words on that.
>
>> This type of bug can lead to
>> serious data leaks and/or data loss. The whole point of dup2/dup3 is
>> to _atomically_ replace a file descriptor.
>>
>> For the most part there are no meaningful errors which close can
>> return, probably only obscure NFS behavior with bad caching settings
>> which are really not handlable by applications anyway, so I feel it
>> would be best to just drop this text (or find a way to detect such
>> errors without close, perhaps using fsync, and recommend that).
>
> On 05/30/2014 07:23 AM, Rich Felker wrote:
> [...]
>> Here is a proposed alternate text that was recommended to me by a user
>> on Stack Overflow:
>
> It'd be great to add URLs when citing such discussions... With some
> effort, I found
> http://stackoverflow.com/questions/23440216/race-condition-when-using-dup
>
>>     A careful programmer will first dup() the target descriptor, then
>>     use dup2()/dup3() to replace the target descriptor atomically, and
>>     finally close the initially duplicated target descriptor. This
>>     replaces the target descriptor atomically, but also retains a
>>     duplicate for closing so that close-time errors may be checked
>>     for. (In Linux, close() should only be called once, as the
>>     referred to descriptor is always closed, even in case of
>>     errno==EINTR.)
>>
>> I'm not sure this is the best wording, since it suggests doing a lot
>> of work that's likely overkill (and useless in the case where the
>> target descriptor was read-only, for instance). I might balance such
>> text with a warning that it's an error to use dup2 or dup3 when the
>> target descriptor is not known to be open unless you know the code
>> will only be used in single-threaded programs. And I'm a little bit
>> hesitant on the parenthetical text about close() since the behavior
>> it's documenting is contrary to the requirements of the upcoming issue
>> 8 of POSIX,
>
> Again citing the Issue 8 discussion would be helpful. Could
> you tell me where it is? (It could be useful for the change log.)
>
>> and rather irrelevant since EINTR cannot happen in Linux's
>> close() except with custom device drivers anyway.
>
> So, how about something like the following (code not
> compile-tested...):
>
>        If newfd was open, any errors that would have been reported  at
>        close(2) time are lost.  If this is of concern, then—unless the
>        program is single-threaded and does not allocate file  descrip‐
>        tors  in  signal  handlers—the correct approach is not to close
>        newfd before calling dup2(),  because  of  the  race  condition
>        described  above.   Instead,  code something like the following
>        could be used:
>
>            /* Obtain a duplicate of 'newfd' that can subsequently
>               be used to check for close() errors; an EBADF error
>               means that 'newfd' was not open. */
>
>            tmpfd = dup(newfd);
>            if (tmpfd == -1 && errno != EBADF) {
>                /* Handle unexpected dup() error */
>            }
>
>            /* Atomically duplicate 'oldfd' on 'newfd' */
>
>            if (dup2(oldfd, newfd) == -1) {
>                /* Handle dup2() error */
>            }
>
>            /* Now check for close() errors on the file originally
>               referred to by 'newfd' */
>
>            if (tmpfd != -1) {
>                if (close(tmpfd) == -1) {
>                    /* Handle errors from close */
>                }
>            }
>
> Cheers,
>
> Michael
>
>
> --
> Michael Kerrisk
> Linux man-pages maintainer; http://www.kernel.org/doc/man-pages/
> Linux/UNIX System Programming Training: http://man7.org/training/



-- 
Michael Kerrisk
Linux man-pages maintainer; http://www.kernel.org/doc/man-pages/
Linux/UNIX System Programming Training: http://man7.org/training/
--
To unsubscribe from this list: send the line "unsubscribe linux-man" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Kernel Documentation]     [Netdev]     [Linux Ethernet Bridging]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux