C - Externs - 在LD_PRELOAD库中监控价值的安全方法

时间:2016-09-30 14:03:03

标签: c gcc ld extern ld-preload

背景

我帮助维护一个简单的命令行工具diskmanager,用于监视磁盘性能不佳,这主要是由于太多操作/用户同时使用同一磁盘。我的工作涉及维护一个偶尔用于监督"磁盘管理器程序通过以下方式启动它:

libdisksupervisor.so

我们这样做的原因是因为库和应用程序具有非常不同的发布计划,由于跨NDAs的原因,源不能共享,等等。为了让我们的生活更轻松,{{的维护者1}}在应用程序中创建了一些LD_PRELOAD=/public/libdisksupervisor.so /sbin/diskmanager 变量,并添加了一些调用" dummy"与diskmanager捆绑在一起的库(extern)中的函数。

当致电libdonothing.so时(通常在diskmanager中进行,但我们会通过int dummy(void) libdonothing.so拦截它,其中也包括LD_PRELOAD函数原型),我们知道libdisksupervisor.so处于我们可以安全地从我们自己的库中读取diskmanager(位于extern int internalStatus)的状态。 diskimager的代码非常简单:

dummy()

问题

到目前为止,这么好。几个月前,# In source for diskmanager int internalStatus = (-1); # In libdummy.so int dummy(void) { return 0; } # In libdisksupervisor.so extern int internalStatus; int dummy(void) { syslog(LOG_ERR, "State:%d", internalStatus); 的其中一位维护者做了一些愚蠢的事情,并从diskmanager删除了int internalStatus,导致我们的库在执行diskmanager时导致分段错误。当初级工程师在使用GCC隐藏属性并将某些值更改为LD_PRELOAD=/public/libdisksupervisor.so diskmanager时会出现类似问题,并再次导致段错误。

问题

有没有办法,在static的代码中,我们可以在继续之前测试这些libdisksupervisor.so(从我们库的角度来看)变量的存在,可能是通过一些神秘的链接器或GCC魔法?我知道我可以将externnm作为预验证脚本的一部分,但我们需要在库中完成此操作。

谢谢。

1 个答案:

答案 0 :(得分:2)

  

有没有办法,在我们的libdisksupervisor.so代码中,我们可以在继续之前测试这些extern(从我们库的角度来看)变量的存在,可能是通过一些神秘的链接器或GCC魔术?

这里有时间问题。实际上,您不需要做任何特别的事情来测试这些符号在编译时的存在和可见性,在您链接的diskmanager版本中。当您尝试将libdisksupervisor.sodiskmanager版本在运行时结果不兼容时,会出现此问题。

  

我知道我可以将nm或objdump作为预验证脚本的一部分投入其中,但我们需要在我们的c库中完成此任务。

我不知道任何与您运行程序的方式相关的方法,并且不会因diskmanager维护而轻易被意外挫败。

但也许有一种方法涉及改变你运行程序的方式。如果您当前呼叫libdisksupervisor.so提供了一个程序入口点(即main())并且您直接运行它,则可以dlopen() diskmanager并检查是否存在所需符号通过dlsym()。然后,它可以将控制权转移到diskmanager main()(也可通过dlsym()访问)。您可以将此视为在系统的动态链接器和diskmanager之间插入一个垫片。

更新

好消息是我有一个概念验证证明它可以完成(见下文)。坏消息是,将主可执行文件作为共享库加载需要特殊的构建选项,听起来让另一方使用这些选项进行构建可能很麻烦。另一方面,这种方法允许他们精确地控制和记录哪些符号暴露在你身边,也许这可以作为合适的胡萝卜。

无论如何,POC包含三个C源文件,两个辅助文件和一个Makefile:

dummy.c

int dummy(void) {
    return 0;
}

的main.c

#include <stdio.h>

int dummy(void);

#ifndef BREAKME
int internalStatus = 42;
#endif

int main(int argc, char *argv[]) {
    printf("dummy() returns %d\n", dummy());
    return 0;
}

shim.c

#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
#include <assert.h>

#define TARGET_PATH "./mainprog"
#define NOT_FOUND_STATUS 127
#define MISSING_SYM_STATUS 126

typedef int (*main_type)(int, char **);

static int *internalStatus_p;
#define internalStatus (*internalStatus_p);

int dummy(void) {
    return internalStatus;
}

#define LOAD_SYM(dso, name, var) do { \
    char *e_; \
    var = dlsym(dso, name); \
    e_ = dlerror(); \
    if (e_) { \
        fprintf(stderr, "%s\n", e_); \
        return MISSING_SYM_STATUS; \
    } \
} while (0)

int main(int argc, char *argv[]) {
    void *diskmanager_bin = dlopen(TARGET_PATH, RTLD_LAZY | RTLD_GLOBAL);
    char *error;
    main_type main_p;

    if (!diskmanager_bin) {
        fprintf(stderr, "Could not load " TARGET_PATH ": %s\naborting\n", dlerror());
        return NOT_FOUND_STATUS;
    } else {
        error = dlerror();
        assert(!error);
    }

    LOAD_SYM(diskmanager_bin, "internalStatus", internalStatus_p);
    LOAD_SYM(diskmanager_bin, "main", main_p);

    return main_p(argc, argv);
}

#undef LOAD_SYM

mainprog_dynamic

{
    main; internalStatus;
};

shim_dynamic

{
    dummy;
};

生成文件

# sources contributing to a shared library must be built with -fpic or -fPIC
CFLAGS = -fPIC -std=c99
LDFLAGS = 

SHLIB_LDFLAGS = -shared
SHLIB_EXTRALIBS = -lc

# Sources contributing to the main program should be built with -fpie or -fPIE
SHMAIN_CFLAGS = -fpie
# The main program must be linked with -pie
SHMAIN_LDFLAGS = -pie

DL_EXTRALIBS = -ldl

LIBDUMMY_SO_VER = 0
LIBDUMMY = libdummy.so.$(LIBDUMMY_SO_VER)

all: mainprog shim

mainprog: main.o $(LIBDUMMY) mainprog_dynamic
    $(CC) $(CFLAGS) $(SHMAIN_CFLAGS) $(LDFLAGS) $(SHMAIN_LDFLAGS) -Wl,--dynamic-list=mainprog_dynamic -o $@ $< $(LIBDUMMY) $(SHLIB_EXTRALIBS)

main.o: main.c
    $(CC) $(CPPFLAGS) $(CFLAGS) $(SHMAIN_CFLAGS) -c -o $@ $<

libdummy.so.$(LIBDUMMY_SO_VER): libdummy.so
    ln -sf $< $@

libdummy.so: dummy.o
    $(CC) -o $@ $(CFLAGS) $(LDFLAGS) $(SHLIB_LDFLAGS) -Wl,-soname,libdummy.so.$(LIBDUMMY_SO_VER) $^ $(SHLIB_EXTRALIBS)

shim: shim.o shim_dynamic
    $(CC) $(CFLAGS) $(LDFLAGS) -Wl,--dynamic-list=shim_dynamic -o $@ $< $(DL_EXTRALIBS)

test: all
    @echo "LD_LIBRARY_PATH=`pwd` ./mainprog :"
    @LD_LIBRARY_PATH=`pwd` ./mainprog
    @echo "LD_LIBRARY_PATH=`pwd` ./shim :"
    @LD_LIBRARY_PATH=`pwd` ./shim

clean:
    rm -f *.o *.so *.so.* mainprog shim

这模拟了您描述的情况,您要覆盖的函数位于单独的共享库中。它假设GNU工具链。成功构建示例(make all)后,您可以make test进行演示:

$ make test
LD_LIBRARY_PATH=/tmp/dl ./mainprog :
dummy() returns 0
LD_LIBRARY_PATH=/tmp/dl ./shim :
dummy() returns 42

*_dynamic文件告诉链接器有关两个可执行文件中应包含在导出(动态)符号中的符号,即使链接中没有任何内容引用它们。

这种方法不允许垫片直接引用主程序的internalStatus变量,因为垫片需要将主程序链接为库,它将自动加载垫片运行时的动态链接器。对变量的引用总是立即绑定,因此如果internalStatus在垫片的控制之外消失,则会导致动态链接器出错。