Safe (and restricted) version of eval 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
The way you implemented escaping some symbols, running eval, and finally restoring the escaped symbols, looks fine.

Have you escaped everything to make this safe?
That's hard to tell.
And will it be future-proof?
Now that's impossible to tell.
You just cannot know if a future version of Bash will introduce something that should be escaped and leave your script vulnerable until patched.
That's unlikely for sure, but this is an ugliness that any blacklisting approach cannot escape.

Most importantly, do you really need this? (As @200_success pointed out in a comment.)
It seems you don't.
If you need to parse key=value pairs from a config file,
then I suggest to do just that,
implement just enough parsing that you need.
The solution will be specialized for the specific use case,
but that will make it safe, and free of any present or future problems with eval.