/* Copyright (C) 2009 J.F.Dockes * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* * Close all file descriptors above a given value. * * A Unix execXX() call used to execute another program does not close open * file descriptors by default. * * The only descriptors closed are those on which the FD_CLOEXEC flag was * set. FD_CLOEXEC is not easily usable on files opened by external * libraries. * * There are many reasons for closing file descriptors before * an exec (security, pipe control, the possibility that a bug will trigger * an unwanted write, etc.) * * A process has currently no POSIX way to determine the set of open file * descriptors or at least the highest value. Closing all files (except a few), * thus implies performing a close() system call on each entry up to the * maximum, which can be both relatively difficult to determine, and quite * high (ie: several thousands), incurring a non-negligible cost. * * A number of systems have non-portable support for mitigating or solving * this problem. * * This module supplies a portable interface to this functionality. * * The initial data on system interfaces was obtained from: * http://stackoverflow.com/questions/899038/\ * getting-the-highest-allocated-file-descriptor * * System interfaces: * FreeBSD: * - Has a closefrom() system call as of release 7.x around Sep 2009 * - Has a /dev/fd, directory which shows the current process' open * descriptors. Only descriptors 0, 1, 2 are shown except if * fdescfs is mounted which it is not by default * * Interface: * int libclf_closefrom(fd) * @param fd All open file descriptors with equal or higher numeric * values will be closed. fd needs not be a valid descriptor. * @return 0 for success, -1 for error. */ #ifndef TEST_CLOSEFROM #include #include #include #include #include /* #define DEBUG_CLOSEFROM*/ #ifdef DEBUG_CLOSEFROM #define DPRINT(X) fprintf X #else #define DPRINT(X) #endif /* Note: sudo has a closefrom implementation, needs a lot of autoconfig, but * we could use it instead. It's quite close to this though */ /*************************************************************************/ /* closefrom() exists on Solaris, netbsd and openbsd, but someone will * have to provide me the appropriate macro to test */ #if (defined(__FreeBSD__) && __FreeBSD_version >= 702104) /* Use closefrom() system call */ int libclf_closefrom(int fd0) { DPRINT((stderr, "libclf_closefrom: using closefrom(2)\n")); closefrom(fd0); return 0; } /*************************************************************************/ #elif defined(F_CLOSEM) /* Use fcntl(fd, F_CLOSEM) */ int libclf_closefrom(int fd0) { DPRINT((stderr, "libclf_closefrom: using fcntl(F_CLOSEM)\n")); // We need a valid descriptor for this to work. Try to dup stdin, else // go wild if (fcntl(0, F_GETFL) != -1) { if (fd0 != 0) dup2(0, fd0); } else { int fd = open("/etc/group", 0); // yes i am a unix man if (fd >= 0 && fd != fd0) { dup2(fd, fd0); close(fd); } } return fcntl(fd0, F_CLOSEM, 0); } /*************************************************************************/ #elif (defined(linux) || defined(__linux)) /* Use /proc/self/fd directory */ #include #include int libclf_closefrom(int fd0) { DIR *dirp; struct dirent *ent; DPRINT((stderr, "libclf_closefrom: using /proc\n")); dirp = opendir("/proc/self/fd"); if (dirp == 0) return -1; while ((ent = readdir(dirp)) != 0) { int fd; if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) continue; if (sscanf(ent->d_name, "%d", &fd) == 1 && fd >= fd0 && fd != dirfd(dirp)) { close(fd); } } closedir(dirp); return 0; } /*************************************************************************/ #else /* System has no native support for this functionality whatsoever. * * Close all descriptors up to compiled/configured maximum. * The caller will usually have an idea of a reasonable maximum, else * we retrieve a value from the system. */ static int closefrom_maxfd = -1; void libclf_setmaxfd(int max) { closefrom_maxfd = max; } #ifdef sun #include #endif int libclf_closefrom(int fd0) { int i, maxfd = closefrom_maxfd; if (maxfd < 0) { #ifdef _SC_OPEN_MAX maxfd = sysconf(_SC_OPEN_MAX); DPRINT((stderr, "Maxfd is %d after sysconf()\n", maxfd)); #else maxfd = getdtablesize(); DPRINT((stderr, "Maxfd is %d after getdtablesize()\n", maxfd)); #endif } if (maxfd < 0) maxfd = OPEN_MAX; DPRINT((stderr, "libclf_closefrom: using loop to %d\n", maxfd)); for (i = fd0; i < maxfd; i++) { (void)close(i); } return 0; } #endif #else /* TEST_CLOSEFROM */ #include #include #include #include #include "closefrom.h" int main(int argc, char **argv) { int i; int fd0 = open("/etc/group", 0); if (fd0 < 0) { perror("open /etc/group"); exit(1); } if (dup2(fd0, 11) < 0) { perror("dup2->11"); exit(1); } if (dup2(fd0, 19) < 0) { perror("dup2->19"); exit(1); } if (dup2(fd0, 99)< 0) { perror("dup2->99 (ok)"); } if (dup2(fd0, 999) < 0) { perror("dup3->999 (ok)"); } libclf_closefrom(11); for (i = 0; i < 10000; i++) { if (fcntl(i, F_GETFL) != -1) { fprintf(stderr, "Descriptor %d is still open", i); if (i < 11) fprintf(stderr, " (OK)\n"); else fprintf(stderr, " (BAD)\n"); } } exit(0); } #endif