Why POSIX wordexp(3) Is Dangerous on Untrusted Input
Translated by a model; the original is in Russian.
POSIX's wordexp(3) is frequently recommended as a convenient way to do shell-style expansions from C — for instance, turning ~/foo into /home/me/foo. The code looks harmless: call wordexp(), take the first element of we_wordv, free it with wordfree(). For tilde expansion, that's all you need.
The trouble hides in the last argument: the flags parameter. When it's 0, every expansion feature is enabled by default — including command substitution. That means an input like $(whoami)/foo or $(cat /etc/passwd)/foo will be dutifully executed by the shell. Your innocent "path-expanding helper" quietly becomes arbitrary command execution.
WRDE_NOCMD doesn't help everywhere
There is, apparently, a cure: the WRDE_NOCMD flag, which is supposed to forbid command substitution. But the author warns that in some libc implementations this is palliative at best.
musl implements wordexp(3) by simply delegating to the system shell. Before doing so it runs a cursory scan for command substitutions, but the check is incomplete: some constructs that the underlying shell (such as GNU Bash) happily treats as valid substitutions slip past musl's matching.
macOS works much the same way: its libc hands the job off to a helper process, /usr/lib/system/wordexp-helper. According to Julian Squires, as recently as 2023 the same bypass worked here as in musl. The author didn't bother testing whether it still does now that macOS's default shell is zsh rather than bash. (A curious footnote: macOS's wordexp(3) used to shell out to Perl — wordexp-helper appears to be the "modern" replacement.)
The bottom line
Don't use wordexp(3) on untrusted inputs, even with WRDE_NOCMD. If all you want is tilde expansion, you're better off doing it by hand rather than handing the string to a shell.
Source: Lobsters
Comments
Sign in, to comment.