From: Chris Friesen on
On 04/19/2010 03:50 PM, David Given wrote:

> Consider a
> hypothetical terminal emulator running under a multithreaded UI library
> such as GTK.
>
> When the UI library starts up, it's going to create background threads
> to do work --- it says clearly in the docs that it's going to do this,
> so this isn't a problem. But once the UI library has started, the main
> program now cannot safely call forkpty() and exec() to start the child
> process, because one of those background threads might open a file
> descriptor at the wrong time and get it propagated to the child.
>
> The least bad way I know of dealing with this is to use one of the
> aforesaid foul hacks to close unwanted file descriptors in the child
> after it's forked, before exec() is called. But these are non-portable
> and not necessarily very reliable, as we've already seen...

In a situation where the library is the framework for the app and is
itself multithreaded, the cleanest solution would probably be for the
library to provide a function for the app to call that would fork off a
clean process with no leakage.

Realistically, only the library has the knowledge of what is required to
do this in a completely safe way.

I'm no GTK programmer, but a couple minutes of poking around found the
g_spawn_* interfaces which provide a supported way to create a new
process. The default behaviour is to close all descriptors except
stdin/stdout/stderr before calling exec(), but this can be overridden if
you want them left open.

Chris
From: David Schwartz on
On Apr 19, 12:08 pm, David Given <d...(a)cowlark.com> wrote:

> My understanding of the problem is that *any* call to fopen(), in a
> multithreaded application, may result in a leaked file descriptor
> (because even if the caller to fopen() remembers to call
> fcntl(fileno(fp), F_SETFD, FD_CLOEXEC) afterwards, there's still a
> window whereby another thread calling fork() might propagate the file
> descriptor).
>
> Is this correct?
>
> If so, is there any actual solution? Because fopen() is not going to go
> away.

Install appropriate at fork handlers. Before the fork, acquire a lock.
After the fork, release the lock. Call 'fopen' while holding the lock.

For me, the practice is simply not to use 'fopen'. There have
historically been too many problems with it, and even though most of
those problems are solved now, for portability it's (IMO) best just
not to use it. Bluntly, it's an ugly wrapper around 'open'.

DS
From: Ersek, Laszlo on
On Mon, 19 Apr 2010, David Given wrote:

> On 19/04/10 20:39, Rainer Weikusat wrote:
> [...]
>> Yes. The actual solution is still (and will remain forever): DO NOT DO
>> THIS. This cannot be that complicated, can it?
>
> So, basically, you're saying, don't use fopen()? In any multithreaded
> apps? And in any *library* which might be used by a multithreaded app?

s/fopen/fork, I guess.


> We all know that the default for close-on-exec is wrong; are there any
> *useful* strategies for dealing with it in a real-world environment?

Reimplement open(), forward request to actual (or rather, next, as in
RTLD_NEXT) open(), but add O_CLOEXEC. If you're lucky, fopen() / freopen()
/ whatever will go through open().

$ cat cloexec.c

#define _GNU_SOURCE

#include <stdarg.h>
#include <fcntl.h>
#include <dlfcn.h>

static int (*real_open)(const char *, int, ...);

int
open(const char *path, int oflag, ...)
{
int ret;

if (oflag & O_CREAT) {
va_list ap;

va_start(ap, oflag);
ret = (*real_open)(path, oflag | O_CLOEXEC, va_arg(ap, mode_t));
va_end(ap);
}
else {
ret = (*real_open)(path, oflag | O_CLOEXEC);
}

return ret;
}

static void init(void) __attribute__((constructor));

static void
init(void)
{
*(void **)&real_open = dlsym(RTLD_NEXT, "open");
}


$ gcc -fPIC -shared -o cloexec.so -Wall -Wextra cloexec.c -ldl

$ strace touch testfile 2>&1 | grep O_WRONLY
open("testfile", O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK, 0666) = 3

$ LD_PRELOAD=./cloexec.so strace touch testfile 2>&1 | grep O_WRONLY
open("testfile", O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK|O_CLOEXEC, 0666) = 3


Now do something similar with socket():

https://bugzilla.redhat.com/show_bug.cgi?id=443321#c10
http://www.kernel.org/doc/man-pages/online/pages/man2/socket.2.html


Call pipe2() instead of pipe():

http://www.kernel.org/doc/man-pages/online/pages/man2/pipe2.2.html


etc etc

Cheers,
lacos
From: William Ahern on
David Given <dg(a)cowlark.com> wrote:
> On 19/04/10 20:39, Rainer Weikusat wrote:
> [...]
> > Yes. The actual solution is still (and will remain forever): DO NOT DO
> > THIS. This cannot be that complicated, can it?

> So, basically, you're saying, don't use fopen()? In any multithreaded
> apps? And in any *library* which might be used by a multithreaded app?
> Which these days, given how popular multithreaded environments like
> GNOME, web servers, and in fact any non-trivial application are, is
> pretty much all code?

The usual rule of thumb is not to mix threading with fork/exec. In fact, I
can't think of any scenario where I would want to use fork or exec after
starting some threads.

Yes, it can be done, and implementions make it work, but obviously there are
unresolved--perhaps unresolvable--caveats. This is an artifact of the fact
that threads came along late in the history of Unix. Until very recently
processes have always been the dominate parallelism paradigm, with simple
concurrency handled by poll() and non-blocking I/O. This is in contrast to
Windows, which is heavily multithreaded for both parallelism and
concurrency; in fact, Windows had threads before it even had robust
protected memory processes, IIRC.

As regards any library that open descriptors and/or starts threads when it
loads--that's a patently broken library in my opinion, especially if it
doesn't provide any way to release those resources explicitly.

From: Ersek, Laszlo on
On Tue, 20 Apr 2010, David Given wrote:

> On 20/04/10 01:15, Ersek, Laszlo wrote:

>> Now do something similar with socket():

>> Call pipe2() instead of pipe():

> Ah, but it's not me that's calling socket() or pipe() --- it's a
> third-party library that's not under my control. And setting the CLOEXEC
> flag for socket() as described still has a (rare, but potential) race
> condition between socket() returning and the call to fcntl() immediately
> afterward.

No, I meant, "write a similar wrapper for pipe(), which diverts all calls
to pipe2(), and write a similar wrapper for socket(), which delegates all
calls to the 'next' socket() with SOCK_CLOEXEC or'd in". Basically, you'd
have to "override" all functions that return new file descriptors. Some
would be implemented by or-ing in a flag and delegating to the overridden
function, others would be implemented by calling differently named
functions. For the latter ones, you wouldn't need to call dlsym(RTLD_NEXT,
"...") in the constructor function.

Of course if the lib used pipe2() itself, then you'd have to override even
that.

Cheers,
lacos
First  |  Prev  |  Next  |  Last
Pages: 1 2 3 4 5 6 7 8 9 10
Prev: Kill process tree, again
Next: ANN: Seed7 Release 2010-04-18