threads/79887: [patch] freopen() isn't thread-safe
John Baldwin
jhb at freebsd.org
Wed Dec 8 15:39:19 UTC 2010
On Wednesday, December 08, 2010 10:20:13 am John Baldwin wrote:
> The following reply was made to PR threads/79887; it has been noted by GNATS.
>
> From: John Baldwin <jhb at freebsd.org>
> To: David Xu <davidxu at freebsd.org>
> Cc: bug-followup at freebsd.org,
> tejblum at yandex-team.ru
> Subject: Re: threads/79887: [patch] freopen() isn't thread-safe
> Date: Wed, 8 Dec 2010 10:03:34 -0500
>
> On Tuesday, December 07, 2010 9:43:35 pm David Xu wrote:
> > John Baldwin wrote:
> > > David,
> > >
> > > I think the submitter's analysis is correct that the only place that can set
> > > the close function pointer is funopen() and that for that case (and any other
> > > "fake" files), the file descriptor will be -1. If the fd is >= 0, then it
> > > must be a file-descriptor-backed FILE, and relying on dup2() to close the fd
> > > is ok.
> > >
> > > As the manpage notes, the most common usage is to redirect stderr or stdout by
> > > doing 'freopen("/dev/null", "w", stderr)'. The bug allows some other random
> > > code that is calling open() in another thread to have that open() return 2
> > > during the window where fd '2' is closed during freopen(). That other file
> > > descriptor then gets trounced by the dup2() call in freopen() to point to
> > > something else.
> > >
> > > The code likely uses _close() rather than close() directly to be cleaner.
> > > Given that this is stdio, I don't think we are really worried about the
> > > performance impact of one extra wrapper function.
> > >
> > > I think the original patch is most likely correct.
> > >
> >
> > The patch works, I just don't like the design of the
> > (*fp->_close)(fp->_cookie)
> > it seems the patch make freopen bypass it.
> > I think the patch can be committed, but I am busy and have
> > no time to do it by myself.
>
> Actually, the freopen() code honors custom _close() routines earlier when it
> checks for _file being < 0. I do really think this is ok. _close() is not
> public, it is only allowed to be set via funopen(). We also need the dup2()
> change to effectively implement this function's rationale, which is a way to
> redirect stdin, stdout, and stderr.
>
> I will take care of committing this today, with an extra bit of comment.
I hadn't seen Daniel's reply when I wrote this last bit. Here is a slightly
updated version of the patch with an extra comment and a change to use
_close() directly:
Index: freopen.c
===================================================================
--- freopen.c (revision 216266)
+++ freopen.c (working copy)
@@ -150,14 +150,6 @@
/* Get a new descriptor to refer to the new file. */
f = _open(file, oflags, DEFFILEMODE);
- if (f < 0 && isopen) {
- /* If out of fd's close the old one and try again. */
- if (errno == ENFILE || errno == EMFILE) {
- (void) (*fp->_close)(fp->_cookie);
- isopen = 0;
- f = _open(file, oflags, DEFFILEMODE);
- }
- }
sverrno = errno;
finish:
@@ -165,9 +157,11 @@
* Finish closing fp. Even if the open succeeded above, we cannot
* keep fp->_base: it may be the wrong size. This loses the effect
* of any setbuffer calls, but stdio has always done this before.
+ *
+ * Leave the existing file descriptor open until dup2() is called
+ * below to avoid races where a concurrent open() in another thread
+ * could claim the existing descriptor.
*/
- if (isopen)
- (void) (*fp->_close)(fp->_cookie);
if (fp->_flags & __SMBF)
free((char *)fp->_bf._base);
fp->_w = 0;
@@ -186,6 +180,8 @@
memset(&fp->_mbstate, 0, sizeof(mbstate_t));
if (f < 0) { /* did not get it after all */
+ if (isopen)
+ (void) (*fp->_close)(fp->_cookie);
fp->_flags = 0; /* set it free */
FUNLOCKFILE(fp);
errno = sverrno; /* restore in case _close clobbered */
@@ -197,11 +193,12 @@
* to maintain the descriptor. Various C library routines (perror)
* assume stderr is always fd STDERR_FILENO, even if being freopen'd.
*/
- if (wantfd >= 0 && f != wantfd) {
+ if (wantfd >= 0) {
if (_dup2(f, wantfd) >= 0) {
(void)_close(f);
f = wantfd;
- }
+ } else
+ (void)_close(fp->_file);
}
/*
We could use 'wantfd' instead of 'fp->_file' in the last hunk above if folks
feel that is clearer.
--
John Baldwin
More information about the freebsd-threads
mailing list