Switch to unified view

a b/src/closefrom.cpp
1
/* Copyright (C) 2009 J.F.Dockes
2
 *   This program is free software; you can redistribute it and/or modify
3
 *   it under the terms of the GNU General Public License as published by
4
 *   the Free Software Foundation; either version 2 of the License, or
5
 *   (at your option) any later version.
6
 *
7
 *   This program is distributed in the hope that it will be useful,
8
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 *   GNU General Public License for more details.
11
 *
12
 *   You should have received a copy of the GNU General Public License
13
 *   along with this program; if not, write to the
14
 *   Free Software Foundation, Inc.,
15
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
16
 */
17
/*
18
 * Close all file descriptors above a given value.
19
 *
20
 * A Unix execXX() call used to execute another program does not close open
21
 * file descriptors by default.
22
 *
23
 * The only descriptors closed are those on which the FD_CLOEXEC flag was
24
 * set. FD_CLOEXEC is not easily usable on files opened by external
25
 * libraries.
26
 *
27
 * There are many reasons for closing file descriptors before
28
 * an exec (security,  pipe control, the possibility that a bug will trigger
29
 * an unwanted write, etc.)
30
 *
31
 * A process has currently no POSIX way to determine the set of open file
32
 * descriptors or at least the highest value. Closing all files (except a few),
33
 * thus implies performing a close() system call on each entry up to the
34
 * maximum, which can be both relatively difficult to determine, and quite
35
 * high (ie: several thousands), incurring a non-negligible cost.
36
 *
37
 * A number of systems have non-portable support for mitigating or solving
38
 * this problem.
39
 *
40
 * This module supplies a portable interface to this functionality.
41
 *
42
 * The initial data on system interfaces was obtained from:
43
 * http://stackoverflow.com/questions/899038/\
44
 *   getting-the-highest-allocated-file-descriptor
45
 *
46
 * System interfaces:
47
 *  FreeBSD:
48
 *   - Has a closefrom() system call as of release 7.x around Sep 2009
49
 *   - Has a /dev/fd, directory which shows the current process' open
50
 *     descriptors. Only descriptors 0, 1, 2 are shown except if
51
 *     fdescfs is mounted which it is not by default
52
 *
53
 * Interface:
54
 * int libclf_closefrom(fd)
55
 *  @param fd All open file descriptors with equal or higher numeric
56
 *       values will be closed. fd needs not be a valid descriptor.
57
 *  @return 0 for success, -1 for error.
58
 */
59
#ifndef TEST_CLOSEFROM
60
#include <stdio.h>
61
#include <unistd.h>
62
#include <fcntl.h>
63
#include <string.h>
64
#include <sys/param.h>
65
66
/* #define DEBUG_CLOSEFROM*/
67
#ifdef DEBUG_CLOSEFROM
68
#define DPRINT(X) fprintf X
69
#else
70
#define DPRINT(X)
71
#endif
72
73
/* Note: sudo has a closefrom implementation, needs a lot of autoconfig, but
74
 * we could use it instead. It's quite close to this though */
75
76
/*************************************************************************/
77
78
/* closefrom() exists on Solaris, netbsd and openbsd, but someone will
79
 * have to provide me the appropriate macro to test */
80
#if (defined(__FreeBSD__) && __FreeBSD_version >= 702104)
81
/* Use closefrom() system call */
82
int libclf_closefrom(int fd0)
83
{
84
    DPRINT((stderr, "libclf_closefrom: using closefrom(2)\n"));
85
    closefrom(fd0);
86
    return 0;
87
}
88
89
/*************************************************************************/
90
#elif defined(F_CLOSEM)
91
92
/* Use fcntl(fd, F_CLOSEM) */
93
94
int libclf_closefrom(int fd0)
95
{
96
    DPRINT((stderr, "libclf_closefrom: using fcntl(F_CLOSEM)\n"));
97
    // We need a valid descriptor for this to work. Try to dup stdin, else
98
    // go wild
99
    if (fcntl(0, F_GETFL) != -1) {
100
        if (fd0 != 0) {
101
            dup2(0, fd0);
102
        }
103
    } else {
104
        int fd = open("/etc/group", 0); // yes i am a unix man
105
        if (fd >= 0 && fd != fd0) {
106
            dup2(fd, fd0);
107
            close(fd);
108
        }
109
    }
110
    return fcntl(fd0, F_CLOSEM, 0);
111
}
112
113
/*************************************************************************/
114
#elif (defined(linux) || defined(__linux))
115
116
/* Use /proc/self/fd directory */
117
#include <sys/types.h>
118
#include <dirent.h>
119
120
int libclf_closefrom(int fd0)
121
{
122
    DIR *dirp;
123
    struct dirent *ent;
124
125
    DPRINT((stderr, "libclf_closefrom: using /proc\n"));
126
    dirp = opendir("/proc/self/fd");
127
    if (dirp == 0) {
128
        return -1;
129
    }
130
131
    while ((ent = readdir(dirp)) != 0) {
132
        int fd;
133
        if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) {
134
            continue;
135
        }
136
137
        if (sscanf(ent->d_name, "%d", &fd) == 1 && fd >= fd0 &&
138
                fd != dirfd(dirp)) {
139
            close(fd);
140
        }
141
    }
142
    closedir(dirp);
143
    return 0;
144
}
145
146
/*************************************************************************/
147
#else
148
/* System has no native support for this functionality whatsoever.
149
 *
150
 * Close all descriptors up to compiled/configured maximum.
151
 * The caller will usually have an idea of a reasonable maximum, else
152
 * we retrieve a value from the system.
153
 */
154
155
static int closefrom_maxfd = -1;
156
157
void libclf_setmaxfd(int max)
158
{
159
    closefrom_maxfd = max;
160
}
161
162
#ifdef sun
163
#include <limits.h>
164
#endif
165
int libclf_closefrom(int fd0)
166
{
167
    int i, maxfd = closefrom_maxfd;
168
169
    if (maxfd < 0) {
170
#ifdef _SC_OPEN_MAX
171
        maxfd = sysconf(_SC_OPEN_MAX);
172
        DPRINT((stderr, "Maxfd is %d after sysconf()\n", maxfd));
173
#else
174
        maxfd = getdtablesize();
175
        DPRINT((stderr, "Maxfd is %d after getdtablesize()\n", maxfd));
176
#endif
177
    }
178
    if (maxfd < 0) {
179
        maxfd = OPEN_MAX;
180
    }
181
182
    DPRINT((stderr, "libclf_closefrom: using loop to %d\n", maxfd));
183
184
    for (i = fd0; i < maxfd; i++) {
185
        (void)close(i);
186
    }
187
    return 0;
188
}
189
#endif
190
191
192
#else /* TEST_CLOSEFROM */
193
194
#include <stdio.h>
195
#include <unistd.h>
196
#include <fcntl.h>
197
#include <stdlib.h>
198
199
#include "closefrom.h"
200
201
int main(int argc, char **argv)
202
{
203
    int i;
204
205
    int fd0 = open("/etc/group", 0);
206
    if (fd0 < 0) {
207
        perror("open /etc/group");
208
        exit(1);
209
    }
210
211
    if (dup2(fd0, 11) < 0) {
212
        perror("dup2->11");
213
        exit(1);
214
    }
215
    if (dup2(fd0, 19) < 0) {
216
        perror("dup2->19");
217
        exit(1);
218
    }
219
    if (dup2(fd0, 99) < 0) {
220
        perror("dup2->99 (ok)");
221
    }
222
    if (dup2(fd0, 999) < 0) {
223
        perror("dup3->999 (ok)");
224
    }
225
226
    libclf_closefrom(11);
227
    for (i = 0; i < 10000; i++) {
228
        if (fcntl(i, F_GETFL) != -1) {
229
            fprintf(stderr, "Descriptor %d is still open", i);
230
            if (i < 11) {
231
                fprintf(stderr, " (OK)\n");
232
            } else {
233
                fprintf(stderr, " (BAD)\n");
234
            }
235
        }
236
    }
237
    exit(0);
238
}
239
240
#endif
241