I have built a shell function aimed to perform the following:
Given a string as first argument, perform safe expansions (i.e. those that cannot cause arbitrary code execution and only produce string output)
Save the output to a variable the name of which is provided as second argument, and can be considered safe for the purpose of this question
The code is as follows :
#
# Argument 1 : string to safely expand
# Argument 2 : name of the variable to which expanded value will be assigned
#
safeval()
{
local _v=$1
_v=${_v//\$\(/\$$'\1'}
_v=${_v//\$\[/\$$'\2'}
_v=${_v//'`'/$'\3'}
_v=${_v//\\ /$'\4'}
_v=${_v// /$'\5'}
eval printf -v _v %b "$_v" || return 1
_v=${_v//$'\1'/\(}
_v=${_v//$'\2'/\[}
_v=${_v//$'\3'/'`'}
_v=${_v//$'\4'/\\ }
_v=${_v//$'\5'/ }
printf -v "$2" %s "$_v"
}
This code aims to achieve its purpose by doing the following :
- Replace all occurrences of
$(
,$[
and`
with (hopefully) safe strings. - Then perform eval (the remaining expansions are performed)
- Then replace the other way around
This means that expansions that are not performed will be kept as is. There may be cases where nested expansions have the outer expansion execute, but the inner expansion be blocked, which maybe could create incorrect output or failure; as long as it just fails without doing anything nasty from a security standpoint, this is OK for me, as I am going for safety first.
Here are example cases (function safeval
assumed to be already loaded):
A=1
safeval '$A' B
echo "$B" # Echoes "1" (without the double quotes)
safeval '$A$(ls /)$A' C
echo "$C" # Echoes "1$(ls /)1" (without the double quotes)
Any feedback about the safety (or lack thereof) and potential failure modes that I have missed is especially welcome.
This function is intended to allow the use of values read from configuration files in which (safe) expansions and shell-type quoting would be allowed.
EDIT
I found a bug in handling of spaces and double quotes. On the eval printf
line, variable _v
is expanded, and if it contains double quotes and spaces, printf
sees many arguments (spaces are stripped due to word splitting). To fixe this the additional code :
- Replace already-escaped spaces with a non-printable character
- Escape remaining spaces so that
printf
will see only one argument - At the end, put the escaped spaces back in
eval
to do proper quote evaluation (or else I will need to write my own parsing code). So you could say that having to handle quotes correctly forces me to have a safe version ofeval
, and not blocking safe expansions is a bonus feature at no additional cost. \$\endgroup\$eval
to parse a config file is fraught with peril. If the file is actually Bash you want to execute justsource
it, otherwise implement a custom parser that only accepts the syntax you want to support. If this proves in Bash another language like Python may be a better fit. \$\endgroup\$