sh: Avoid side effects from builtins in optimized command substitution.

Change the criterion for builtins to be safe to execute in the same process
in optimized command substitution from a blacklist of only cd, . and eval to
a whitelist.

This avoids clobbering the main shell environment such as by $(exit 4) and
$(set -x).

The builtins jobid, jobs, times and trap can still show information not
available in a child process; this is deliberately permitted. (Changing
traps is not.)

For some builtins, whether they are safe depends on the arguments passed to
them. Some of these are always considered unsafe to keep things simple; this
only harms efficiency a little in the rare case they are used alone in a
command substitution.
This commit is contained in:
jilles 2010-12-30 22:33:55 +00:00
parent 12d4883ccf
commit ca3118f4ca
2 changed files with 78 additions and 5 deletions

View File

@ -643,7 +643,31 @@ evalbackcmd(union node *n, struct backcmd *result)
result->fd, result->buf, result->nleft, result->jp));
}
/*
* Check if a builtin can safely be executed in the same process,
* even though it should be in a subshell (command substitution).
* Note that jobid, jobs, times and trap can show information not
* available in a child process; this is deliberate.
* The arguments should already have been expanded.
*/
static int
safe_builtin(int idx, int argc, char **argv)
{
if (idx == BLTINCMD || idx == COMMANDCMD || idx == ECHOCMD ||
idx == FALSECMD || idx == JOBIDCMD || idx == JOBSCMD ||
idx == KILLCMD || idx == PRINTFCMD || idx == PWDCMD ||
idx == TESTCMD || idx == TIMESCMD || idx == TRUECMD ||
idx == TYPECMD)
return (1);
if (idx == EXPORTCMD || idx == TRAPCMD || idx == ULIMITCMD ||
idx == UMASKCMD)
return (argc <= 1 || (argc == 2 && argv[1][0] == '-'));
if (idx == SETCMD)
return (argc <= 1 || (argc == 2 && (argv[1][0] == '-' ||
argv[1][0] == '+') && argv[1][1] == 'o' &&
argv[1][2] == '\0'));
return (0);
}
/*
* Execute a simple command.
@ -861,10 +885,8 @@ evalcommand(union node *cmd, int flags, struct backcmd *backcmd)
|| ((cmdentry.cmdtype == CMDNORMAL || cmdentry.cmdtype == CMDUNKNOWN)
&& ((flags & EV_EXIT) == 0 || have_traps()))
|| ((flags & EV_BACKCMD) != 0
&& (cmdentry.cmdtype != CMDBUILTIN
|| cmdentry.u.index == CDCMD
|| cmdentry.u.index == DOTCMD
|| cmdentry.u.index == EVALCMD))) {
&& (cmdentry.cmdtype != CMDBUILTIN ||
!safe_builtin(cmdentry.u.index, argc, argv)))) {
jp = makejob(cmd, 1);
mode = cmd->ncmd.backgnd;
if (flags & EV_BACKCMD) {

View File

@ -0,0 +1,51 @@
# $FreeBSD$
a1=$(alias)
: $(alias testalias=abcd)
a2=$(alias)
[ "$a1" = "$a2" ] || echo Error at line $LINENO
alias testalias2=abcd
a1=$(alias)
: $(unalias testalias2)
a2=$(alias)
[ "$a1" = "$a2" ] || echo Error at line $LINENO
[ "$(command -V pwd)" = "$(command -V pwd; exit $?)" ] || echo Error at line $LINENO
v=1
: $(export v=2)
[ "$v" = 1 ] || echo Error at line $LINENO
rotest=1
: $(readonly rotest=2)
[ "$rotest" = 1 ] || echo Error at line $LINENO
set +u
: $(set -u)
case $- in
*u*) echo Error at line $LINENO ;;
esac
set +u
set +u
: $(set -o nounset)
case $- in
*u*) echo Error at line $LINENO ;;
esac
set +u
set +u
: $(command set -u)
case $- in
*u*) echo Error at line $LINENO ;;
esac
set +u
umask 77
u1=$(umask)
: $(umask 022)
u2=$(umask)
[ "$u1" = "$u2" ] || echo Error at line $LINENO
dummy=$(exit 3); [ $? -eq 3 ] || echo Error at line $LINENO