为什么'sudo -E'不保留export -f导出的函数环境变量?

为什么'sudo -E'不保留export -f导出的函数环境变量?

在脚本.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"

输出如下:

bash: func: command not found
I am here a variable
  1. 为什么?
  2. 有什么解决方案可以让我通过 sudo 在新生 shell 中调用函数 [在 script.sh 中声明]?

其实我知道一个方法,比如:

#!/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"

但我觉得它太重了,因为我必须用这种方式注册每个函数。

答案1

export -f func当我在环境中看到以下内容后:

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

Bash 将函数导出为变量,其名称类似于BASH_FUNC_foo%%,值以().我相信以前炮弹休克名称没有任何前缀 ( foo=() …)。Apple 前缀似乎是__BASH_FUNC<。无论如何,值都以 开头()

sudo尝试对这些名称以及整个环境非常小心。想象一下,您可以sudo只使用脚本来运行 Bash 脚本。想象一下该脚本合法地使用cat.通过导出名为 you 的 shell 函数,cat可以有效地更改脚本并运行任意代码。

您可以使用自己的$PATH(和有办法做到这一点;但管理员也有办法不让你这样做)。或者,如果您设法使用$LD_PRELOAD(不那么容易,请参阅man 5 sudoers其中提到LD_)。

让我们回到导出的 shell 函数。首先,当我sudo -V以 root 身份(sudo sudo -V作为普通用户)运行时,部分输出如下:

Environment variables to remove:
        *=()*

或者可能:

Environment variables to remove:
        __BASH_FUNC<*
        BASH_FUNC_*

我认为取决于 的版本sudo

我可以使用以下行从列表中删除这些模式sudoers(但不要这样做,请先阅读整个答案):

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

我什至可以将它们添加到“保留”列表中(也不要):

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

但它并不一定能让您的导出函数与sudo.原因是 的模式*=()*可能是硬编码的,某些版本的sudo拒绝变量的值以 开头()。这种行为是进化而来的。可用的解决方案取决于sudo您拥有的版本。

man 5 sudoers

命令环境

由于环境变量可以影响程序行为,因此sudoers提供了一种方法来限制要运行的命令继承用户环境中的哪些变量。 sudoers 有两种不同的方式来处理环境变量。

默认情况下,该env_reset选项处于启用状态。 [...] HOMEMAILSHELL和环境变量根据目标用户进行初始化,并LOGNAME根据调用用户设置变量。如果或选项允许,则将从调用用户的环境中保留其他变量,例如、和。这实际上是环境变量的白名单。 […]除非名称和值部分均由或匹配,否则将删除以 开头的值的环境变量,因为它们可能被 bash shell 解释为函数。在 1.8.11 版本之前,此类变量始终被删除。USERSUDO_*DISPLAYPATHTERMenv_checkenv_keep()env_keepenv_check

但是,如果env_reset禁用该选项,则env_checkenv_delete选项未明确拒绝的任何变量将从调用进程继承。在这种情况下,env_check行为env_delete就像黑名单一样。在版本 1.8.21 之前,()始终删除以 开头的值的环境变量。从版本 1.8.21 开始,使用模式 inenv_delete来匹配 bash shell 函数。由于不可能将所有潜在危险的环境变量列入黑名单,env_reset因此鼓励使用默认行为。

env_check由、env_delete、 或指定的环境变量env_keep可能包含一个或多个*字符,这些字符将匹配零个或多个字符。不支持其他通配符。

默认情况下,环境变量按名称匹配。但是,如果模式包含等号 ( =),则变量名称和值都必须匹配。例如,bash shell 函数可以按如下方式匹配:

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

如果没有=()*后缀,这将不匹配,因为默认情况下不保留 bash shell 函数。

正如你所看到的,env_reset这很重要。您很可能已启用它。请注意,-E选项 ( )的全部要点是按需sudo -E禁用。env_reset


在我的 Kubuntu 18.04.3 LTS 中,我的sudo版本是 1.8.21。默认情况下,它放置*=()*在要删除的变量列表中。这一行sudoers让它停止:

Defaults       env_delete -= "*=()*"

然后sudo -E将保留任何导出的 shell 函数。这是我的主要答案。


我的 Debian 9sudo版本是 1.8.19。默认情况下,它将BASH_FUNC_*和放在__BASH_FUNC<*要删除的变量列表中。删除这些条目不起作用,因为该工具几乎总是拒绝值以()无论如何开头的变量;这种行为似乎是硬编码的。

该版本的手册提供了一些线索。我找到了一个解决方案:

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

然后sudo 没有 -Efunc如果导出,将保留这个特定的 shell 函数 ( )。缺点(或者在某些情况下可能是优点):

  • 这不适用于sudo -E,所以我不能简单地按需保留环境的其余部分;
  • 我需要显式指定变量的全名,不带通配符;BASH_FUNC_*%%=()*BASH_FUNC_*否则*=()*将无法工作。

这种方法在 1.8.21 中也有效。


根据手册,在版本 1.8.11 之前,以 开头的变量()总是被删除。如果您sudo已经那么老了,那么除了您在问题(或类似问题)中使用的技巧之外,没有其他解决方案。


笔记:

  • 不要sudoers像任何其他文件一样编辑;使用visudo。其他预防措施以防万一:
    • 以 root ( ) 身份启动一个单独的“备用”shell sudo -i。如果您sudoers在主 shell 中做了一些愚蠢的事情,您仍然可以以 root 身份运行而不依赖该文件。
    • 通过 SSH 连接时,请在生成提升的 shell 之前使用tmux或。screen如果您做了一些愚蠢的事情sudoers并且(意外地)连接失败,提升的 shell 将等待您的常规用户重新连接,而不是退出。
    • 首先制作文件的备份副本 ( cp -a),特别是如果您要进行试验的话。
    • 当您认为已完成时,请确保在退出“备用”提升的 shell 之前可以重新获得 root 访问权限。
  • sudo即使您是唯一的用户,并且强化防范允许 sudoers 运行任意代码的技巧也没有什么意义(因为您sudo可以自由地运行任意代码,而无需任何技巧,并且您希望这样),请三思而后行你允许太多了。允许sudo保留任何 shell 功能可能会让您很容易搬起石头砸自己的脚;不一定是现在,而是将来。

相关内容