Why BASH_FUNC_foobar%% Environment Variable Is Unset in Shell Subprocesses

bashenvironment-variables

I'm messing around with the security of a setuid binary (with the intention of disclosing anything I find to the author, obviously). I'm pretty sure it has an arbitrary code execution vulnerability because it invokes a shell script and it doesn't sanitize the environment – I thought of bash's export -f but I can't actually make a proof of concept work.

The basic problem is that for some reason, BASH_FUNC_foobar%% (where foobar is the function being exported) is mysteriously vanishing from the environment in some subprocesses. It works with non-shells:

% env 'BASH_FUNC_foobar%%=() { echo pwd lol; }' env | grep BASH
BASH_FUNC_foobar%%=() { echo pwd lol; }

But if I replace env | grep BASH with the actual program name, modified to dump the environment (basically just system("env") in the C source), this variable is removed. The same happens when I specify sh as the command to invoke, although weirdly enough, if I specify bash then the function is picked up just fine.

Note that on my test system /bin/sh is provided by dash.

What the heck is going on? Why is this variable disappearing?

Best Answer

On many linux systems, /bin/sh is actually /bin/dash.

As already mentioned in a comment by @MichaelHomer /bin/dash will sanitize the environment and remove from it any strings which are not of the form /^[a-zA-Z_][a-zA-Z_0-9]*=.*/, and that includes the BASH_FUNC_foo%%=..., because of the %%.

This is quite specific to dash and to OpenBSD's ksh (and mksh which is based on it) -- other shells won't bother to do that.

Example, on debian where /bin/sh is dash:

$ env - '@#%=' 'foo%%=bar' /bin/sh -c /usr/bin/printenv
PWD=/your/cwd
$ env - '@#%=' 'foo%%=bar' /usr/bin/printenv
@#%=
foo%%=bar
$ env - '@#%=' 'foo%%=bar' /bin/bash -c /usr/bin/printenv
[...]
@#%=
foo%%=bar

For a source reference you can look at src/var.c in the dash source:

    initvar();
    for (envp = environ ; *envp ; envp++) {
            p = endofname(*envp);
            if (p != *envp && *p == '=') {
                    setvareq(*envp, VEXPORT|VTEXTFIXED);
            }
    }

When dash exec's another binary, the env argument passed to execve is built from the variable list dynamically (with listvars(VEXPORT, VUNSET, 0) as called via the environment() macro).