When close fails with EINTR, it’s impossible to know if the FD is closed or open

Originator:mark
Number:rdar://14999594 Date Originated:2013-09-16
Status:Open Resolved:
Product:Mac OS X Product Version:10.8.4 12E55
Classification:Serious Bug Reproducible:Not Applicable
 
Summary:
On Mac OS X, callers of close generally use close$UNIX2003 (32-bit x86) or close (x86_64). These map to close in 10.8.4 xnu-2050.22.13/bsd/kern/kern_descrip.c. The first thing that does is call pthread_testcancel, which can cause the entire system call to stop and return EINTR, before any actual work has been done. It then calls close_nocancel, which will close the FD before EINTR can be returned, but it appears that EINTR still may be returnable after the FD is actually closed, since callee closef_locked says that it might return anything if closef_finish is called. Considering that the FD may correspond to something other than a file on a local filesystem (for example, a file on a remote NFS/AFP/CIFS filesystem, or a socket), there are many possible ways that EINTR might be returned even after the FD is closed.

From user space, it is thus impossible to know whether close failing with EINTR did so before or after closing the FD.

An application wishing to handle this case correctly is unable to do so. If close is retried when it fails with EINTR, and the FD was actually closed, the retry will either fail with an unrelated error (EBADF), or worse, if the FD was reused (such as by a concurrent operation on another thread), the close will succeed and close an unrelated FD. If close is not retried when it fails with EINTR, and the FD wasn’t actually closed, it will leak.

The standard does not provide clarity. Technically, Mac OS X is standards-conforming, although it is impossible to write a correct application given Mac OS X’ implementation.

http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html:

> If close() is interrupted by a signal that is to be caught, it shall return
> -1 with errno set to [EINTR] and the state of fildes is unspecified.

The Austin Group has acknowledged this as a defect: http://austingroupbugs.net/view.php?id=529 .

An alternative version available on Mac OS X, close$NOCANCEL$UNIX2003 (32-bit x86) and close$NOCANCEL (x86_64) is available. These versions cannot return EINTR until the FD is actually closed. It is thus safe to not retry a close when it returns EINTR with this implementation without worrying that the FD will leak. This matches the behavior on Linux (http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html). Chrome is taking advantage of this (http://crbug.com/269623).

When close fails with EINTR on Mac OS X, the FD should be in a known state: either closed (thus no further action necessary, no FDs leaked) or open (thus requiring a retry to close the FD, guaranteed not to close an unrelated FD).

Notes:
Workaround: use close$NOCANCEL in your application and don’t retry close when it fails with EINTR. https://codereview.chromium.org/23455051/ .

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!