summaryrefslogtreecommitdiff
path: root/shell
diff options
context:
space:
mode:
authorDenys Vlasenko <vda.linux@googlemail.com>2025-08-11 23:18:01 +0200
committerDenys Vlasenko <vda.linux@googlemail.com>2025-08-11 23:18:01 +0200
commitab1de7df999c43f12be26d90e6a143ecfa966630 (patch)
treebcedd224d7401bd55efa9f25de7c72abf70d07c3 /shell
parent1cd53c15a2b370487c7951345efff3b43f228d1d (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>
Diffstat (limited to 'shell')
-rw-r--r--shell/hush.c101
-rw-r--r--shell/hush_test/hush-parsing/bad_cmd1.right1
-rwxr-xr-xshell/hush_test/hush-parsing/bad_cmd1.tests1
-rw-r--r--shell/hush_test/hush-parsing/bad_cmd2.right1
-rwxr-xr-xshell/hush_test/hush-parsing/bad_cmd2.tests1
-rw-r--r--shell/hush_test/hush-parsing/bad_cmd3.right1
-rwxr-xr-xshell/hush_test/hush-parsing/bad_cmd3.tests1
-rw-r--r--shell/hush_test/hush-parsing/bad_pipe1.right1
-rwxr-xr-xshell/hush_test/hush-parsing/bad_pipe1.tests1
-rw-r--r--shell/hush_test/hush-parsing/bad_pipe2.right1
-rwxr-xr-xshell/hush_test/hush-parsing/bad_pipe2.tests1
-rw-r--r--shell/hush_test/hush-parsing/bad_pipe3.right1
-rwxr-xr-xshell/hush_test/hush-parsing/bad_pipe3.tests1
-rw-r--r--shell/hush_test/hush-parsing/bad_pipe4.right1
-rwxr-xr-xshell/hush_test/hush-parsing/bad_pipe4.tests1
-rw-r--r--shell/hush_test/hush-parsing/bad_pipe5.right1
-rwxr-xr-xshell/hush_test/hush-parsing/bad_pipe5.tests1
-rw-r--r--shell/hush_test/hush-parsing/bad_pipe6.right1
-rwxr-xr-xshell/hush_test/hush-parsing/bad_pipe6.tests1
-rw-r--r--shell/hush_test/hush-parsing/bad_pipe7.right1
-rwxr-xr-xshell/hush_test/hush-parsing/bad_pipe7.tests1
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