The problem is in cases where the content of $x has not been sanitized and contains data that could potentially be under the control of an attacker in cases that shell code may end up being used in a privilege escalation context (for instance a script invoked by a setuid application, a sudoers script or used to process off-the-network data (CGI, DHCP hook...) directly or indirectly).
If:
x='PATH=2' Then:
x=$((1-$x)) has the side effect of setting PATH to 2 (a relative path that could very well be under control of the attacker). You can replace PATH with LD_LIBRARY_PATH or IFS... The same happens with x=$((1-x)) in bash, zsh or ksh (not dash nor yash which only accept numerical constants in variables there).
In bash, zsh and ksh (not dash or yash), if x is:
x='a[0$(uname>&2)]' Then the expansion of $((1-$x)) or $((1-x)) causes that uname command to be executed (for zsh, a needs to be an array variable, but one can use psvar for instance for that).
Note that:
x=$((1-$x)) won't work properly for negative values of $x in some shells that implement the (optional as per POSIX) -- (decrement) operator (as with x=-1, that means asking the shell to evaluate the 1--1 arithmetic expression). "$((1-x))" doesn't have the problem as x is expanded as part of (not before) the arithmetic evaluation.
In summary, one shouldn't use uninitialised or non-sanitized external data in arithmetic expressions in shells (note that arithmetic evaluation can be done by $((...)) (aka $[...] in bash or zsh) but also depending on the shell in the let, [/test, declare/typeset/export..., return, break, continue, exit, printf, print builtins and ((..)) and [[...]] constructs to name a few).
To check that a variable contains a literal decimal integer number, you can use POSIXly:
case $var in ("" | - | *[!0123456789-]* | ?*-*) echo >&2 not a valid number; exit 1;; esac Beware that [0-9] in some locales matches more than 0123456789. [[:digit:]] should be OK but I wouldn't bet on it.
Also remember that numbers with leading zeros are treated as octal in some contexts (010 is sometimes 10, sometimes 8).
Examples:
$ export 'x=psvar[0$(uname>&2)]' $ ksh93 -c 'echo "$((x))"' Linux ksh93: psvar: parameter not set $ ksh93 -c '[ x -lt 2 ]' Linux ksh93: [: psvar: parameter not set $ bash -c 'echo "$((x))"' Linux 0 $ bash -c '[[ $x -lt 2 ]]' Linux $ bash -c 'typeset -i a; export a="$x"' Linux $ bash -c 'typeset -a a=([x]=1)' Linux $ mksh -c '[[ $x -lt 2 ]]' Linux $ zsh -c 'echo "$((x))"' Linux 0 $ zsh -c 'printf %d $x' Linux 0 $ zsh -c 'integer x' Linux $ zsh -c 'exit $x' Linux More reading at:
- http://www.zsh.org/mla/workers/2014/msg01041.html (where Oliver Kiddle brought the
x[0$(...)]issue to our attention). - http://thread.gmane.org/gmane.comp.standards.posix.austin.general/9971
- http://thread.gmane.org/gmane.comp.shells.bash.bugs/22737 for another mis-design potentially leading to code injection in
bash. - Security implications of forgetting to quote a variable in bash/POSIX shells where that and leaving a variable unquoted can aggravate each other.