C-Externs-监控LD_PRELOAD库中的值的安全方法 [英] C - Externs - Safe way to monitor value in LD_PRELOAD'ed library

查看:0
本文介绍了C-Externs-监控LD_PRELOAD库中的值的安全方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

背景

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

LD_PRELOAD=/public/libdisksupervisor.so /sbin/diskmanager

我们这样做的原因是,库和应用程序的发布计划非常不同,由于存在跨NDA而无法共享源代码等。为了方便我们的工作,diskmanager的维护人员在应用程序中创建了几个extern变量,并添加了对它们与diskmanager捆绑在一起的库(libdonothing.so)中的";哑元&函数的一些调用。

当调用int dummy(void)(通常可以在libdonothing.so中找到,但我们通过LD_PRELOADinglibdisksupervisor.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的一个维护者做了一些愚蠢的事情,并从diskmanager中删除了int internalStatus,导致我们的库在执行LD_PRELOAD=/public/libdisksupervisor.so diskmanager时导致分段错误。当一名初级工程师笨手笨脚地处理GCC的隐藏属性并将某些值更改为static时,也出现了类似的问题,并再次导致段故障。


问题

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

谢谢。

推荐答案

在libdiskSupervisor中的代码中,有没有什么方法可以在继续之前测试这些外部变量(从我们的库的角度)的存在,可能是通过一些神秘的链接器或GCC魔术?

您在这里遇到了时间问题。事实上,您不需要做任何特殊的事情来测试在编译时,在您所链接的diskmanager版本中的存在和可见性。当您尝试将libdisksupervisor.so与运行时不兼容的diskmanager版本一起使用时,会出现此问题。

我知道我可以将nm或objump作为预验证脚本的一部分抛给它,但我们需要仅在我们的c库中完成这项工作。

我不知道有任何方法适用于您运行程序的方式,并且不容易被diskmanager维护意外挫败。

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


更新:

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

总之,POC由三个C源文件、两个辅助文件和一个Makefile组成:

ummy.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
", 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
", 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
aborting
", 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消失,则会导致动态链接器出现错误。

这篇关于C-Externs-监控LD_PRELOAD库中的值的安全方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆