User: Password:
Subscribe / Log in / New account

Fun with file descriptors

Fun with file descriptors

Posted Jun 8, 2007 0:33 UTC (Fri) by bronson (subscriber, #4806)
In reply to: Fun with file descriptors by RobSeace
Parent article: Fun with file descriptors

If file descriptors were safe by default (close-on-exec), then I agree, let glibc scatter FDs everywhere. In my case, though, it's a bit more difficult. I'm writing a test harness that starts as root, does some non-trivial processing (including a lot of forking), then eventually drops perms and execs a potentially hostile, user-supplied executable. (by hostile, I mean the way any executable in ~/bin on a multi-user system is potentially hostile)

Well, I definitely I run through the entire FD space to make sure ALL FDs that I don't know about are closed. If glibc opens an FD to some sensitive resource while I'm running as root, and that FD remains open when I drop perms and exec, that user's executable gets free reign over some potentially sensitive system resource.

I'll admit that I haven't thought about this too deeply (it's just a one-off hack)... Is there any better solution than running through and closing anything I don't know about? I've found the occasional lurking fd (a file leaked earlier in the process, a forgotten syslog, etc) so my solution, while damned ugly, has probably saved me once or twice.

Why oh why can't FDs be safe by default?

(Log in to post comments)

Fun with file descriptors

Posted Jun 8, 2007 4:09 UTC (Fri) by daney (subscriber, #24551) [Link]

> Why oh why can't FDs be safe by default?

Probably because POSIX defines how they work. Being compatible with legacy systems has its price.

Fun with file descriptors

Posted Jun 8, 2007 7:16 UTC (Fri) by quotemstr (subscriber, #45331) [Link]

What about a thread-specific default set of FD flags?

Using something like this, we wouldn't need to modify any existing APIs.

/* Internal glibc function */
A: old_fd_flags = kernel_default_fd_flags(FD_CLOEXEC | FD_RANDFD);
B: event_fd = super_duper_event_polling_mechanism_fd();
C: kernel_default_fd_flags(old_fd_flags);

Since the state is thread-specific, we don't need to worry about cross-thread synchronization. It wouldn't be inherited across exec, fork or clone, since it's intended for purely local options. I can't think of a situation where one would want to create a new thread and atomically give it a default set of FD flags.

It's race-free as well. If a fork happens between A and B, nothing unusual happens; the child process doesn't inherit the thread setting flags. If a fork happens after B, event_fd is closed when the child exec()s.

It's adheres to POSIX as long as the application doesn't touch kernel_default_fd_flags itself, and as long as any libraries restore flags after they're done with them.

Why not add an FD_CLOFORK owhile we're at it? That's a lot closer to what you'd want for a piece of code that allocated an internal file descriptor. Granted, multithreaded programs shouldn't fork except to then exec.

Fun with file descriptors

Posted Jun 8, 2007 10:34 UTC (Fri) by RobSeace (subscriber, #4435) [Link]

I understand your concerns, but as far as I can see, a separate FD space doesn't do anything to help with your problems at all... The FDs would still be there, but merely outside the normal range... (And, if they were in a separate "/proc" location, you might not even know where they were to legitimately be able to close them prior to your exec*()... Unless glibc had diligently and properly set CLOEXEC for them all... But, if it did that anyway, it wouldn't matter if they were in the separate space or not, for your purposes...) Now, the automatic CLOEXEC stuff would help you, and I have no problems with that notion at all... The only thing I find a bit strange is the supposed need for this separate FD space, because there are supposedly apps that don't operate properly when glibc (or whoever) creates an FD of its own, which I find absurd... If such an app really exists, I'd like to know about it, for no other reason than to know whose code to avoid in the future...

Fun with file descriptors

Posted Jun 11, 2007 9:33 UTC (Mon) by nlucas (subscriber, #33793) [Link]

According to the text, the problem is with applications assuming the "lowest available descriptor" guarantee, which seems to be a POSIX thing, so can't be changed in the generic case.

An example where it can be an issue would be for an application to allocate several file descriptors in a loop, mixed with the usual mix of libc functions, and then assume the <max> parameter for select (which, by the way, is a brain-dead parameter) as the first file descriptor plus the number of file descriptors created.

This seems to be a POSIX accepted behaviour, even if the code seems a bit fishy (not very good for later maintenance), so must be supported either by the kernel or by libc.

Fun with file descriptors

Posted Jun 11, 2007 10:32 UTC (Mon) by RobSeace (subscriber, #4435) [Link]

No, the only thing guaranteed is that any particular call that creates a new FD will return the lowest numbered FD currently available... That's it... It doesn't guarantee that nothing else outside the app code will use up any FDs... And, no sane code would EVER make such a brain-dead assumption... Because, any programmer worth a damn KNOWS for a fact that LOTS of library code DOES indeed open up lots of FDs of its own for various uses... So, unless you have complete control over the code, and aren't making any library function calls, you better make NO assumptions over what particular FD number you are going to get assigned at any particular time... You can be guaranteed it's the lowest currently available number, but that doesn't mean a whole lot if you don't know all of the currently open FDs...

For instance, this common behavior is fairly reasonable:

close (0);
open ("/dev/null", O_RDONLY);

Assuming that the open() will get FD# 0... That's reasonable enough, because it's hard to imagine the need for either close() or open() to create a persistent extra FD of its own for some use behind your back, and this sort of behavior has historically always worked... But, if you add any other lib function calls between the close() and the open(), you're just asking for trouble, and you can't be too surprised when it doesn't work anymore... (Plus, if you really wanted to write good code, you'd instead use dup2() or freopen() or something, to guarantee assignment of the desired FD#...)

Fun with file descriptors

Posted Jun 11, 2007 12:29 UTC (Mon) by nlucas (subscriber, #33793) [Link]

Fair enough. To me, even the lowest number fd guarantee is strange, so I though it was some POSIX weirdness (along with other weird behaviours for compatibility sake).

Anyway, even the { close(0);open(...); } is strange, as it doesn't take into account multithreading, so I wouldn't ever do anything like that, even in single threaded applications (I never know when a piece of code that seems trivial will be latter "copy/pasted" to a multithreaded application).

Fun with file descriptors

Posted Jun 14, 2007 7:58 UTC (Thu) by slamb (guest, #1070) [Link]

Avoiding code which is broken if copy'n'pasted into the wrong context is a hopeless job - the best you can do is state your code's assumptions, and whoever adds code (whether pasted or not) must take responsibility for it.

Anyway, I can't really imagine when you'd ever want to replace stdin/stdout/stderr while multiple threads are going, so I don't know why someone would paste this there.

Fun with file descriptors

Posted Sep 15, 2007 6:40 UTC (Sat) by schabi (guest, #14079) [Link]

You wrote:
For instance, this common behavior is fairly reasonable:

close (0);
open ("/dev/null", O_RDONLY);
This one is broken as sonn as threads are involved - typical race condition, another thread could do anything between close(0) and open().

Copyright © 2017, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds