【问题标题】:Does using linux capabilities disable LD_PRELOAD使用 linux 功能是否会禁用 LD_PRELOAD
【发布时间】:2013-08-06 04:13:00
【问题描述】:

在我的自定义环境中,预加载了一个拦截器库,它运行bind()connect() 等调用的特殊实现。

我看到的问题是,每当应用程序使用命令setcap 显式启用功能时,执行应用程序无法预加载拦截器库并调用默认 libc connect()

这是预期的行为吗?如果是,禁用LD_PRELOAD 的原因是什么?

是否有任何调整或方法可以用来成功预加载启用功能的库?

【问题讨论】:

  • 请参阅this question 以获得答案。
  • 您可以为目标二进制文件编写一个包装程序。它或多或少必须是 setuid root。它将派生一个子进程,然后执行目标二进制文件(设置LD_PRELOAD);目标二进制文件没有设置任何文件功能。然后,您的预加载库与子进程通信(通过例如 fd 3 上的套接字对),子进程向目标进程授予必要的功能,然后退出(并且预加载库获取子进程)。如果你想要一个例子,请告诉我。
  • @NominalAnimal 很高兴你能给我一个例子。

标签: c linux linux-kernel shared-libraries linux-capabilities


【解决方案1】:

是的,出于安全原因(请参阅man sudo)。

您必须通过在 main() 开头使用 dlopen (或通过包装 main 或类似方法)从您的代码中显式打开库来解决此问题。

【讨论】:

  • 如果对ld_preload 的理解有误,请纠正我:共享对象库是在任何标准库函数之前预加载的。如果是这样,则预加载所需的库,然后是 libc.so;正如您建议在应用程序main() 中使用dlopen 打开一个库,在打开dlopen 时符号不是已经被预加载的库或libc 解析了吗?
【解决方案2】:

就像 Oliver Matthews 回答的那样,出于安全原因,LD_PRELOAD 对于 setuid 二进制文件和具有文件功能的二进制文件都被禁用。

要在启用文件功能的同时预加载库,您有两种选择:

  1. 设置预加载的库setuid root

    (如果库由 root 拥有并标记为 set-uid,Linux 动态链接器 ld.so 会预加载库,即使是启用了 setuid/file-capability 的二进制文件。)

  2. 使用 setuid 根包装器

    包装器获得完整的 root 权限(真实和有效用户和组 ID 均为零),并将原始真实用户和组 ID 存储到例如环境变量。

    预加载的库有一个构造函数,例如

    static void my_library_init(void) __attribute__((constructor));
    static void my_library_init(void)
    {
        /* ... */
    }
    

    main() 之前自动运行(但可能在其他预加载库中的其他构造函数之后,或者在预加载库所依赖的库中)。

    此构造函数获得所需的功能,可以通过环境变量(getenv()cap_from_text())或二进制可执行文件本身(cap_from_file("/proc/self/exe"))指定。

    在限制之前,构造函数必须临时使用prctl(PR_SET_KEEPCAPS, 1) 来保持身份更改的能力,并保留CAP_SETUIDCAP_SETGID 能力以便能够将身份从root 更改为环境变量中指定的用户和组自己到最终的能力集。

这两个选项都有明显的安全考虑。我建议在预加载的库构造函数中进行完整性检查(并清除 LD_PRELOAD)。如果有任何可疑之处,请使用_exit() 立即中止该过程。

一般来说,为了简单起见,我推荐第一个选项(实现和安全问题),但如果有某些原因不能使用它,我也可以为第二种情况提供概念验证代码。 (我已经验证这两个选项都适用于运行 3.8.0-27-generic x86-64 内核的 Ubuntu 12.04.2 LTS,使用 ext4 文件系统。)

希望这会有所帮助。

【讨论】:

  • 选项 1,将 setuid root 设置为预加载库确实有助于我进行预加载。但是,当我设置在exec 之后有几个边界更改的功能可编程性时,prctl(PR_SET_KEEPCAPS, 1L) 没有保留功能。我的测试程序执行以strace/gdb ./executable 运行时未能保留功能并以./executable 运行时成功。这是因为它的边界从 shell 更改为 strace/gdb,然后是 ./executable。想知道是什么导致能力不能保留超过边界变化。
  • @SunEric: prctl(PR_SET_KEEPCAPS, 1L) 仅对下一个exec*() 有效,因为PR_SET_KEEPCAPSexec*() 调用后始终重置为0。 man 2 prctl 手册页中提到了这一点。
猜你喜欢
  • 1970-01-01
  • 2012-04-08
  • 1970-01-01
  • 1970-01-01
  • 2016-10-09
  • 2015-11-13
  • 1970-01-01
  • 1970-01-01
  • 2011-12-04
相关资源
最近更新 更多