diff options
author | Denys Vlasenko <vda.linux@googlemail.com> | 2016-08-22 19:54:12 +0200 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2016-08-22 19:54:12 +0200 |
commit | aa3576a29b9619f4e1c1b131f5db53ad2bc2cb00 (patch) | |
tree | 9dba84b07e8dda7f82a8dba84299bab21e6b9fd2 | |
parent | d8e61bbf13d0cf38d477255cfd5dc71c5d51d575 (diff) | |
download | busybox-w32-aa3576a29b9619f4e1c1b131f5db53ad2bc2cb00.tar.gz busybox-w32-aa3576a29b9619f4e1c1b131f5db53ad2bc2cb00.tar.bz2 busybox-w32-aa3576a29b9619f4e1c1b131f5db53ad2bc2cb00.zip |
hush: fix "redirects can close script fd" bug
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r-- | include/libbb.h | 24 | ||||
-rw-r--r-- | shell/hush.c | 201 | ||||
-rw-r--r-- | shell/hush_test/hush-misc/redir_script.right | 1 | ||||
-rwxr-xr-x | shell/hush_test/hush-misc/redir_script.tests | 29 |
4 files changed, 186 insertions, 69 deletions
diff --git a/include/libbb.h b/include/libbb.h index e39021eb1..b2e4299dd 100644 --- a/include/libbb.h +++ b/include/libbb.h | |||
@@ -185,24 +185,32 @@ int klogctl(int type, char *b, int len); | |||
185 | /* Busybox does not use threads, we can speed up stdio. */ | 185 | /* Busybox does not use threads, we can speed up stdio. */ |
186 | #ifdef HAVE_UNLOCKED_STDIO | 186 | #ifdef HAVE_UNLOCKED_STDIO |
187 | # undef getc | 187 | # undef getc |
188 | # define getc(stream) getc_unlocked(stream) | 188 | # define getc(stream) getc_unlocked(stream) |
189 | # undef getchar | 189 | # undef getchar |
190 | # define getchar() getchar_unlocked() | 190 | # define getchar() getchar_unlocked() |
191 | # undef putc | 191 | # undef putc |
192 | # define putc(c, stream) putc_unlocked(c, stream) | 192 | # define putc(c,stream) putc_unlocked(c,stream) |
193 | # undef putchar | 193 | # undef putchar |
194 | # define putchar(c) putchar_unlocked(c) | 194 | # define putchar(c) putchar_unlocked(c) |
195 | # undef fgetc | 195 | # undef fgetc |
196 | # define fgetc(stream) getc_unlocked(stream) | 196 | # define fgetc(stream) getc_unlocked(stream) |
197 | # undef fputc | 197 | # undef fputc |
198 | # define fputc(c, stream) putc_unlocked(c, stream) | 198 | # define fputc(c,stream) putc_unlocked(c,stream) |
199 | #endif | 199 | #endif |
200 | /* Above functions are required by POSIX.1-2008, below ones are extensions */ | 200 | /* Above functions are required by POSIX.1-2008, below ones are extensions */ |
201 | #ifdef HAVE_UNLOCKED_LINE_OPS | 201 | #ifdef HAVE_UNLOCKED_LINE_OPS |
202 | # undef fgets | 202 | # undef fgets |
203 | # define fgets(s, n, stream) fgets_unlocked(s, n, stream) | 203 | # define fgets(s,n,stream) fgets_unlocked(s,n,stream) |
204 | # undef fputs | 204 | # undef fputs |
205 | # define fputs(s, stream) fputs_unlocked(s, stream) | 205 | # define fputs(s,stream) fputs_unlocked(s,stream) |
206 | # undef fflush | ||
207 | # define fflush(stream) fflush_unlocked(stream) | ||
208 | # undef feof | ||
209 | # define feof(stream) feof_unlocked(stream) | ||
210 | # undef ferror | ||
211 | # define ferror(stream) ferror_unlocked(stream) | ||
212 | # undef fileno | ||
213 | # define fileno(stream) fileno_unlocked(stream) | ||
206 | #endif | 214 | #endif |
207 | 215 | ||
208 | 216 | ||
diff --git a/shell/hush.c b/shell/hush.c index 6b0d85039..a8ec54ec0 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
@@ -103,6 +103,9 @@ | |||
103 | #else | 103 | #else |
104 | # define CLEAR_RANDOM_T(rnd) ((void)0) | 104 | # define CLEAR_RANDOM_T(rnd) ((void)0) |
105 | #endif | 105 | #endif |
106 | #ifndef F_DUPFD_CLOEXEC | ||
107 | # define F_DUPFD_CLOEXEC F_DUPFD | ||
108 | #endif | ||
106 | #ifndef PIPE_BUF | 109 | #ifndef PIPE_BUF |
107 | # define PIPE_BUF 4096 /* amount of buffering in a pipe */ | 110 | # define PIPE_BUF 4096 /* amount of buffering in a pipe */ |
108 | #endif | 111 | #endif |
@@ -711,10 +714,10 @@ enum { | |||
711 | }; | 714 | }; |
712 | 715 | ||
713 | 716 | ||
714 | /* Can be extended to handle a FIXME in setup_redirects about not saving script fds */ | ||
715 | struct FILE_list { | 717 | struct FILE_list { |
716 | struct FILE_list *next; | 718 | struct FILE_list *next; |
717 | FILE *fp; | 719 | FILE *fp; |
720 | int fd; | ||
718 | }; | 721 | }; |
719 | 722 | ||
720 | 723 | ||
@@ -809,9 +812,7 @@ struct globals { | |||
809 | unsigned handled_SIGCHLD; | 812 | unsigned handled_SIGCHLD; |
810 | smallint we_have_children; | 813 | smallint we_have_children; |
811 | #endif | 814 | #endif |
812 | #if ENABLE_FEATURE_SH_STANDALONE | ||
813 | struct FILE_list *FILE_list; | 815 | struct FILE_list *FILE_list; |
814 | #endif | ||
815 | /* Which signals have non-DFL handler (even with no traps set)? | 816 | /* Which signals have non-DFL handler (even with no traps set)? |
816 | * Set at the start to: | 817 | * Set at the start to: |
817 | * (SIGQUIT + maybe SPECIAL_INTERACTIVE_SIGS + maybe SPECIAL_JOBSTOP_SIGS) | 818 | * (SIGQUIT + maybe SPECIAL_INTERACTIVE_SIGS + maybe SPECIAL_JOBSTOP_SIGS) |
@@ -1262,35 +1263,34 @@ static void free_strings(char **strings) | |||
1262 | } | 1263 | } |
1263 | 1264 | ||
1264 | 1265 | ||
1266 | static int xdup_and_close(int fd, int F_DUPFD_maybe_CLOEXEC) | ||
1267 | { | ||
1268 | /* We avoid taking stdio fds. Mimicking ash: use fds above 9 */ | ||
1269 | int newfd = fcntl(fd, F_DUPFD_maybe_CLOEXEC, 10); | ||
1270 | if (newfd < 0) { | ||
1271 | /* fd was not open? */ | ||
1272 | if (errno == EBADF) | ||
1273 | return fd; | ||
1274 | xfunc_die(); | ||
1275 | } | ||
1276 | close(fd); | ||
1277 | return newfd; | ||
1278 | } | ||
1279 | |||
1280 | |||
1265 | /* Manipulating the list of open FILEs */ | 1281 | /* Manipulating the list of open FILEs */ |
1266 | static FILE *remember_FILE(FILE *fp) | 1282 | static FILE *remember_FILE(FILE *fp) |
1267 | { | 1283 | { |
1268 | if (fp) { | 1284 | if (fp) { |
1269 | #if ENABLE_FEATURE_SH_STANDALONE | ||
1270 | struct FILE_list *n = xmalloc(sizeof(*n)); | 1285 | struct FILE_list *n = xmalloc(sizeof(*n)); |
1271 | n->fp = fp; | ||
1272 | n->next = G.FILE_list; | 1286 | n->next = G.FILE_list; |
1273 | G.FILE_list = n; | 1287 | G.FILE_list = n; |
1274 | #endif | 1288 | n->fp = fp; |
1275 | close_on_exec_on(fileno(fp)); | 1289 | n->fd = fileno(fp); |
1290 | close_on_exec_on(n->fd); | ||
1276 | } | 1291 | } |
1277 | return fp; | 1292 | return fp; |
1278 | } | 1293 | } |
1279 | #if ENABLE_FEATURE_SH_STANDALONE | ||
1280 | static void close_all_FILE_list(void) | ||
1281 | { | ||
1282 | struct FILE_list *fl = G.FILE_list; | ||
1283 | while (fl) { | ||
1284 | /* fclose would also free FILE object. | ||
1285 | * It is disastrous if we share memory with a vforked parent. | ||
1286 | * I'm not sure we never come here after vfork. | ||
1287 | * Therefore just close fd, nothing more. | ||
1288 | */ | ||
1289 | /*fclose(fl->fp); - unsafe */ | ||
1290 | close(fileno(fl->fp)); | ||
1291 | fl = fl->next; | ||
1292 | } | ||
1293 | } | ||
1294 | static void fclose_and_forget(FILE *fp) | 1294 | static void fclose_and_forget(FILE *fp) |
1295 | { | 1295 | { |
1296 | struct FILE_list **pp = &G.FILE_list; | 1296 | struct FILE_list **pp = &G.FILE_list; |
@@ -1305,8 +1305,46 @@ static void fclose_and_forget(FILE *fp) | |||
1305 | } | 1305 | } |
1306 | fclose(fp); | 1306 | fclose(fp); |
1307 | } | 1307 | } |
1308 | #else | 1308 | static int save_FILEs_on_redirect(int fd) |
1309 | # define fclose_and_forget(fp) fclose(fp); | 1309 | { |
1310 | struct FILE_list *fl = G.FILE_list; | ||
1311 | while (fl) { | ||
1312 | if (fd == fl->fd) { | ||
1313 | /* We use it only on script files, they are all CLOEXEC */ | ||
1314 | fl->fd = xdup_and_close(fd, F_DUPFD_CLOEXEC); | ||
1315 | return 1; | ||
1316 | } | ||
1317 | fl = fl->next; | ||
1318 | } | ||
1319 | return 0; | ||
1320 | } | ||
1321 | static void restore_redirected_FILEs(void) | ||
1322 | { | ||
1323 | struct FILE_list *fl = G.FILE_list; | ||
1324 | while (fl) { | ||
1325 | int should_be = fileno(fl->fp); | ||
1326 | if (fl->fd != should_be) { | ||
1327 | xmove_fd(fl->fd, should_be); | ||
1328 | fl->fd = should_be; | ||
1329 | } | ||
1330 | fl = fl->next; | ||
1331 | } | ||
1332 | } | ||
1333 | #if ENABLE_FEATURE_SH_STANDALONE | ||
1334 | static void close_all_FILE_list(void) | ||
1335 | { | ||
1336 | struct FILE_list *fl = G.FILE_list; | ||
1337 | while (fl) { | ||
1338 | /* fclose would also free FILE object. | ||
1339 | * It is disastrous if we share memory with a vforked parent. | ||
1340 | * I'm not sure we never come here after vfork. | ||
1341 | * Therefore just close fd, nothing more. | ||
1342 | */ | ||
1343 | /*fclose(fl->fp); - unsafe */ | ||
1344 | close(fl->fd); | ||
1345 | fl = fl->next; | ||
1346 | } | ||
1347 | } | ||
1310 | #endif | 1348 | #endif |
1311 | 1349 | ||
1312 | 1350 | ||
@@ -6147,6 +6185,74 @@ static void setup_heredoc(struct redir_struct *redir) | |||
6147 | wait(NULL); /* wait till child has died */ | 6185 | wait(NULL); /* wait till child has died */ |
6148 | } | 6186 | } |
6149 | 6187 | ||
6188 | /* fd: redirect wants this fd to be used (e.g. 3>file). | ||
6189 | * Move all conflicting internally used fds, | ||
6190 | * and remember them so that we can restore them later. | ||
6191 | */ | ||
6192 | static int save_fds_on_redirect(int fd, int squirrel[3]) | ||
6193 | { | ||
6194 | if (squirrel) { | ||
6195 | /* Handle redirects of fds 0,1,2 */ | ||
6196 | |||
6197 | /* If we collide with an already moved stdio fd... */ | ||
6198 | if (fd == squirrel[0]) { | ||
6199 | squirrel[0] = xdup_and_close(squirrel[0], F_DUPFD); | ||
6200 | return 1; | ||
6201 | } | ||
6202 | if (fd == squirrel[1]) { | ||
6203 | squirrel[1] = xdup_and_close(squirrel[1], F_DUPFD); | ||
6204 | return 1; | ||
6205 | } | ||
6206 | if (fd == squirrel[2]) { | ||
6207 | squirrel[2] = xdup_and_close(squirrel[2], F_DUPFD); | ||
6208 | return 1; | ||
6209 | } | ||
6210 | /* If we are about to redirect stdio fd, and did not yet move it... */ | ||
6211 | if (fd <= 2 && squirrel[fd] < 0) { | ||
6212 | /* We avoid taking stdio fds */ | ||
6213 | squirrel[fd] = fcntl(fd, F_DUPFD, 10); | ||
6214 | if (squirrel[fd] < 0 && errno != EBADF) | ||
6215 | xfunc_die(); | ||
6216 | return 0; /* "we did not close fd" */ | ||
6217 | } | ||
6218 | } | ||
6219 | |||
6220 | #if ENABLE_HUSH_INTERACTIVE | ||
6221 | if (fd != 0 && fd == G.interactive_fd) { | ||
6222 | G.interactive_fd = xdup_and_close(G.interactive_fd, F_DUPFD_CLOEXEC); | ||
6223 | return 1; | ||
6224 | } | ||
6225 | #endif | ||
6226 | |||
6227 | /* Are we called from setup_redirects(squirrel==NULL)? Two cases: | ||
6228 | * (1) Redirect in a forked child. No need to save FILEs' fds, | ||
6229 | * we aren't going to use them anymore, ok to trash. | ||
6230 | * (2) "exec 3>FILE". Bummer. We can save FILEs' fds, | ||
6231 | * but how are we doing to use them? | ||
6232 | * "fileno(fd) = new_fd" can't be done. | ||
6233 | */ | ||
6234 | if (!squirrel) | ||
6235 | return 0; | ||
6236 | |||
6237 | return save_FILEs_on_redirect(fd); | ||
6238 | } | ||
6239 | |||
6240 | static void restore_redirects(int squirrel[3]) | ||
6241 | { | ||
6242 | int i, fd; | ||
6243 | for (i = 0; i <= 2; i++) { | ||
6244 | fd = squirrel[i]; | ||
6245 | if (fd != -1) { | ||
6246 | /* We simply die on error */ | ||
6247 | xmove_fd(fd, i); | ||
6248 | } | ||
6249 | } | ||
6250 | |||
6251 | /* Moved G.interactive_fd stays on new fd, not doing anything for it */ | ||
6252 | |||
6253 | restore_redirected_FILEs(); | ||
6254 | } | ||
6255 | |||
6150 | /* squirrel != NULL means we squirrel away copies of stdin, stdout, | 6256 | /* squirrel != NULL means we squirrel away copies of stdin, stdout, |
6151 | * and stderr if they are redirected. */ | 6257 | * and stderr if they are redirected. */ |
6152 | static int setup_redirects(struct command *prog, int squirrel[]) | 6258 | static int setup_redirects(struct command *prog, int squirrel[]) |
@@ -6157,13 +6263,7 @@ static int setup_redirects(struct command *prog, int squirrel[]) | |||
6157 | for (redir = prog->redirects; redir; redir = redir->next) { | 6263 | for (redir = prog->redirects; redir; redir = redir->next) { |
6158 | if (redir->rd_type == REDIRECT_HEREDOC2) { | 6264 | if (redir->rd_type == REDIRECT_HEREDOC2) { |
6159 | /* "rd_fd<<HERE" case */ | 6265 | /* "rd_fd<<HERE" case */ |
6160 | if (redir->rd_fd <= 2 | 6266 | save_fds_on_redirect(redir->rd_fd, squirrel); |
6161 | && squirrel | ||
6162 | && squirrel[redir->rd_fd] < 0 | ||
6163 | ) { | ||
6164 | /* Save old fds 0..2 if redirect uses them */ | ||
6165 | squirrel[redir->rd_fd] = dup(redir->rd_fd); | ||
6166 | } | ||
6167 | /* for REDIRECT_HEREDOC2, rd_filename holds _contents_ | 6267 | /* for REDIRECT_HEREDOC2, rd_filename holds _contents_ |
6168 | * of the heredoc */ | 6268 | * of the heredoc */ |
6169 | debug_printf_parse("set heredoc '%s'\n", | 6269 | debug_printf_parse("set heredoc '%s'\n", |
@@ -6199,47 +6299,26 @@ static int setup_redirects(struct command *prog, int squirrel[]) | |||
6199 | } | 6299 | } |
6200 | 6300 | ||
6201 | if (openfd != redir->rd_fd) { | 6301 | if (openfd != redir->rd_fd) { |
6202 | if (redir->rd_fd <= 2 | 6302 | int closed = save_fds_on_redirect(redir->rd_fd, squirrel); |
6203 | && squirrel | ||
6204 | && squirrel[redir->rd_fd] < 0 | ||
6205 | ) { | ||
6206 | /* Save old fds 0..2 if redirect uses them */ | ||
6207 | //FIXME: script fd's also need this! "sh SCRIPT" and SCRIPT has "echo FOO 3>&-": | ||
6208 | // open("SCRIPT", O_RDONLY) = 3 | ||
6209 | // fcntl(3, F_SETFD, FD_CLOEXEC) = 0 | ||
6210 | // read(3, "echo FOO 3>&-\n....", 4096) = N | ||
6211 | // close(3) = 0 | ||
6212 | // write(1, "FOO\n", 4) = 4 | ||
6213 | // ... | ||
6214 | // read(3, 0x205f8e0, 4096) = -1 EBADF <== BUG | ||
6215 | // | ||
6216 | squirrel[redir->rd_fd] = dup(redir->rd_fd); | ||
6217 | } | ||
6218 | if (openfd == REDIRFD_CLOSE) { | 6303 | if (openfd == REDIRFD_CLOSE) { |
6219 | /* "n>-" means "close me" */ | 6304 | /* "rd_fd >&-" means "close me" */ |
6220 | close(redir->rd_fd); | 6305 | if (!closed) { |
6306 | /* ^^^ optimization: saving may already | ||
6307 | * have closed it. If not... */ | ||
6308 | close(redir->rd_fd); | ||
6309 | } | ||
6221 | } else { | 6310 | } else { |
6222 | xdup2(openfd, redir->rd_fd); | 6311 | xdup2(openfd, redir->rd_fd); |
6223 | if (redir->rd_dup == REDIRFD_TO_FILE) | 6312 | if (redir->rd_dup == REDIRFD_TO_FILE) |
6313 | /* "rd_fd > FILE" */ | ||
6224 | close(openfd); | 6314 | close(openfd); |
6315 | /* else: "rd_fd > rd_dup" */ | ||
6225 | } | 6316 | } |
6226 | } | 6317 | } |
6227 | } | 6318 | } |
6228 | return 0; | 6319 | return 0; |
6229 | } | 6320 | } |
6230 | 6321 | ||
6231 | static void restore_redirects(int squirrel[]) | ||
6232 | { | ||
6233 | int i, fd; | ||
6234 | for (i = 0; i <= 2; i++) { | ||
6235 | fd = squirrel[i]; | ||
6236 | if (fd != -1) { | ||
6237 | /* We simply die on error */ | ||
6238 | xmove_fd(fd, i); | ||
6239 | } | ||
6240 | } | ||
6241 | } | ||
6242 | |||
6243 | static char *find_in_path(const char *arg) | 6322 | static char *find_in_path(const char *arg) |
6244 | { | 6323 | { |
6245 | char *ret = NULL; | 6324 | char *ret = NULL; |
diff --git a/shell/hush_test/hush-misc/redir_script.right b/shell/hush_test/hush-misc/redir_script.right new file mode 100644 index 000000000..6694ed334 --- /dev/null +++ b/shell/hush_test/hush-misc/redir_script.right | |||
@@ -0,0 +1 @@ | |||
Ok: script fd is not closed | |||
diff --git a/shell/hush_test/hush-misc/redir_script.tests b/shell/hush_test/hush-misc/redir_script.tests new file mode 100755 index 000000000..ccc497d7b --- /dev/null +++ b/shell/hush_test/hush-misc/redir_script.tests | |||
@@ -0,0 +1,29 @@ | |||
1 | # Builds a " 3>&- 4>&-" string. | ||
2 | # Note: one of these fds is a directory opened to /proc/self/fd | ||
3 | # for globbing. It is unwanted, but I don't know how to filter it out. | ||
4 | find_fds() { | ||
5 | fds="" | ||
6 | for f in /proc/self/fd/*; do | ||
7 | test "$f" = "/proc/self/fd/0" && continue | ||
8 | test "$f" = "/proc/self/fd/1" && continue | ||
9 | test "$f" = "/proc/self/fd/2" && continue | ||
10 | fds="$fds ${f##*/}>&-" | ||
11 | done | ||
12 | } | ||
13 | |||
14 | find_fds | ||
15 | fds1="$fds" | ||
16 | |||
17 | # One of the fds is open to the script body | ||
18 | # Close it while executing something. | ||
19 | eval "find_fds $fds" | ||
20 | |||
21 | # Shell should not lose that fd. Did it? | ||
22 | find_fds | ||
23 | test x"$fds1" = x"$fds" && { echo "Ok: script fd is not closed"; exit 0; } | ||
24 | |||
25 | echo "Bug: script fd is closed" | ||
26 | echo "fds1:$fds1" | ||
27 | echo "fds2:$fds" | ||
28 | exit 1 | ||
29 | |||