diff options
author | Denys Vlasenko <vda.linux@googlemail.com> | 2016-08-20 15:58:34 +0200 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2016-08-20 15:58:34 +0200 |
commit | 7b25b1c5b2794a499c8ae99db75830a6d564561e (patch) | |
tree | c136ae68fd879d80277eebac0ef7686b749181df | |
parent | 869994cf4f9647fdfb519a1945f8582e71d3df3d (diff) | |
download | busybox-w32-7b25b1c5b2794a499c8ae99db75830a6d564561e.tar.gz busybox-w32-7b25b1c5b2794a499c8ae99db75830a6d564561e.tar.bz2 busybox-w32-7b25b1c5b2794a499c8ae99db75830a6d564561e.zip |
hush: do not leak script fds into NOEXEC children
We set all opened script fds to CLOEXEC, thus making then go away
after fork+exec.
Unfortunately, CLOFORK does not exist. NOEXEC children will still see those fds open.
For one, "ls" applet is NOEXEC. Therefore running "ls -l /proc/self/fd"
in a script from standalone shell shows this:
lrwx------ 1 root root 64 Aug 20 15:17 0 -> /dev/pts/3
lrwx------ 1 root root 64 Aug 20 15:17 1 -> /dev/pts/3
lrwx------ 1 root root 64 Aug 20 15:17 2 -> /dev/pts/3
lr-x------ 1 root root 64 Aug 20 15:17 3 -> /path/to/top/level/script
lr-x------ 1 root root 64 Aug 20 15:17 4 -> /path/to/sourced/SCRIPT1
...
with as many open fds as there are ". SCRIPTn" nest levels.
Fix it by closing these fds after fork (only for NOEXEC children).
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r-- | shell/hush.c | 78 |
1 files changed, 68 insertions, 10 deletions
diff --git a/shell/hush.c b/shell/hush.c index 9b45b312f..6b0d85039 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
@@ -711,6 +711,13 @@ enum { | |||
711 | }; | 711 | }; |
712 | 712 | ||
713 | 713 | ||
714 | /* Can be extended to handle a FIXME in setup_redirects about not saving script fds */ | ||
715 | struct FILE_list { | ||
716 | struct FILE_list *next; | ||
717 | FILE *fp; | ||
718 | }; | ||
719 | |||
720 | |||
714 | /* "Globals" within this file */ | 721 | /* "Globals" within this file */ |
715 | /* Sorted roughly by size (smaller offsets == smaller code) */ | 722 | /* Sorted roughly by size (smaller offsets == smaller code) */ |
716 | struct globals { | 723 | struct globals { |
@@ -802,6 +809,9 @@ struct globals { | |||
802 | unsigned handled_SIGCHLD; | 809 | unsigned handled_SIGCHLD; |
803 | smallint we_have_children; | 810 | smallint we_have_children; |
804 | #endif | 811 | #endif |
812 | #if ENABLE_FEATURE_SH_STANDALONE | ||
813 | struct FILE_list *FILE_list; | ||
814 | #endif | ||
805 | /* Which signals have non-DFL handler (even with no traps set)? | 815 | /* Which signals have non-DFL handler (even with no traps set)? |
806 | * Set at the start to: | 816 | * Set at the start to: |
807 | * (SIGQUIT + maybe SPECIAL_INTERACTIVE_SIGS + maybe SPECIAL_JOBSTOP_SIGS) | 817 | * (SIGQUIT + maybe SPECIAL_INTERACTIVE_SIGS + maybe SPECIAL_JOBSTOP_SIGS) |
@@ -1252,6 +1262,54 @@ static void free_strings(char **strings) | |||
1252 | } | 1262 | } |
1253 | 1263 | ||
1254 | 1264 | ||
1265 | /* Manipulating the list of open FILEs */ | ||
1266 | static FILE *remember_FILE(FILE *fp) | ||
1267 | { | ||
1268 | if (fp) { | ||
1269 | #if ENABLE_FEATURE_SH_STANDALONE | ||
1270 | struct FILE_list *n = xmalloc(sizeof(*n)); | ||
1271 | n->fp = fp; | ||
1272 | n->next = G.FILE_list; | ||
1273 | G.FILE_list = n; | ||
1274 | #endif | ||
1275 | close_on_exec_on(fileno(fp)); | ||
1276 | } | ||
1277 | return fp; | ||
1278 | } | ||
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) | ||
1295 | { | ||
1296 | struct FILE_list **pp = &G.FILE_list; | ||
1297 | while (*pp) { | ||
1298 | struct FILE_list *cur = *pp; | ||
1299 | if (cur->fp == fp) { | ||
1300 | *pp = cur->next; | ||
1301 | free(cur); | ||
1302 | break; | ||
1303 | } | ||
1304 | pp = &cur->next; | ||
1305 | } | ||
1306 | fclose(fp); | ||
1307 | } | ||
1308 | #else | ||
1309 | # define fclose_and_forget(fp) fclose(fp); | ||
1310 | #endif | ||
1311 | |||
1312 | |||
1255 | /* Helpers for setting new $n and restoring them back | 1313 | /* Helpers for setting new $n and restoring them back |
1256 | */ | 1314 | */ |
1257 | typedef struct save_arg_t { | 1315 | typedef struct save_arg_t { |
@@ -5968,8 +6026,7 @@ static FILE *generate_stream_from_string(const char *s, pid_t *pid_p) | |||
5968 | free(to_free); | 6026 | free(to_free); |
5969 | # endif | 6027 | # endif |
5970 | close(channel[1]); | 6028 | close(channel[1]); |
5971 | close_on_exec_on(channel[0]); | 6029 | return remember_FILE(xfdopen_for_read(channel[0])); |
5972 | return xfdopen_for_read(channel[0]); | ||
5973 | } | 6030 | } |
5974 | 6031 | ||
5975 | /* Return code is exit status of the process that is run. */ | 6032 | /* Return code is exit status of the process that is run. */ |
@@ -5998,7 +6055,7 @@ static int process_command_subs(o_string *dest, const char *s) | |||
5998 | } | 6055 | } |
5999 | 6056 | ||
6000 | debug_printf("done reading from `cmd` pipe, closing it\n"); | 6057 | debug_printf("done reading from `cmd` pipe, closing it\n"); |
6001 | fclose(fp); | 6058 | fclose_and_forget(fp); |
6002 | /* We need to extract exitcode. Test case | 6059 | /* We need to extract exitcode. Test case |
6003 | * "true; echo `sleep 1; false` $?" | 6060 | * "true; echo `sleep 1; false` $?" |
6004 | * should print 1 */ | 6061 | * should print 1 */ |
@@ -6584,6 +6641,8 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, | |||
6584 | if (a >= 0) { | 6641 | if (a >= 0) { |
6585 | # if BB_MMU /* see above why on NOMMU it is not allowed */ | 6642 | # if BB_MMU /* see above why on NOMMU it is not allowed */ |
6586 | if (APPLET_IS_NOEXEC(a)) { | 6643 | if (APPLET_IS_NOEXEC(a)) { |
6644 | /* Do not leak open fds from opened script files etc */ | ||
6645 | close_all_FILE_list(); | ||
6587 | debug_printf_exec("running applet '%s'\n", argv[0]); | 6646 | debug_printf_exec("running applet '%s'\n", argv[0]); |
6588 | run_applet_no_and_exit(a, argv); | 6647 | run_applet_no_and_exit(a, argv); |
6589 | } | 6648 | } |
@@ -8086,10 +8145,10 @@ int hush_main(int argc, char **argv) | |||
8086 | debug_printf("sourcing /etc/profile\n"); | 8145 | debug_printf("sourcing /etc/profile\n"); |
8087 | input = fopen_for_read("/etc/profile"); | 8146 | input = fopen_for_read("/etc/profile"); |
8088 | if (input != NULL) { | 8147 | if (input != NULL) { |
8089 | close_on_exec_on(fileno(input)); | 8148 | remember_FILE(input); |
8090 | install_special_sighandlers(); | 8149 | install_special_sighandlers(); |
8091 | parse_and_run_file(input); | 8150 | parse_and_run_file(input); |
8092 | fclose(input); | 8151 | fclose_and_forget(input); |
8093 | } | 8152 | } |
8094 | /* bash: after sourcing /etc/profile, | 8153 | /* bash: after sourcing /etc/profile, |
8095 | * tries to source (in the given order): | 8154 | * tries to source (in the given order): |
@@ -8111,11 +8170,11 @@ int hush_main(int argc, char **argv) | |||
8111 | G.global_argv++; | 8170 | G.global_argv++; |
8112 | debug_printf("running script '%s'\n", G.global_argv[0]); | 8171 | debug_printf("running script '%s'\n", G.global_argv[0]); |
8113 | input = xfopen_for_read(G.global_argv[0]); | 8172 | input = xfopen_for_read(G.global_argv[0]); |
8114 | close_on_exec_on(fileno(input)); | 8173 | remember_FILE(input); |
8115 | install_special_sighandlers(); | 8174 | install_special_sighandlers(); |
8116 | parse_and_run_file(input); | 8175 | parse_and_run_file(input); |
8117 | #if ENABLE_FEATURE_CLEAN_UP | 8176 | #if ENABLE_FEATURE_CLEAN_UP |
8118 | fclose(input); | 8177 | fclose_and_forget(input); |
8119 | #endif | 8178 | #endif |
8120 | goto final_return; | 8179 | goto final_return; |
8121 | } | 8180 | } |
@@ -8983,7 +9042,7 @@ static int FAST_FUNC builtin_source(char **argv) | |||
8983 | if (arg_path) | 9042 | if (arg_path) |
8984 | filename = arg_path; | 9043 | filename = arg_path; |
8985 | } | 9044 | } |
8986 | input = fopen_or_warn(filename, "r"); | 9045 | input = remember_FILE(fopen_or_warn(filename, "r")); |
8987 | free(arg_path); | 9046 | free(arg_path); |
8988 | if (!input) { | 9047 | if (!input) { |
8989 | /* bb_perror_msg("%s", *argv); - done by fopen_or_warn */ | 9048 | /* bb_perror_msg("%s", *argv); - done by fopen_or_warn */ |
@@ -8992,7 +9051,6 @@ static int FAST_FUNC builtin_source(char **argv) | |||
8992 | */ | 9051 | */ |
8993 | return EXIT_FAILURE; | 9052 | return EXIT_FAILURE; |
8994 | } | 9053 | } |
8995 | close_on_exec_on(fileno(input)); | ||
8996 | 9054 | ||
8997 | #if ENABLE_HUSH_FUNCTIONS | 9055 | #if ENABLE_HUSH_FUNCTIONS |
8998 | sv_flg = G.flag_return_in_progress; | 9056 | sv_flg = G.flag_return_in_progress; |
@@ -9003,7 +9061,7 @@ static int FAST_FUNC builtin_source(char **argv) | |||
9003 | save_and_replace_G_args(&sv, argv); | 9061 | save_and_replace_G_args(&sv, argv); |
9004 | 9062 | ||
9005 | parse_and_run_file(input); | 9063 | parse_and_run_file(input); |
9006 | fclose(input); | 9064 | fclose_and_forget(input); |
9007 | 9065 | ||
9008 | if (argv[1]) | 9066 | if (argv[1]) |
9009 | restore_G_args(&sv, argv); | 9067 | restore_G_args(&sv, argv); |