cgexec 无法继承 LD_PRELOAD 环境变量

2018-08-16 张炎泼 更多博文 » 博客 » GitHub »

cgroup ld_preload

原文链接 https://drmingdrmer.github.io/tech/cgroup/2018/08/16/cgexec.html
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。


<!-- mdtoc start -->

<!-- mdtoc end -->

问题: cgexec 会忽略掉 LD_PRELOAD的环境变量

有时需要替换malloc库的时候, 我们会使用LD_PRELOAD=/usr/lib64/libtcmalloc.so.4 my_prog 方便地将运行的malloc库替换成tcmalloc.

但是如果使用cgroup来启动my_prog, 这个环境变量就无法生效了.

验证这个问题, 可以通过一个 test_preload.sh 的脚本:

echo env:
env | grep LD # 显示是否继承了LD_PRELOAD的环境变量.

echo lsof:
lsof -p $$ | grep tcmalloc # 显示是否当前进程加载了tcmalloc

运行:

LD_PRELOAD=/usr/lib64/libtcmalloc.so.4 ./test_preload.sh
> env:
> LD_PRELOAD=/usr/lib64/libtcmalloc.so.4
> lsof:
> test_prel 7588 root  mem    REG  253,0    301136 36021701 /usr/lib64/libtcmalloc.so.4.4.5

通过cgexec运行时, 没有输出:

LD_PRELOAD=/usr/lib64/libtcmalloc.so.4 cgexec ./test_preload.sh
> env:
> lsof:

说明环境变量没有继承过来. 也没有被加载.

解决方案

使用cgexec时, 必须把环境变量的指定放到cgexec启动之后:

cgexec sh -c 'LD_PRELOAD=/usr/lib64/libtcmalloc.so.4 ./test_preload.sh'
> env:
> LD_PRELOAD=/usr/lib64/libtcmalloc.so.4
> lsof:
> test_prel 10059 root  mem    REG  253,0    301136 36021701 /usr/lib64/libtcmalloc.so.4.4.5

原因

可执行文件如果设置了capability属性或setuid属性(表示某些只有root级别才有的权限, 网卡抓包等), 就不允许LD_PRELOAD=tcmalloc.so 的环境变量来加载额外的lib, 因为安全性考虑, 不允许任意的代码跑到root身份上运行.

例子:

# ping是设置了网络相关的capability所以普通用户才能抓包.

getcap /bin/ping
> /bin/ping = cap_net_admin,cap_net_raw+p

# 用非root用户运行:
LD_PRELOAD=/usr/lib64/libtcmalloc.so.4 ping baidu.com

# 拿到pid 29841:
lsof -p 29841 | grep tcmalloc
# 啥也没有

# 用root用户运行同样的命令:
LD_PRELOAD=/usr/lib64/libtcmalloc.so.4 ping baidu.com

# 可以看到允许被加载tcmalloc
lsof -p 2256 | grep tcmalloc
> ping    2256 root  mem    REG  253,0    301136 36021701 /usr/lib64/libtcmalloc.so.4.4.5

同样不允许通过环境变量加载so的属性还有setuid;

setuid属性: 文件被执行时将自己的用户修改为文件所有者的用户, 而不是调用者的.

以及cgexec

How to use cgroup... 文中写的:

They are required to launch, then classify as the application makes use of LD_LIBRARY_PATH which is not available when running a binary with setuid (such as cgexec).

总之有机会把任意代码提升权限的事情都不会让做.

使用cgexec加LD_PRELOAD需要这么做:

# 没有cgexec时:
LD_PRELOAD=/usr/lib64/libtcmalloc.so.4 ./test_preload.sh "a b" "c"

# 使用cgexec时:
cgexec sh -c 'LD_PRELOAD=/usr/lib64/libtcmalloc.so.4 ./test_preload.sh "a b" "c"'