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 /shell | |
| 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>
Diffstat (limited to 'shell')
| -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 |
3 files changed, 170 insertions, 61 deletions
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 | |||
