diff options
| author | Denys Vlasenko <vda.linux@googlemail.com> | 2025-08-13 00:47:25 +0200 |
|---|---|---|
| committer | Denys Vlasenko <vda.linux@googlemail.com> | 2025-08-13 00:51:44 +0200 |
| commit | 1847fee2d4ee7ce13edd676d3e69b8e11c3d1e7d (patch) | |
| tree | 1ccd6426fe2a1e44b08f83d8da3a05059e6be265 /shell | |
| parent | f161bc628fc58ae9708e8499c93da657998d49f5 (diff) | |
| download | busybox-w32-1847fee2d4ee7ce13edd676d3e69b8e11c3d1e7d.tar.gz busybox-w32-1847fee2d4ee7ce13edd676d3e69b8e11c3d1e7d.tar.bz2 busybox-w32-1847fee2d4ee7ce13edd676d3e69b8e11c3d1e7d.zip | |
hush: fix a corner case in "case" stmt, ctx_dsemicolon is in fact unused
function old new delta
parse_stream 2446 2476 +30
done_word 797 800 +3
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 2/0 up/down: 33/0) Total: 33 bytes
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
Diffstat (limited to 'shell')
| -rw-r--r-- | shell/hush.c | 91 | ||||
| -rw-r--r-- | shell/hush_test/hush-misc/case2.right | 1 | ||||
| -rwxr-xr-x | shell/hush_test/hush-misc/case2.tests | 6 | ||||
| -rw-r--r-- | shell/hush_test/hush-misc/case3.right | 2 | ||||
| -rwxr-xr-x | shell/hush_test/hush-misc/case3.tests | 8 |
5 files changed, 55 insertions, 53 deletions
diff --git a/shell/hush.c b/shell/hush.c index b32d903ae..9ec813df2 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
| @@ -748,9 +748,6 @@ struct parse_context { | |||
| 748 | smallint is_assignment; /* 0:maybe, 1:yes, 2:no, 3:keyword */ | 748 | smallint is_assignment; /* 0:maybe, 1:yes, 2:no, 3:keyword */ |
| 749 | #if HAS_KEYWORDS | 749 | #if HAS_KEYWORDS |
| 750 | smallint ctx_res_w; | 750 | smallint ctx_res_w; |
| 751 | #if ENABLE_HUSH_CASE | ||
| 752 | smallint ctx_dsemicolon; /* ";;" seen */ | ||
| 753 | #endif | ||
| 754 | /* bitmask of FLAG_xxx, for figuring out valid reserved words */ | 751 | /* bitmask of FLAG_xxx, for figuring out valid reserved words */ |
| 755 | int old_flag; | 752 | int old_flag; |
| 756 | /* group we are enclosed in: | 753 | /* group we are enclosed in: |
| @@ -4170,7 +4167,7 @@ static const struct reserved_combo* reserved_word(struct parse_context *ctx) | |||
| 4170 | { | 4167 | { |
| 4171 | # if ENABLE_HUSH_CASE | 4168 | # if ENABLE_HUSH_CASE |
| 4172 | static const struct reserved_combo reserved_match = { | 4169 | static const struct reserved_combo reserved_match = { |
| 4173 | "", RES_MATCH, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_ESAC | 4170 | "", RES_MATCH, NOT_ASSIGNMENT, FLAG_MATCH | FLAG_ESAC |
| 4174 | }; | 4171 | }; |
| 4175 | # endif | 4172 | # endif |
| 4176 | const struct reserved_combo *r; | 4173 | const struct reserved_combo *r; |
| @@ -4266,7 +4263,7 @@ static const struct reserved_combo* reserved_word(struct parse_context *ctx) | |||
| 4266 | * Look at it and update current command: | 4263 | * Look at it and update current command: |
| 4267 | * update current command's argv/cmd_type/etc, fill in redirect name and type, | 4264 | * update current command's argv/cmd_type/etc, fill in redirect name and type, |
| 4268 | * check reserved-ness and assignment-ness, etc... | 4265 | * check reserved-ness and assignment-ness, etc... |
| 4269 | * Normal return is 0. Syntax errors return 1. | 4266 | * Normal return is 0. Syntax errors print error message and return 1. |
| 4270 | * Note: on return, word is reset, but not o_free'd! | 4267 | * Note: on return, word is reset, but not o_free'd! |
| 4271 | */ | 4268 | */ |
| 4272 | static int done_word(struct parse_context *ctx) | 4269 | static int done_word(struct parse_context *ctx) |
| @@ -4324,15 +4321,16 @@ static int done_word(struct parse_context *ctx) | |||
| 4324 | 4321 | ||
| 4325 | #if HAS_KEYWORDS | 4322 | #if HAS_KEYWORDS |
| 4326 | # if ENABLE_HUSH_CASE | 4323 | # if ENABLE_HUSH_CASE |
| 4327 | 4324 | if (ctx->ctx_res_w == RES_MATCH | |
| 4328 | if (ctx->ctx_dsemicolon | ||
| 4329 | && (ctx->word.has_quoted_part | 4325 | && (ctx->word.has_quoted_part |
| 4330 | || strcmp(ctx->word.data, "esac") != 0 | 4326 | || strcmp(ctx->word.data, "esac") != 0 |
| 4331 | ) | 4327 | ) |
| 4332 | ) { /* ";; WORD" but not "... PATTERN) CMD;; esac" */ | 4328 | ) { /* ";; WORD" but not ";; esac" */ |
| 4333 | /* already done when ctx_dsemicolon was set to 1: */ | 4329 | /* Do not match WORD as keyword: |
| 4334 | /* ctx->ctx_res_w = RES_MATCH; */ | 4330 | * the WORD is a case match, can be keyword-like: |
| 4335 | ctx->ctx_dsemicolon = 0; | 4331 | * if) echo got_if;; |
| 4332 | * is allowed. | ||
| 4333 | */ | ||
| 4336 | } else | 4334 | } else |
| 4337 | # endif | 4335 | # endif |
| 4338 | # if defined(CMD_TEST2_SINGLEWORD_NOGLOB) | 4336 | # if defined(CMD_TEST2_SINGLEWORD_NOGLOB) |
| @@ -4345,13 +4343,13 @@ static int done_word(struct parse_context *ctx) | |||
| 4345 | } else | 4343 | } else |
| 4346 | # endif | 4344 | # endif |
| 4347 | if (!command->argv /* if it's the first word of command... */ | 4345 | if (!command->argv /* if it's the first word of command... */ |
| 4348 | && !command->redirects /* no redirects yet... disallows: </dev/null ! true; echo $? */ | 4346 | && !command->redirects /* no redirects yet... disallows: </dev/null if true; then... */ |
| 4349 | # if ENABLE_HUSH_LOOPS | 4347 | # if ENABLE_HUSH_LOOPS |
| 4350 | && ctx->ctx_res_w != RES_FOR /* not after FOR or IN */ | 4348 | && ctx->ctx_res_w != RES_FOR /* not after FOR or IN */ |
| 4351 | && ctx->ctx_res_w != RES_IN | 4349 | && ctx->ctx_res_w != RES_IN |
| 4352 | # endif | 4350 | # endif |
| 4353 | # if ENABLE_HUSH_CASE | 4351 | # if ENABLE_HUSH_CASE |
| 4354 | && ctx->ctx_res_w != RES_CASE /* not after CASE */ | 4352 | && ctx->ctx_res_w != RES_CASE /* not after CASE */ |
| 4355 | # endif | 4353 | # endif |
| 4356 | ) { | 4354 | ) { |
| 4357 | const struct reserved_combo *reserved; | 4355 | const struct reserved_combo *reserved; |
| @@ -5648,7 +5646,7 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5648 | if (ch == EOF) { | 5646 | if (ch == EOF) { |
| 5649 | /* Testcase: eval 'echo Ok\' */ | 5647 | /* Testcase: eval 'echo Ok\' */ |
| 5650 | /* bash-4.3.43 was removing backslash, | 5648 | /* bash-4.3.43 was removing backslash, |
| 5651 | * but 4.4.19 retains it, most other shells too | 5649 | * but 4.4.19 retains it, most other shells retain too |
| 5652 | */ | 5650 | */ |
| 5653 | break; | 5651 | break; |
| 5654 | } | 5652 | } |
| @@ -5749,9 +5747,8 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5749 | } | 5747 | } |
| 5750 | /* ch == last eaten whitespace char */ | 5748 | /* ch == last eaten whitespace char */ |
| 5751 | #endif | 5749 | #endif |
| 5752 | if (done_word(&ctx)) { | 5750 | if (done_word(&ctx)) |
| 5753 | goto parse_error_exitcode1; | 5751 | goto parse_error_exitcode1; |
| 5754 | } | ||
| 5755 | if (ch == '\n') { | 5752 | if (ch == '\n') { |
| 5756 | /* Is this a case when newline is simply ignored? | 5753 | /* Is this a case when newline is simply ignored? |
| 5757 | * Some examples: | 5754 | * Some examples: |
| @@ -5784,10 +5781,10 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5784 | if (pi->num_cmds != 0 /* check #1 */ | 5781 | if (pi->num_cmds != 0 /* check #1 */ |
| 5785 | && pi->followup != PIPE_BG /* check #2 */ | 5782 | && pi->followup != PIPE_BG /* check #2 */ |
| 5786 | ) { | 5783 | ) { |
| 5787 | continue; | 5784 | continue; /* ignore newline */ |
| 5788 | } | 5785 | } |
| 5789 | } | 5786 | } |
| 5790 | /* Treat newline as a command separator. */ | 5787 | /* Treat newline as a command separator */ |
| 5791 | done_pipe(&ctx, PIPE_SEQ); | 5788 | done_pipe(&ctx, PIPE_SEQ); |
| 5792 | debug_printf_heredoc("heredoc_cnt:%d\n", heredoc_cnt); | 5789 | debug_printf_heredoc("heredoc_cnt:%d\n", heredoc_cnt); |
| 5793 | if (heredoc_cnt) { | 5790 | if (heredoc_cnt) { |
| @@ -5805,11 +5802,11 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5805 | 5802 | ||
| 5806 | /* "cmd}" or "cmd }..." without semicolon or &: | 5803 | /* "cmd}" or "cmd }..." without semicolon or &: |
| 5807 | * } is an ordinary char in this case, even inside { cmd; } | 5804 | * } is an ordinary char in this case, even inside { cmd; } |
| 5808 | * Pathological example: { ""}; } should exec "}" cmd | 5805 | * Pathological example: { ""}; } should run "}" command. |
| 5809 | */ | 5806 | */ |
| 5810 | if (ch == '}') { | 5807 | if (ch == '}') { |
| 5811 | if (ctx.word.length != 0 /* word} */ | 5808 | if (ctx.word.length != 0 /* word} */ |
| 5812 | || ctx.word.has_quoted_part /* ""} */ | 5809 | || ctx.word.has_quoted_part /* ""} */ |
| 5813 | ) { | 5810 | ) { |
| 5814 | goto ordinary_char; | 5811 | goto ordinary_char; |
| 5815 | } | 5812 | } |
| @@ -5842,9 +5839,8 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5842 | ) | 5839 | ) |
| 5843 | #endif | 5840 | #endif |
| 5844 | ) { | 5841 | ) { |
| 5845 | if (done_word(&ctx)) { | 5842 | if (done_word(&ctx)) |
| 5846 | goto parse_error_exitcode1; | 5843 | goto parse_error_exitcode1; |
| 5847 | } | ||
| 5848 | if (done_pipe(&ctx, PIPE_SEQ)) { | 5844 | if (done_pipe(&ctx, PIPE_SEQ)) { |
| 5849 | /* Testcase: sh -c 'date|;not_reached' */ | 5845 | /* Testcase: sh -c 'date|;not_reached' */ |
| 5850 | syntax_error_unterm_ch('|'); | 5846 | syntax_error_unterm_ch('|'); |
| @@ -5889,9 +5885,8 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5889 | switch (ch) { | 5885 | switch (ch) { |
| 5890 | case '>': | 5886 | case '>': |
| 5891 | redir_fd = redirect_opt_num(&ctx.word); | 5887 | redir_fd = redirect_opt_num(&ctx.word); |
| 5892 | if (done_word(&ctx)) { | 5888 | if (done_word(&ctx)) |
| 5893 | goto parse_error_exitcode1; | 5889 | goto parse_error_exitcode1; |
| 5894 | } | ||
| 5895 | redir_style = REDIRECT_OVERWRITE; | 5890 | redir_style = REDIRECT_OVERWRITE; |
| 5896 | if (next == '>') { | 5891 | if (next == '>') { |
| 5897 | redir_style = REDIRECT_APPEND; | 5892 | redir_style = REDIRECT_APPEND; |
| @@ -5909,9 +5904,8 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5909 | continue; /* get next char */ | 5904 | continue; /* get next char */ |
| 5910 | case '<': | 5905 | case '<': |
| 5911 | redir_fd = redirect_opt_num(&ctx.word); | 5906 | redir_fd = redirect_opt_num(&ctx.word); |
| 5912 | if (done_word(&ctx)) { | 5907 | if (done_word(&ctx)) |
| 5913 | goto parse_error_exitcode1; | 5908 | goto parse_error_exitcode1; |
| 5914 | } | ||
| 5915 | redir_style = REDIRECT_INPUT; | 5909 | redir_style = REDIRECT_INPUT; |
| 5916 | if (next == '<') { | 5910 | if (next == '<') { |
| 5917 | redir_style = REDIRECT_HEREDOC; | 5911 | redir_style = REDIRECT_HEREDOC; |
| @@ -5951,7 +5945,7 @@ static struct pipe *parse_stream(char **pstring, | |||
| 5951 | } | 5945 | } |
| 5952 | ch = i_getch(input); | 5946 | ch = i_getch(input); |
| 5953 | if (ch == EOF) | 5947 | if (ch == EOF) |
| 5954 | break; | 5948 | goto eof; |
| 5955 | } | 5949 | } |
| 5956 | continue; /* get next char */ | 5950 | continue; /* get next char */ |
| 5957 | } | 5951 | } |
| @@ -6026,27 +6020,17 @@ static struct pipe *parse_stream(char **pstring, | |||
| 6026 | } | 6020 | } |
| 6027 | #endif | 6021 | #endif |
| 6028 | case ';': | 6022 | case ';': |
| 6029 | #if ENABLE_HUSH_CASE | 6023 | if (done_word(&ctx)) |
| 6030 | case_semi: | ||
| 6031 | #endif | ||
| 6032 | if (done_word(&ctx)) { | ||
| 6033 | goto parse_error_exitcode1; | 6024 | goto parse_error_exitcode1; |
| 6034 | } | ||
| 6035 | done_pipe(&ctx, PIPE_SEQ); | 6025 | done_pipe(&ctx, PIPE_SEQ); |
| 6036 | #if ENABLE_HUSH_CASE | 6026 | #if ENABLE_HUSH_CASE |
| 6037 | /* Eat multiple semicolons, detect | 6027 | if (ctx.ctx_res_w == RES_CASE_BODY |
| 6038 | * whether it means something special */ | 6028 | && !is_blank /* is it really actual semicolon? */ |
| 6039 | while (1) { | 6029 | && i_peek_and_eat_bkslash_nl(input) == ';' /* and next char is ';' too? */ |
| 6040 | ch = i_peek_and_eat_bkslash_nl(input); | 6030 | ) { |
| 6041 | if (ch != ';') | ||
| 6042 | break; | ||
| 6043 | ch = i_getch(input); | 6031 | ch = i_getch(input); |
| 6044 | nommu_addchr(&ctx.as_string, ch); | 6032 | nommu_addchr(&ctx.as_string, ch); |
| 6045 | if (ctx.ctx_res_w == RES_CASE_BODY) { | 6033 | ctx.ctx_res_w = RES_MATCH; /* "we are in PATTERN)" */ |
| 6046 | ctx.ctx_dsemicolon = 1; | ||
| 6047 | ctx.ctx_res_w = RES_MATCH; | ||
| 6048 | break; | ||
| 6049 | } | ||
| 6050 | } | 6034 | } |
| 6051 | #endif | 6035 | #endif |
| 6052 | new_cmd: | 6036 | new_cmd: |
| @@ -6056,9 +6040,8 @@ static struct pipe *parse_stream(char **pstring, | |||
| 6056 | debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); | 6040 | debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); |
| 6057 | continue; /* get next char */ | 6041 | continue; /* get next char */ |
| 6058 | case '&': | 6042 | case '&': |
| 6059 | if (done_word(&ctx)) { | 6043 | if (done_word(&ctx)) |
| 6060 | goto parse_error_exitcode1; | 6044 | goto parse_error_exitcode1; |
| 6061 | } | ||
| 6062 | if (ctx.pipe->num_cmds == 0 && IS_NULL_CMD(ctx.command)) { | 6045 | if (ctx.pipe->num_cmds == 0 && IS_NULL_CMD(ctx.command)) { |
| 6063 | /* Testcase: sh -c '&& date' */ | 6046 | /* Testcase: sh -c '&& date' */ |
| 6064 | /* Testcase: sh -c '&' */ | 6047 | /* Testcase: sh -c '&' */ |
| @@ -6082,9 +6065,8 @@ static struct pipe *parse_stream(char **pstring, | |||
| 6082 | } | 6065 | } |
| 6083 | goto new_cmd; | 6066 | goto new_cmd; |
| 6084 | case '|': | 6067 | case '|': |
| 6085 | if (done_word(&ctx)) { | 6068 | if (done_word(&ctx)) |
| 6086 | goto parse_error_exitcode1; | 6069 | goto parse_error_exitcode1; |
| 6087 | } | ||
| 6088 | #if ENABLE_HUSH_CASE | 6070 | #if ENABLE_HUSH_CASE |
| 6089 | if (ctx.ctx_res_w == RES_MATCH) | 6071 | if (ctx.ctx_res_w == RES_MATCH) |
| 6090 | break; /* we are in case's "word | word)" */ | 6072 | break; /* we are in case's "word | word)" */ |
| @@ -6135,8 +6117,12 @@ static struct pipe *parse_stream(char **pstring, | |||
| 6135 | } | 6117 | } |
| 6136 | case ')': | 6118 | case ')': |
| 6137 | #if ENABLE_HUSH_CASE | 6119 | #if ENABLE_HUSH_CASE |
| 6138 | if (ctx.ctx_res_w == RES_MATCH) | 6120 | if (ctx.ctx_res_w == RES_MATCH) { |
| 6139 | goto case_semi; | 6121 | if (done_word(&ctx)) |
| 6122 | goto parse_error_exitcode1; | ||
| 6123 | done_pipe(&ctx, PIPE_SEQ); | ||
| 6124 | goto new_cmd; | ||
| 6125 | } | ||
| 6140 | #endif | 6126 | #endif |
| 6141 | case '}': | 6127 | case '}': |
| 6142 | /* proper use of this character is caught by end_trigger: | 6128 | /* proper use of this character is caught by end_trigger: |
| @@ -6150,7 +6136,7 @@ static struct pipe *parse_stream(char **pstring, | |||
| 6150 | bb_error_msg_and_die("BUG: unexpected %c", ch); | 6136 | bb_error_msg_and_die("BUG: unexpected %c", ch); |
| 6151 | } | 6137 | } |
| 6152 | } /* while (1) */ | 6138 | } /* while (1) */ |
| 6153 | 6139 | eof: | |
| 6154 | /* Reached EOF */ | 6140 | /* Reached EOF */ |
| 6155 | if (heredoc_cnt) { | 6141 | if (heredoc_cnt) { |
| 6156 | syntax_error_unterm_str("here document"); | 6142 | syntax_error_unterm_str("here document"); |
| @@ -6165,9 +6151,8 @@ static struct pipe *parse_stream(char **pstring, | |||
| 6165 | goto parse_error_exitcode1; | 6151 | goto parse_error_exitcode1; |
| 6166 | } | 6152 | } |
| 6167 | 6153 | ||
| 6168 | if (done_word(&ctx)) { | 6154 | if (done_word(&ctx)) |
| 6169 | goto parse_error_exitcode1; | 6155 | goto parse_error_exitcode1; |
| 6170 | } | ||
| 6171 | o_free_and_set_NULL(&ctx.word); | 6156 | o_free_and_set_NULL(&ctx.word); |
| 6172 | if (done_pipe(&ctx, PIPE_SEQ)) { | 6157 | if (done_pipe(&ctx, PIPE_SEQ)) { |
| 6173 | /* Testcase: sh -c 'date |' */ | 6158 | /* Testcase: sh -c 'date |' */ |
diff --git a/shell/hush_test/hush-misc/case2.right b/shell/hush_test/hush-misc/case2.right new file mode 100644 index 000000000..4780199de --- /dev/null +++ b/shell/hush_test/hush-misc/case2.right | |||
| @@ -0,0 +1 @@ | |||
| hush: syntax error: unexpected ) | |||
diff --git a/shell/hush_test/hush-misc/case2.tests b/shell/hush_test/hush-misc/case2.tests new file mode 100755 index 000000000..b9475392e --- /dev/null +++ b/shell/hush_test/hush-misc/case2.tests | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | # had a parser bug which incorrectly detected ";;" | ||
| 2 | case w in | ||
| 3 | a); | ||
| 4 | b);; | ||
| 5 | esac | ||
| 6 | echo Should not be reached | ||
diff --git a/shell/hush_test/hush-misc/case3.right b/shell/hush_test/hush-misc/case3.right new file mode 100644 index 000000000..02a29ccbc --- /dev/null +++ b/shell/hush_test/hush-misc/case3.right | |||
| @@ -0,0 +1,2 @@ | |||
| 1 | Keyword-like word may be in pattern | ||
| 2 | Ok:0 | ||
diff --git a/shell/hush_test/hush-misc/case3.tests b/shell/hush_test/hush-misc/case3.tests new file mode 100755 index 000000000..63b9dfd99 --- /dev/null +++ b/shell/hush_test/hush-misc/case3.tests | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | if=if | ||
| 2 | case if in | ||
| 3 | if) echo "Keyword-like word may be in pattern";; | ||
| 4 | fi) echo "Not reached";; | ||
| 5 | # esac) echo "Not reached";; # not accepted by bash 5.2.15 | ||
| 6 | do) echo "Not reached" | ||
| 7 | esac | ||
| 8 | echo Ok:$? | ||
