mirror of
https://github.com/sheumann/hush.git
synced 2025-01-10 16:29:44 +00:00
hush: fix improper handling of newline and hash chars in few corner cases
Signed-off-by: Denys Vlasenko <dvlasenk@redhat.com>
This commit is contained in:
parent
1e23f32453
commit
7b4c0fd5f4
237
shell/hush.c
237
shell/hush.c
@ -543,7 +543,6 @@ struct command {
|
|||||||
#define IS_NULL_CMD(cmd) \
|
#define IS_NULL_CMD(cmd) \
|
||||||
(!(cmd)->group && !(cmd)->argv && !(cmd)->redirects)
|
(!(cmd)->group && !(cmd)->argv && !(cmd)->redirects)
|
||||||
|
|
||||||
|
|
||||||
struct pipe {
|
struct pipe {
|
||||||
struct pipe *next;
|
struct pipe *next;
|
||||||
int num_cmds; /* total number of commands in pipe */
|
int num_cmds; /* total number of commands in pipe */
|
||||||
@ -2622,6 +2621,94 @@ static void free_pipe_list(struct pipe *pi)
|
|||||||
|
|
||||||
/*** Parsing routines ***/
|
/*** Parsing routines ***/
|
||||||
|
|
||||||
|
#ifndef debug_print_tree
|
||||||
|
static void debug_print_tree(struct pipe *pi, int lvl)
|
||||||
|
{
|
||||||
|
static const char *const PIPE[] = {
|
||||||
|
[PIPE_SEQ] = "SEQ",
|
||||||
|
[PIPE_AND] = "AND",
|
||||||
|
[PIPE_OR ] = "OR" ,
|
||||||
|
[PIPE_BG ] = "BG" ,
|
||||||
|
};
|
||||||
|
static const char *RES[] = {
|
||||||
|
[RES_NONE ] = "NONE" ,
|
||||||
|
# if ENABLE_HUSH_IF
|
||||||
|
[RES_IF ] = "IF" ,
|
||||||
|
[RES_THEN ] = "THEN" ,
|
||||||
|
[RES_ELIF ] = "ELIF" ,
|
||||||
|
[RES_ELSE ] = "ELSE" ,
|
||||||
|
[RES_FI ] = "FI" ,
|
||||||
|
# endif
|
||||||
|
# if ENABLE_HUSH_LOOPS
|
||||||
|
[RES_FOR ] = "FOR" ,
|
||||||
|
[RES_WHILE] = "WHILE",
|
||||||
|
[RES_UNTIL] = "UNTIL",
|
||||||
|
[RES_DO ] = "DO" ,
|
||||||
|
[RES_DONE ] = "DONE" ,
|
||||||
|
# endif
|
||||||
|
# if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE
|
||||||
|
[RES_IN ] = "IN" ,
|
||||||
|
# endif
|
||||||
|
# if ENABLE_HUSH_CASE
|
||||||
|
[RES_CASE ] = "CASE" ,
|
||||||
|
[RES_CASE_IN ] = "CASE_IN" ,
|
||||||
|
[RES_MATCH] = "MATCH",
|
||||||
|
[RES_CASE_BODY] = "CASE_BODY",
|
||||||
|
[RES_ESAC ] = "ESAC" ,
|
||||||
|
# endif
|
||||||
|
[RES_XXXX ] = "XXXX" ,
|
||||||
|
[RES_SNTX ] = "SNTX" ,
|
||||||
|
};
|
||||||
|
static const char *const CMDTYPE[] = {
|
||||||
|
"{}",
|
||||||
|
"()",
|
||||||
|
"[noglob]",
|
||||||
|
# if ENABLE_HUSH_FUNCTIONS
|
||||||
|
"func()",
|
||||||
|
# endif
|
||||||
|
};
|
||||||
|
|
||||||
|
int pin, prn;
|
||||||
|
|
||||||
|
pin = 0;
|
||||||
|
while (pi) {
|
||||||
|
fprintf(stderr, "%*spipe %d res_word=%s followup=%d %s\n", lvl*2, "",
|
||||||
|
pin, RES[pi->res_word], pi->followup, PIPE[pi->followup]);
|
||||||
|
prn = 0;
|
||||||
|
while (prn < pi->num_cmds) {
|
||||||
|
struct command *command = &pi->cmds[prn];
|
||||||
|
char **argv = command->argv;
|
||||||
|
|
||||||
|
fprintf(stderr, "%*s cmd %d assignment_cnt:%d",
|
||||||
|
lvl*2, "", prn,
|
||||||
|
command->assignment_cnt);
|
||||||
|
if (command->group) {
|
||||||
|
fprintf(stderr, " group %s: (argv=%p)%s%s\n",
|
||||||
|
CMDTYPE[command->cmd_type],
|
||||||
|
argv
|
||||||
|
# if !BB_MMU
|
||||||
|
, " group_as_string:", command->group_as_string
|
||||||
|
# else
|
||||||
|
, "", ""
|
||||||
|
# endif
|
||||||
|
);
|
||||||
|
debug_print_tree(command->group, lvl+1);
|
||||||
|
prn++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (argv) while (*argv) {
|
||||||
|
fprintf(stderr, " '%s'", *argv);
|
||||||
|
argv++;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
prn++;
|
||||||
|
}
|
||||||
|
pi = pi->next;
|
||||||
|
pin++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif /* debug_print_tree */
|
||||||
|
|
||||||
static struct pipe *new_pipe(void)
|
static struct pipe *new_pipe(void)
|
||||||
{
|
{
|
||||||
struct pipe *pi;
|
struct pipe *pi;
|
||||||
@ -4011,15 +4098,16 @@ static struct pipe *parse_stream(char **pstring,
|
|||||||
goto parse_error;
|
goto parse_error;
|
||||||
}
|
}
|
||||||
if (ch == '\n') {
|
if (ch == '\n') {
|
||||||
#if ENABLE_HUSH_CASE
|
/* Is this a case when newline is simply ignored?
|
||||||
/* "case ... in <newline> word) ..." -
|
* Some examples:
|
||||||
* newlines are ignored (but ';' wouldn't be) */
|
* "cmd | <newline> cmd ..."
|
||||||
if (ctx.command->argv == NULL
|
* "case ... in <newline> word) ..."
|
||||||
&& ctx.ctx_res_w == RES_MATCH
|
*/
|
||||||
|
if (IS_NULL_CMD(ctx.command)
|
||||||
|
&& dest.length == 0 && !dest.has_quoted_part
|
||||||
) {
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
/* Treat newline as a command separator. */
|
/* Treat newline as a command separator. */
|
||||||
done_pipe(&ctx, PIPE_SEQ);
|
done_pipe(&ctx, PIPE_SEQ);
|
||||||
debug_printf_parse("heredoc_cnt:%d\n", heredoc_cnt);
|
debug_printf_parse("heredoc_cnt:%d\n", heredoc_cnt);
|
||||||
@ -4151,6 +4239,31 @@ static struct pipe *parse_stream(char **pstring,
|
|||||||
if (parse_redirect(&ctx, redir_fd, redir_style, input))
|
if (parse_redirect(&ctx, redir_fd, redir_style, input))
|
||||||
goto parse_error;
|
goto parse_error;
|
||||||
continue; /* back to top of while (1) */
|
continue; /* back to top of while (1) */
|
||||||
|
case '#':
|
||||||
|
if (dest.length == 0 && !dest.has_quoted_part) {
|
||||||
|
/* skip "#comment" */
|
||||||
|
while (1) {
|
||||||
|
ch = i_peek(input);
|
||||||
|
if (ch == EOF || ch == '\n')
|
||||||
|
break;
|
||||||
|
i_getch(input);
|
||||||
|
/* note: we do not add it to &ctx.as_string */
|
||||||
|
}
|
||||||
|
nommu_addchr(&ctx.as_string, '\n');
|
||||||
|
continue; /* back to top of while (1) */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
if (next == '\n') {
|
||||||
|
/* It's "\<newline>" */
|
||||||
|
#if !BB_MMU
|
||||||
|
/* Remove trailing '\' from ctx.as_string */
|
||||||
|
ctx.as_string.data[--ctx.as_string.length] = '\0';
|
||||||
|
#endif
|
||||||
|
ch = i_getch(input); /* eat it */
|
||||||
|
continue; /* back to top of while (1) */
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dest.o_assignment == MAYBE_ASSIGNMENT
|
if (dest.o_assignment == MAYBE_ASSIGNMENT
|
||||||
@ -4165,19 +4278,8 @@ static struct pipe *parse_stream(char **pstring,
|
|||||||
/* Note: nommu_addchr(&ctx.as_string, ch) is already done */
|
/* Note: nommu_addchr(&ctx.as_string, ch) is already done */
|
||||||
|
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
case '#':
|
case '#': /* non-comment #: "echo a#b" etc */
|
||||||
if (dest.length == 0) {
|
|
||||||
while (1) {
|
|
||||||
ch = i_peek(input);
|
|
||||||
if (ch == EOF || ch == '\n')
|
|
||||||
break;
|
|
||||||
i_getch(input);
|
|
||||||
/* note: we do not add it to &ctx.as_string */
|
|
||||||
}
|
|
||||||
nommu_addchr(&ctx.as_string, '\n');
|
|
||||||
} else {
|
|
||||||
o_addQchr(&dest, ch);
|
o_addQchr(&dest, ch);
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case '\\':
|
case '\\':
|
||||||
if (next == EOF) {
|
if (next == EOF) {
|
||||||
@ -4185,7 +4287,7 @@ static struct pipe *parse_stream(char **pstring,
|
|||||||
xfunc_die();
|
xfunc_die();
|
||||||
}
|
}
|
||||||
ch = i_getch(input);
|
ch = i_getch(input);
|
||||||
if (ch != '\n') {
|
/* note: ch != '\n' (that case does not reach this place) */
|
||||||
o_addchr(&dest, '\\');
|
o_addchr(&dest, '\\');
|
||||||
/*nommu_addchr(&ctx.as_string, '\\'); - already done */
|
/*nommu_addchr(&ctx.as_string, '\\'); - already done */
|
||||||
o_addchr(&dest, ch);
|
o_addchr(&dest, ch);
|
||||||
@ -4193,13 +4295,6 @@ static struct pipe *parse_stream(char **pstring,
|
|||||||
/* Example: echo Hello \2>file
|
/* Example: echo Hello \2>file
|
||||||
* we need to know that word 2 is quoted */
|
* we need to know that word 2 is quoted */
|
||||||
dest.has_quoted_part = 1;
|
dest.has_quoted_part = 1;
|
||||||
}
|
|
||||||
#if !BB_MMU
|
|
||||||
else {
|
|
||||||
/* It's "\<newline>". Remove trailing '\' from ctx.as_string */
|
|
||||||
ctx.as_string.data[--ctx.as_string.length] = '\0';
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
break;
|
break;
|
||||||
case '$':
|
case '$':
|
||||||
if (parse_dollar(&ctx.as_string, &dest, input, /*quote_mask:*/ 0) != 0) {
|
if (parse_dollar(&ctx.as_string, &dest, input, /*quote_mask:*/ 0) != 0) {
|
||||||
@ -6869,94 +6964,6 @@ static NOINLINE int run_pipe(struct pipe *pi)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef debug_print_tree
|
|
||||||
static void debug_print_tree(struct pipe *pi, int lvl)
|
|
||||||
{
|
|
||||||
static const char *const PIPE[] = {
|
|
||||||
[PIPE_SEQ] = "SEQ",
|
|
||||||
[PIPE_AND] = "AND",
|
|
||||||
[PIPE_OR ] = "OR" ,
|
|
||||||
[PIPE_BG ] = "BG" ,
|
|
||||||
};
|
|
||||||
static const char *RES[] = {
|
|
||||||
[RES_NONE ] = "NONE" ,
|
|
||||||
# if ENABLE_HUSH_IF
|
|
||||||
[RES_IF ] = "IF" ,
|
|
||||||
[RES_THEN ] = "THEN" ,
|
|
||||||
[RES_ELIF ] = "ELIF" ,
|
|
||||||
[RES_ELSE ] = "ELSE" ,
|
|
||||||
[RES_FI ] = "FI" ,
|
|
||||||
# endif
|
|
||||||
# if ENABLE_HUSH_LOOPS
|
|
||||||
[RES_FOR ] = "FOR" ,
|
|
||||||
[RES_WHILE] = "WHILE",
|
|
||||||
[RES_UNTIL] = "UNTIL",
|
|
||||||
[RES_DO ] = "DO" ,
|
|
||||||
[RES_DONE ] = "DONE" ,
|
|
||||||
# endif
|
|
||||||
# if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE
|
|
||||||
[RES_IN ] = "IN" ,
|
|
||||||
# endif
|
|
||||||
# if ENABLE_HUSH_CASE
|
|
||||||
[RES_CASE ] = "CASE" ,
|
|
||||||
[RES_CASE_IN ] = "CASE_IN" ,
|
|
||||||
[RES_MATCH] = "MATCH",
|
|
||||||
[RES_CASE_BODY] = "CASE_BODY",
|
|
||||||
[RES_ESAC ] = "ESAC" ,
|
|
||||||
# endif
|
|
||||||
[RES_XXXX ] = "XXXX" ,
|
|
||||||
[RES_SNTX ] = "SNTX" ,
|
|
||||||
};
|
|
||||||
static const char *const CMDTYPE[] = {
|
|
||||||
"{}",
|
|
||||||
"()",
|
|
||||||
"[noglob]",
|
|
||||||
# if ENABLE_HUSH_FUNCTIONS
|
|
||||||
"func()",
|
|
||||||
# endif
|
|
||||||
};
|
|
||||||
|
|
||||||
int pin, prn;
|
|
||||||
|
|
||||||
pin = 0;
|
|
||||||
while (pi) {
|
|
||||||
fprintf(stderr, "%*spipe %d res_word=%s followup=%d %s\n", lvl*2, "",
|
|
||||||
pin, RES[pi->res_word], pi->followup, PIPE[pi->followup]);
|
|
||||||
prn = 0;
|
|
||||||
while (prn < pi->num_cmds) {
|
|
||||||
struct command *command = &pi->cmds[prn];
|
|
||||||
char **argv = command->argv;
|
|
||||||
|
|
||||||
fprintf(stderr, "%*s cmd %d assignment_cnt:%d",
|
|
||||||
lvl*2, "", prn,
|
|
||||||
command->assignment_cnt);
|
|
||||||
if (command->group) {
|
|
||||||
fprintf(stderr, " group %s: (argv=%p)%s%s\n",
|
|
||||||
CMDTYPE[command->cmd_type],
|
|
||||||
argv
|
|
||||||
# if !BB_MMU
|
|
||||||
, " group_as_string:", command->group_as_string
|
|
||||||
# else
|
|
||||||
, "", ""
|
|
||||||
# endif
|
|
||||||
);
|
|
||||||
debug_print_tree(command->group, lvl+1);
|
|
||||||
prn++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (argv) while (*argv) {
|
|
||||||
fprintf(stderr, " '%s'", *argv);
|
|
||||||
argv++;
|
|
||||||
}
|
|
||||||
fprintf(stderr, "\n");
|
|
||||||
prn++;
|
|
||||||
}
|
|
||||||
pi = pi->next;
|
|
||||||
pin++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif /* debug_print_tree */
|
|
||||||
|
|
||||||
/* NB: called by pseudo_exec, and therefore must not modify any
|
/* NB: called by pseudo_exec, and therefore must not modify any
|
||||||
* global data until exec/_exit (we can be a child after vfork!) */
|
* global data until exec/_exit (we can be a child after vfork!) */
|
||||||
static int run_list(struct pipe *pi)
|
static int run_list(struct pipe *pi)
|
||||||
|
2
shell/hush_test/hush-misc/assignment3.right
Normal file
2
shell/hush_test/hush-misc/assignment3.right
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Done:0
|
||||||
|
abc=123
|
5
shell/hush_test/hush-misc/assignment3.tests
Executable file
5
shell/hush_test/hush-misc/assignment3.tests
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
# This must be interpreted as assignments
|
||||||
|
a=1 b\
|
||||||
|
=2 c=3
|
||||||
|
echo Done:$?
|
||||||
|
echo abc=$a$b$c
|
2
shell/hush_test/hush-parsing/comment1.right
Normal file
2
shell/hush_test/hush-parsing/comment1.right
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Nothing:
|
||||||
|
String: #should-be-echoed
|
2
shell/hush_test/hush-parsing/comment1.tests
Executable file
2
shell/hush_test/hush-parsing/comment1.tests
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
echo Nothing: #should-not-be-echoed
|
||||||
|
echo String: ""#should-be-echoed
|
1
shell/hush_test/hush-parsing/eol1.right
Normal file
1
shell/hush_test/hush-parsing/eol1.right
Normal file
@ -0,0 +1 @@
|
|||||||
|
Done:0
|
18
shell/hush_test/hush-parsing/eol1.tests
Executable file
18
shell/hush_test/hush-parsing/eol1.tests
Executable file
@ -0,0 +1,18 @@
|
|||||||
|
# bug was that we treated <newline> as ';' in this line:
|
||||||
|
true || echo foo |
|
||||||
|
echo BAD1 | cat
|
||||||
|
|
||||||
|
# variation on the same theme
|
||||||
|
true || echo foo |
|
||||||
|
# comment
|
||||||
|
echo BAD2 | cat
|
||||||
|
|
||||||
|
# variation on the same theme
|
||||||
|
true || echo foo |
|
||||||
|
|
||||||
|
echo BAD3 | cat
|
||||||
|
|
||||||
|
# this should error out, but currently works in hush:
|
||||||
|
#true || echo foo |;
|
||||||
|
|
||||||
|
echo Done:$?
|
Loading…
x
Reference in New Issue
Block a user