aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenys Vlasenko <vda.linux@googlemail.com>2016-08-22 19:54:12 +0200
committerDenys Vlasenko <vda.linux@googlemail.com>2016-08-22 19:54:12 +0200
commitaa3576a29b9619f4e1c1b131f5db53ad2bc2cb00 (patch)
tree9dba84b07e8dda7f82a8dba84299bab21e6b9fd2
parentd8e61bbf13d0cf38d477255cfd5dc71c5d51d575 (diff)
downloadbusybox-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.h24
-rw-r--r--shell/hush.c201
-rw-r--r--shell/hush_test/hush-misc/redir_script.right1
-rwxr-xr-xshell/hush_test/hush-misc/redir_script.tests29
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 */
715struct FILE_list { 717struct 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
1266static 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 */
1266static FILE *remember_FILE(FILE *fp) 1282static 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
1280static 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}
1294static void fclose_and_forget(FILE *fp) 1294static 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 1308static 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}
1321static 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
1334static 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 */
6192static 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
6240static 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. */
6152static int setup_redirects(struct command *prog, int squirrel[]) 6258static 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
6231static 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
6243static char *find_in_path(const char *arg) 6322static 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.
4find_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
14find_fds
15fds1="$fds"
16
17# One of the fds is open to the script body
18# Close it while executing something.
19eval "find_fds $fds"
20
21# Shell should not lose that fd. Did it?
22find_fds
23test x"$fds1" = x"$fds" && { echo "Ok: script fd is not closed"; exit 0; }
24
25echo "Bug: script fd is closed"
26echo "fds1:$fds1"
27echo "fds2:$fds"
28exit 1
29