TIL: Some surprising code execution sources in bash
https://yossarian.net/til/post/some-surprising-code-execution-sources-in-bash
@yossarian The use of '[[' is the problem. That's an evaluating comparison and is as dangerous as 'eval' (as shown). All scripts should be using just the single '['. Using '[[' is for compatibility with ancient shells.
@kees @yossarian Do we need to do something about this?
$ git grep '\[\[.*\]\]' origin/master -- ':*.sh' | wc --lines
1065
edit: there is some amount of false positives from sed regexes embedded in shell scripts. but also enough real cases
@vegard @kees @yossarian more like git grep '\[' ...
at least for me the `oh bummer' part was the observation that [ also behaves this way, because in bash it's a builtin. perhaps that can actually be considered a bug?
@kees @yossarian uh, good to know. I'm pretty sure at some point I've been told (maybe by some linting tool?) that [[ is preferrable to [.
@kees @yossarian https://www.shellcheck.net/wiki/SC2292 says "[[ .. ]] suppresses word splitting and globbing, supports a wider variety of tests, and is generally safer and better defined than [ .. ]"
Not enabled by default in current version, but I believe it was in the past.
@hanno @yossarian Yeah this was a common recommendation long ago to "avoid bash-isms" for compatibility. Since then busybox and dash (the common non-bash "/bin/sh" instances) grew '[' support either internally or via /usr/bin/[
@kees @yossarian I'm confused, this tells me [[ is the bash'ism, and [ the posix thing: https://mywiki.wooledge.org/BashFAQ/031
@hanno @yossarian Ah, I may have it backwards then, but '[' remains the safe one.
@kees @hanno @yossarian `[` is the POSIX one, but it has silly semantics around strings, which is you you end up doing stuff like `[ "x$foo" -ne "x" ]` to test non-emptiness.
`[[` on the other hand behaves "sanely", but it's definitely a bashism and not portable.
@kees @hanno @yossarian There are many misconceptions in this thread. The test command `[` is well-defined[1] and works as expected. It can be argued that `[[` doesn't work as expected since it introduces semantics which are incompatible with the rest of the shell language.
@flameeyes Your example is wrong, `-ne` is for math. You can test for null or unset simply: `[ "$foo" ]`.
Neither option is unsafe, OP issue stems from array indexing in bash, specifically.
[1]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html
@jntesteves @kees @hanno @yossarian I blame not having had my coffee for that `-ne`
@jntesteves @hanno @yossarian @flameeyes
Ah! That's why this doesn't work:
$ foo='0$(echo bad >&2) + 7' $ if [[ "$foo" -eq 0 ]]; then echo zero; fi
-bash: [[: 0$(echo bad >&2) + 7: syntax error: invalid arithmetic operator (error token is "$(echo bad >&2) + 7")
@jntesteves @kees @hanno @yossarian @flameeyes Jup, the x-in-variable expansion was for buggy implementations of `[` but it can be implemented correctly without requiring this workaround.
As @jntesteves said, `[` is well-defined. Arguably, `[[` is a bit "safer" for word expansions (after all, that's what it was designed to be better at), but you can do `[` quite safely.
@hanno @kees @yossarian Yeah [[ is a bashism. For example, with FreeBSD's default sh:
[[ x ]] && echo true
/bin/sh: [[: not found
[ via a builtin or /usr/bin/[ has existed as far back as I can see
@kees this itself is a TIL for me, since I was taught/received that [[ is newer than [!
but also, is this always true? [ and ‘test’ will also use eval if the builtin version gets used first, but I don’t know the rules around when the builtins are used vs. not (for my interactive shell, they seem to always have precedence)
$ type [
[ is a shell builtin
$ foo='a[$(echo bad >&2)] + 7'
$ if [ "$foo" -eq 0 ]; then echo zero; fi
-bash: [: a[$(echo bad >&2)] + 7: integer expression expected
$ if /usr/bin/[ "$foo" -eq 0 ]; then echo zero; fi
/usr/bin/[: invalid integer ‘a[$(echo bad >&2)] + 7’
Both built-in and binary appear safe to me. The [[ remains unsafe:
$ if [[ "$foo" -eq 0 ]]; then echo zero; fi
bad
Dash and busybox are safe too:
dash: 3: [: Illegal number: a[$(echo bad >&2)] + 7
sh: a[$(echo bad >&2)] + 7: bad number
@kees out of curiosity, which version of bash was that? I got the [ and test variants working locally; I’ll find and share those tonight
@yossarian My bash is from Ubuntu 22.04:
GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)
If you have a PoC working with [ I am excited to see it! I wonder if there is some distro shell patching going on...
here's a PoC for `test` and `[` working with the `-v` case:
```
# case 1
test -v 'x[$(cat /etc/passwd)]'
# case 2
[ -v 'x[$(cat /etc/passwd)]' ]
```
OTOH the arithmetic case doesn't work, and i get the same errors as you two:
```
$ test 'a[$(cat /etc/passwd > /tmp/pwned)]' -eq 0
bash: test: a[$(cat /etc/passwd > /tmp/pwned)]: integer expression expected
$ [ 'a[$(cat /etc/passwd > /tmp/pwned)]' -eq 0 ]
bash: [: a[$(cat /etc/passwd > /tmp/pwned)]: integer expression expected
```
...so my post is a little wrong, the "it works for [ and test" should be only under the `-v` case
Hah! Nice find. I didn't even know about "-v", and it seems to be a bashism (dash, busybox, and test all balk at it). I've always used "-z" for that kind of thing.
Looking at the bash manpage, it seems it's not everything that takes a "varname" (i.e. -R doesn't trip), just -v exposing the array parser again.
@kees @yossarian oh that's terrible though, because [ forks and [[ does not.
@vathpela @yossarian Nah, [ is a builtin too
@kees @yossarian oh this is one of those things where it was different in the past for dumb reasons, isn't it?
@vathpela @kees @yossarian It's mostly that POSIX defines [ to exist as a binary but allows for a shell to instead implement it as a built-in.
@kees @yossarian I tried with [ and it did not work. And 'type [' says "shell builtin" whereas 'type [[' says "shell keyword"
@kees @yossarian There is a problem with '[': it can confuse values coming from variables with operators. For example, suppose that you have
[ "$x" == a -a "$y" == b ]
Most of the time, it works. However, if $x is "!", it breaks. The correct version is
[ "x$x" == xa -a "x$y" == xb ]
Similarly,
[ -n "$x" -a -n "$y" ]
breaks if $x is "==".