diff options
| author | Denys Vlasenko <vda.linux@googlemail.com> | 2025-08-11 23:18:01 +0200 |
|---|---|---|
| committer | Denys Vlasenko <vda.linux@googlemail.com> | 2025-08-11 23:18:01 +0200 |
| commit | ab1de7df999c43f12be26d90e6a143ecfa966630 (patch) | |
| tree | bcedd224d7401bd55efa9f25de7c72abf70d07c3 | |
| parent | 1cd53c15a2b370487c7951345efff3b43f228d1d (diff) | |
hush: test for, and disallow several invalid syntaxes
function old new delta
parse_stream 2292 2456 +164
done_pipe 231 252 +21
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 2/0 up/down: 185/0) Total: 185 bytes
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
21 files changed, 96 insertions, 25 deletions
diff --git a/shell/hush.c b/shell/hush.c index 73d2ee7f1..254f39943 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -1445,6 +1445,7 @@ static void xxfree(void *ptr) # define syntax_error_unterm_ch(lineno, ch) syntax_error_unterm_ch(ch) # define syntax_error_unterm_str(lineno, s) syntax_error_unterm_str(s) # define syntax_error_unexpected_ch(lineno, ch) syntax_error_unexpected_ch(ch) +# define syntax_error_unexpected_str(lineno, s) syntax_error_unexpected_str(s) #endif static void die_if_script(void) @@ -1492,24 +1493,27 @@ static void syntax_error_unterm_str(unsigned lineno UNUSED_PARAM, const char *s) // die_if_script(); } -static void syntax_error_unterm_ch(unsigned lineno, char ch) +static void syntax_error_unterm_ch(unsigned lineno, int ch) { char msg[2] = { ch, '\0' }; - syntax_error_unterm_str(lineno, msg); + syntax_error_unterm_str(lineno, ch == EOF ? "EOF" : msg); } -static void syntax_error_unexpected_ch(unsigned lineno UNUSED_PARAM, int ch) +static void syntax_error_unexpected_str(unsigned lineno UNUSED_PARAM, const char *s) { - char msg[2]; - msg[0] = ch; - msg[1] = '\0'; #if HUSH_DEBUG >= 2 bb_error_msg("hush.c:%u", lineno); #endif - bb_error_msg("syntax error: unexpected %s", ch == EOF ? "EOF" : msg); + bb_error_msg("syntax error: unexpected %s", s); die_if_script(); } +static void syntax_error_unexpected_ch(unsigned lineno UNUSED_PARAM, int ch) +{ + char msg[2] = { ch, '\0' }; + syntax_error_unexpected_str(lineno, ch == EOF ? "EOF" : msg); +} + #if HUSH_DEBUG < 2 # undef msg_and_die_if_script # undef syntax_error @@ -1517,6 +1521,7 @@ static void syntax_error_unexpected_ch(unsigned lineno UNUSED_PARAM, int ch) # undef syntax_error_unterm_ch # undef syntax_error_unterm_str # undef syntax_error_unexpected_ch +# undef syntax_error_unexpected_str #else # define msg_and_die_if_script(...) msg_and_die_if_script(__LINE__, __VA_ARGS__) # define syntax_error(msg) syntax_error(__LINE__, msg) @@ -1524,6 +1529,7 @@ static void syntax_error_unexpected_ch(unsigned lineno UNUSED_PARAM, int ch) # define syntax_error_unterm_ch(ch) syntax_error_unterm_ch(__LINE__, ch) # define syntax_error_unterm_str(s) syntax_error_unterm_str(__LINE__, s) # define syntax_error_unexpected_ch(ch) syntax_error_unexpected_ch(__LINE__, ch) +# define syntax_error_unexpected_str(s) syntax_error_unexpected_str(__LINE__, s) #endif /* @@ -3952,13 +3958,24 @@ static int done_command(struct parse_context *ctx) return pi->num_cmds; /* used only for 0/nonzero check */ } -static void done_pipe(struct parse_context *ctx, pipe_style type) +static int done_pipe(struct parse_context *ctx, pipe_style type) { - int not_null; + int not_null_pipe; + int oldnum; + int nullcommand; debug_printf_parse("done_pipe entered, followup %d\n", type); /* Close previous command */ - not_null = done_command(ctx); + oldnum = ctx->pipe->num_cmds; + not_null_pipe = done_command(ctx); + + /* This is true if this was a non-empty pipe, + * but done_command didn't add a new member to it. + * Usually it is a syntax error. + * Examples: "date | | ...", "date | ; ..." + */ + nullcommand = (oldnum && not_null_pipe == oldnum); + #if HAS_KEYWORDS ctx->pipe->pi_inverted = ctx->ctx_inverted; ctx->ctx_inverted = 0; @@ -4000,7 +4017,7 @@ static void done_pipe(struct parse_context *ctx, pipe_style type) ctx->list_head = ctx->pipe = pi; /* for cases like "cmd && &", do not be tricked by last command * being null - the entire {...} & is NOT null! */ - not_null = 1; + not_null_pipe = 1; } else { no_conv: ctx->pipe->followup = type; @@ -4009,7 +4026,7 @@ static void done_pipe(struct parse_context *ctx, pipe_style type) /* Without this check, even just <enter> on command line generates * tree of three NOPs (!). Which is harmless but annoying. * IOW: it is safe to do it unconditionally. */ - if (not_null + if (not_null_pipe #if ENABLE_HUSH_IF || ctx->ctx_res_w == RES_FI #endif @@ -4024,8 +4041,8 @@ static void done_pipe(struct parse_context *ctx, pipe_style type) ) { struct pipe *new_p; debug_printf_parse("done_pipe: adding new pipe: " - "not_null:%d ctx->ctx_res_w:%d\n", - not_null, ctx->ctx_res_w); + "not_null_pipe:%d ctx->ctx_res_w:%d\n", + not_null_pipe, ctx->ctx_res_w); new_p = new_pipe(); ctx->pipe->next = new_p; ctx->pipe = new_p; @@ -4054,7 +4071,8 @@ static void done_pipe(struct parse_context *ctx, pipe_style type) done_command(ctx); //debug_print_tree(ctx->list_head, 10); } - debug_printf_parse("done_pipe return\n"); + debug_printf_parse("done_pipe return:%d\n", nullcommand); + return nullcommand; } static void initialize_context(struct parse_context *ctx) @@ -5612,7 +5630,12 @@ static struct pipe *parse_stream(char **pstring, goto parse_error_exitcode1; } o_free_and_set_NULL(&ctx.word); - done_pipe(&ctx, PIPE_SEQ); + if (done_pipe(&ctx, PIPE_SEQ)) { + /* Testcase: sh -c 'date |' */ + syntax_error_unterm_ch('|'); + goto parse_error_exitcode1; + } +// TODO: catch 'date &&<whitespace><EOF>' and 'date ||<whitespace><EOF>' too /* Do we sit inside of any if's, loops or case's? */ if (HAS_KEYWORDS @@ -5624,8 +5647,6 @@ static struct pipe *parse_stream(char **pstring, pi = ctx.list_head; /* If we got nothing... */ - /* (this makes bare "&" cmd a no-op. - * bash says: "syntax error near unexpected token '&'") */ if (pi->num_cmds == 0 IF_HAS_KEYWORDS(&& pi->res_word == RES_NONE) ) { @@ -5867,7 +5888,11 @@ static struct pipe *parse_stream(char **pstring, if (done_word(&ctx)) { goto parse_error_exitcode1; } - done_pipe(&ctx, PIPE_SEQ); + if (done_pipe(&ctx, PIPE_SEQ)) { + /* Testcase: sh -c 'date|;not_reached' */ + syntax_error_unterm_ch('|'); + goto parse_error_exitcode1; + }; ctx.is_assignment = MAYBE_ASSIGNMENT; debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); /* Do we sit outside of any if's, loops or case's? */ @@ -6077,12 +6102,26 @@ static struct pipe *parse_stream(char **pstring, if (done_word(&ctx)) { goto parse_error_exitcode1; } + if (ctx.pipe->num_cmds == 0 && IS_NULL_CMD(ctx.command)) { + /* Testcase: sh -c '&& date' */ + /* Testcase: sh -c '&' */ + syntax_error_unexpected_str("&&" + (next != '&')); + goto parse_error_exitcode1; + } if (next == '&') { ch = i_getch(input); nommu_addchr(&ctx.as_string, ch); - done_pipe(&ctx, PIPE_AND); + if (done_pipe(&ctx, PIPE_AND)) { + /* Testcase: sh -c 'date | && date' */ + syntax_error_unterm_ch('|'); + goto parse_error_exitcode1; + } } else { - done_pipe(&ctx, PIPE_BG); + if (done_pipe(&ctx, PIPE_BG)) { + /* Testcase: sh -c 'date | &' */ + syntax_error_unterm_ch('|'); + goto parse_error_exitcode1; + } } goto new_cmd; case '|': @@ -6096,11 +6135,23 @@ static struct pipe *parse_stream(char **pstring, if (next == '|') { /* || */ ch = i_getch(input); nommu_addchr(&ctx.as_string, ch); - done_pipe(&ctx, PIPE_OR); + if (ctx.pipe->num_cmds == 0 && IS_NULL_CMD(ctx.command)) { + /* Testcase: sh -c '|| date' */ + syntax_error_unexpected_str("||"); + goto parse_error_exitcode1; + } + if (done_pipe(&ctx, PIPE_OR)) { + /* Testcase: sh -c 'date | || date' */ + syntax_error_unterm_ch('|'); + goto parse_error_exitcode1; + } } else { - /* we could pick up a file descriptor choice here - * with redirect_opt_num(), but bash doesn't do it. - * "echo foo 2| cat" yields "foo 2". */ + if (IS_NULL_CMD(ctx.command)) { + /* Example: "| cat" */ + /* Example: "date | | cat" */ + syntax_error_unexpected_ch('|'); + goto parse_error_exitcode1; + } done_command(&ctx); } goto new_cmd; diff --git a/shell/hush_test/hush-parsing/bad_cmd1.right b/shell/hush_test/hush-parsing/bad_cmd1.right new file mode 100644 index 000000000..ad7f80d96 --- /dev/null +++ b/shell/hush_test/hush-parsing/bad_cmd1.right @@ -0,0 +1 @@ +hush: syntax error: unexpected || diff --git a/shell/hush_test/hush-parsing/bad_cmd1.tests b/shell/hush_test/hush-parsing/bad_cmd1.tests new file mode 100755 index 000000000..b4bf53a75 --- /dev/null +++ b/shell/hush_test/hush-parsing/bad_cmd1.tests @@ -0,0 +1 @@ +||date diff --git a/shell/hush_test/hush-parsing/bad_cmd2.right b/shell/hush_test/hush-parsing/bad_cmd2.right new file mode 100644 index 000000000..9c3ed0a65 --- /dev/null +++ b/shell/hush_test/hush-parsing/bad_cmd2.right @@ -0,0 +1 @@ +hush: syntax error: unexpected && diff --git a/shell/hush_test/hush-parsing/bad_cmd2.tests b/shell/hush_test/hush-parsing/bad_cmd2.tests new file mode 100755 index 000000000..13580e14a --- /dev/null +++ b/shell/hush_test/hush-parsing/bad_cmd2.tests @@ -0,0 +1 @@ +&&date diff --git a/shell/hush_test/hush-parsing/bad_cmd3.right b/shell/hush_test/hush-parsing/bad_cmd3.right new file mode 100644 index 000000000..05618a2d9 --- /dev/null +++ b/shell/hush_test/hush-parsing/bad_cmd3.right @@ -0,0 +1 @@ +hush: syntax error: unexpected & diff --git a/shell/hush_test/hush-parsing/bad_cmd3.tests b/shell/hush_test/hush-parsing/bad_cmd3.tests new file mode 100755 index 000000000..bec7e5180 --- /dev/null +++ b/shell/hush_test/hush-parsing/bad_cmd3.tests @@ -0,0 +1 @@ +& date diff --git a/shell/hush_test/hush-parsing/bad_pipe1.right b/shell/hush_test/hush-parsing/bad_pipe1.right new file mode 100644 index 000000000..c8ce26235 --- /dev/null +++ b/shell/hush_test/hush-parsing/bad_pipe1.right @@ -0,0 +1 @@ +hush: syntax error: unexpected | diff --git a/shell/hush_test/hush-parsing/bad_pipe1.tests b/shell/hush_test/hush-parsing/bad_pipe1.tests new file mode 100755 index 000000000..ca5f2ec48 --- /dev/null +++ b/shell/hush_test/hush-parsing/bad_pipe1.tests @@ -0,0 +1 @@ +|date diff --git a/shell/hush_test/hush-parsing/bad_pipe2.right b/shell/hush_test/hush-parsing/bad_pipe2.right new file mode 100644 index 000000000..6d63482b8 --- /dev/null +++ b/shell/hush_test/hush-parsing/bad_pipe2.right @@ -0,0 +1 @@ +hush: syntax error: unterminated | diff --git a/shell/hush_test/hush-parsing/bad_pipe2.tests b/shell/hush_test/hush-parsing/bad_pipe2.tests new file mode 100755 index 000000000..d0aa5c73c --- /dev/null +++ b/shell/hush_test/hush-parsing/bad_pipe2.tests @@ -0,0 +1 @@ +date| diff --git a/shell/hush_test/hush-parsing/bad_pipe3.right b/shell/hush_test/hush-parsing/bad_pipe3.right new file mode 100644 index 000000000..6d63482b8 --- /dev/null +++ b/shell/hush_test/hush-parsing/bad_pipe3.right @@ -0,0 +1 @@ +hush: syntax error: unterminated | diff --git a/shell/hush_test/hush-parsing/bad_pipe3.tests b/shell/hush_test/hush-parsing/bad_pipe3.tests new file mode 100755 index 000000000..ab312d139 --- /dev/null +++ b/shell/hush_test/hush-parsing/bad_pipe3.tests @@ -0,0 +1 @@ +date|; diff --git a/shell/hush_test/hush-parsing/bad_pipe4.right b/shell/hush_test/hush-parsing/bad_pipe4.right new file mode 100644 index 000000000..6d63482b8 --- /dev/null +++ b/shell/hush_test/hush-parsing/bad_pipe4.right @@ -0,0 +1 @@ +hush: syntax error: unterminated | diff --git a/shell/hush_test/hush-parsing/bad_pipe4.tests b/shell/hush_test/hush-parsing/bad_pipe4.tests new file mode 100755 index 000000000..c39098c8b --- /dev/null +++ b/shell/hush_test/hush-parsing/bad_pipe4.tests @@ -0,0 +1 @@ +date| & diff --git a/shell/hush_test/hush-parsing/bad_pipe5.right b/shell/hush_test/hush-parsing/bad_pipe5.right new file mode 100644 index 000000000..6d63482b8 --- /dev/null +++ b/shell/hush_test/hush-parsing/bad_pipe5.right @@ -0,0 +1 @@ +hush: syntax error: unterminated | diff --git a/shell/hush_test/hush-parsing/bad_pipe5.tests b/shell/hush_test/hush-parsing/bad_pipe5.tests new file mode 100755 index 000000000..d5866ce79 --- /dev/null +++ b/shell/hush_test/hush-parsing/bad_pipe5.tests @@ -0,0 +1 @@ +date| && date diff --git a/shell/hush_test/hush-parsing/bad_pipe6.right b/shell/hush_test/hush-parsing/bad_pipe6.right new file mode 100644 index 000000000..6d63482b8 --- /dev/null +++ b/shell/hush_test/hush-parsing/bad_pipe6.right @@ -0,0 +1 @@ +hush: syntax error: unterminated | diff --git a/shell/hush_test/hush-parsing/bad_pipe6.tests b/shell/hush_test/hush-parsing/bad_pipe6.tests new file mode 100755 index 000000000..1a9476b92 --- /dev/null +++ b/shell/hush_test/hush-parsing/bad_pipe6.tests @@ -0,0 +1 @@ +date| || date diff --git a/shell/hush_test/hush-parsing/bad_pipe7.right b/shell/hush_test/hush-parsing/bad_pipe7.right new file mode 100644 index 000000000..c8ce26235 --- /dev/null +++ b/shell/hush_test/hush-parsing/bad_pipe7.right @@ -0,0 +1 @@ +hush: syntax error: unexpected | diff --git a/shell/hush_test/hush-parsing/bad_pipe7.tests b/shell/hush_test/hush-parsing/bad_pipe7.tests new file mode 100755 index 000000000..b048affb0 --- /dev/null +++ b/shell/hush_test/hush-parsing/bad_pipe7.tests @@ -0,0 +1 @@ +date| |date |
