-
Notifications
You must be signed in to change notification settings - Fork 242
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use O_CLOEXEC for exec-safety in MMDB_open #158
Conversation
Otherwise, a program which might concurrently call MMDB_open() and fork()->exec() in different threads of execution cannot prevent the possible leak of a copy of the temporary database filehandle to the new child process.
Thanks @blblack! I think this is a good idea. However, as you can see the Travis build failed. It looks like that's because we specify POSIX.1-2001 specifically and Out of concern for portability, I'm hesitant to bump our POSIX version unless it's very critical. Is the lack of close-on-exec causing particular problems for you? |
Yes, the issue is I have a daemon which (re-)loads maxminddb updates in a separate pthread as they become available (filesystem notification), and then the main thread can also do fork->exec() cycles on user demand for downtime-less restarts. The race window is short, but without atomic close-on-exec at creation time there's no way to avoid the issue here, and the leaked FDs would potentially build over time with every exec()-based restart of the daemon. The only alternative in a pure POSIX.1-2001 world is to use Another alternative would be something like:
Which would get it working right for the OS's that do support O_CLOEXEC without requiring the standards bump. |
I see. Thanks for the explanation! Yeah, I think using the I wonder if there is anything that could be done on the application side. Would something like a global lock before forking or opening the database solve this? Another idea would be to close all open fds you can after fork+exec. Working around above the library is not ideal either of course, but it might be a way out. Another idea might be to look into conditionally using POSIX 2008 here. Or maybe I'm wrong and we don't need to worry about keeping POSIX 2001! I wonder what systems we'd be losing compatibility with. |
Yeah it's a deep and thorny issue, sorry! The "close all fds" approach isn't considered sane for various other reasons in the general case, and the mutex solution between unrelated concerns is dicey to impose on apps as well. The bottom line is that atomic close-on-exec enhancements to various POSIX APIs that create file descriptors is the only way out of this mess, but it has taken a long time for standards to catch up. If you want to burn some time looking down this rabbithole, the general wider-scope issue with all the other fd-creating interfaces is well-documented in http://austingroupbugs.net/view.php?id=411 . Most *nixes have adopted most of the most-important fixups from that link ahead of the standards being published, but there's definitely some questions about portability there since there's been no updated POSIX standard since. FreeBSD has a nice list of the issues here too: https://wiki.freebsd.org/AtomicCloseOnExec Luckily this particular case ( On a pragmatic level for libmaxminddb, if you want to keep to strict POSIX.1-2001, another path might be to offer an interface that takes an already-open fd that the app manages. e.g. |
Thanks for the details! It's definitely an interesting problem. I've been thinking about this more. I like your API addition suggestion, as well as the possibility of conditionally bumping the entire library to defaulting to POSIX 2008 if it's available. However, I think your I missed the opportunity we have from conditionally defining #ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200112L
#endif So if an app defined it to POSIX 2008 prior to including the library, we'd have Then where we go to call int flags = O_RDONLY;
#if _POSIX_C_SOURCE >= 200809L
flags |= O_CLOEXEC;
#endif
int fd = open(mmdb->filename, flags); This means we'd be able to keep defaulting to 2001 (and so avoid mistakenly breaking compatibility), but be able to use 2008 features, like this one, where they're useful. What do you think? |
Seems like a possibly sane approach! I think you'll probably also need to add |
Hmm, yeah. We'd have to do that or something like build the library with Oh, there's #if _POSIX_C_VERSION == 200809L
flags |= O_CLOEXEC;
#endif That way building the library we'll get up to and including 2008 by default, and be able to handle it if implementations give us something else. We'd want to add builds to Travis where we specifically request This does bump us to 2008 in a way, but as long as we keep testing with 2001, we'd still support both. Would you be interested in updating your PR to an alternate approach like this? Otherwise I can do so. |
Yeah, I think your approach above makes sense, the more I read about various related things. To recap for gauranteed clarity:
The source changes are pretty trivial and I'm not sure about other concerns around your travis build list and how you want to do (3) above, and life is busy at the moment. If you want to patch it up yourself and close off this PR I won't be offended :) |
Your recap is exactly right! Okay, I will take a stab at this. Thank you for your help figuring it out! |
I've made a new PR: #163. I'll close this one in favour of it. Thanks again! |
Otherwise, a program which might concurrently call MMDB_open() and
fork()->exec() in different threads of execution cannot prevent
the possible leak of a copy of the temporary database filehandle
to the new child process.