3

In script.sh:

#!/bin/bash

func(){
  echo "I am here a func."
}

export -f func
export variable="I am here a variable"

sudo -E bash -c "func ; echo $variable"

And the output came:

bash: func: command not found
I am here a variable
  1. WHY?
  2. Is there any solution to let me call a function [which declared in script.sh] within a newborn shell by sudo?

Actually I know a method like :

#!/bin/bash

func(){
  echo "I am here a func."
}

FUNC=$(declare -f func)
export variable="I am here a variable"

sudo -E bash -c "$FUNC; func ; echo $variable"

But I think it's too heavy, because I have to register every function in this way.

0

After export -f func I can see the following in the environment:

$ env | grep func
BASH_FUNC_func%%=() …

Bash exports functions as variables with names like BASH_FUNC_foo%% and values starting with (). I believe before Shellshock names were without any prefix (foo=() …). Apple prefix seems to be __BASH_FUNC<. In any case the value starts with ().

sudo tries to be very careful with these names. First of all, when I run sudo -V as root (sudo sudo -V as regular user), a part of the output is like:

Environment variables to remove:
        *=()*

Or maybe:

Environment variables to remove:
        __BASH_FUNC<*
        BASH_FUNC_*

depending on the version of sudo, I think.

I can remove these patterns from the list with the following line in sudoers (but don't do this yet, read the whole answer first):

Defaults       env_delete -= "*=()* BASH_FUNC_* __BASH_FUNC<*"

And I can even add them to the "preserve" list (also don't):

Defaults        env_keep += "*=()* BASH_FUNC_* __BASH_FUNC<*"

Still it will not necessarily make your exported function work with sudo. The reason is the pattern of *=()* might be hardcoded, some versions of sudo reject variables with values starting with (). This behavior evolved. Available solutions depend on sudo version you have.

From man 5 sudoers:

Command environment

Since environment variables can influence program behavior, sudoers provides a means to restrict which variables from the user's environment are inherited by the command to be run. There are two distinct ways sudoers can deal with environment variables.

By default, the env_reset option is enabled. […] The HOME, MAIL, SHELL, LOGNAME and USER environment variables are initialized based on the target user and the SUDO_* variables are set based on the invoking user. Additional variables, such as DISPLAY, PATH and TERM, are preserved from the invoking user's environment if permitted by the env_check or env_keep options. This is effectively a whitelist for environment variables. […] Environment variables with a value beginning with () are removed unless both the name and value parts are matched by env_keep or env_check, as they may be interpreted as functions by the bash shell. Prior to version 1.8.11, such variables were always removed.

If, however, the env_reset option is disabled, any variables not explicitly denied by the env_check and env_delete options are inherited from the invoking process. In this case, env_check and env_delete behave like a blacklist. Prior to version 1.8.21, environment variables with a value beginning with () were always removed. Beginning with version 1.8.21, a pattern in env_delete is used to match bash shell functions instead. Since it is not possible to blacklist all potentially dangerous environment variables, use of the default env_reset behavior is encouraged.

Environment variables specified by env_check, env_delete, or env_keep may include one or more * characters which will match zero or more characters. No other wildcard characters are supported.

By default, environment variables are matched by name. However, if the pattern includes an equal sign (=), both the variables name and value must match. For example, a bash shell function could be matched as follows:

env_keep += "BASH_FUNC_my_func%%=()*"

Without the =()* suffix, this would not match, as bash shell functions are not preserved by default.

As you can see env_reset is important. You most likely have it enabled. Note the whole point of the -E option (sudo -E) is to disable env_reset on demand.


In my Kubuntu 18.04.3 LTS I have sudo in the version 1.8.21. By default it places *=()* on the list of variables to remove. This line in sudoers makes it stop:

Defaults       env_delete -= "*=()*"

Then sudo -E will preserve any exported shell function. This is my primary answer.


In my Debian 9 sudo is in the version 1.8.19. By default it places BASH_FUNC_* and __BASH_FUNC<* on the list of variables to remove. Removing these entries doesn't work because the tool almost always rejects variables with values beginning with () anyway; this behavior seems to be hardcoded.

The manual for this version gives some clues. I found one solution:

Defaults       env_keep += "BASH_FUNC_func%%=()*"

Then sudo without -E will preserve this particular shell function (func) if exported. Disadvantages (or maybe advantages in some circumstances):

  • this doesn't work with sudo -E, so I cannot simply keep the rest of environment on demand;
  • I need to explicitly specify the whole name of the variable, without wildcards; BASH_FUNC_*%%=()*, BASH_FUNC_* or *=()* will not work.

This approach works in 1.8.21 as well.


According to the manual, prior to version 1.8.11 variables starting with () were always removed. If your sudo is that old then there is no solution other than the trick you used in the question (or similar).


Notes:

  • Do not edit sudoers like any other file; use visudo. Other precautions just in case:
    • Start a separate "spare" shell as root (sudo su -). If you do something stupid with sudoers in your main shell, you will still be able to act as root without relying on the file.
    • When connecting via SSH, use tmux or screen before spawning the elevated shell. If you do something stupid with sudoers and (accidentally) the connection fails, the elevated shell will wait for your regular user to reattach, instead of exiting.
    • Make a backup copy of the file first (cp -a), especially if you're going to experiment with it.
    • When you think you're done, make sure you can gain the root access anew before you exit the "spare" elevated shell.
  • Allowing sudo to preserve any shell function may make it easy for you to shoot yourself in the foot. There are reasons the tool rejects functions by default.
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.