hook io异常注入

文中code https://gitee.com/bbjg001/darcy_common/tree/master/io_hook

需求引入

最近工作需要,需要验证一下我们的服务在硬盘故障下的鲁棒性。

从同事大佬哪里了解到hook技术,可以通过LD_PRELOAD这个环境变量拦截依赖库的调用链,将对标准库函数的调用转移到自己自定义的函数,然后返回自定义的错误代码。

使用方式

export LD_PRELOAD=hook_lib.so
./main
# hook_lib.so是自行编译的用来拦截的so文件
# ./main是要运行的二进制文件

一个简单的例子

有这样一个简单的main函数

// main.cpp
#include 
#include 
#include 
#include 

int main()
{
    int rfd = open("test.txt", O_RDONLY);
    std::cout << "open(), with ret: " << rfd << ", err(" << errno << "): " << strerror(errno) << std::endl;

    close(rfd);
}

如果test.txt不存在,输出是这样的

在这里插入图片描述

如果test.txt是一个存在的正常文件,输出

在这里插入图片描述

下面自定义open函数,通过hook拦截调用链调用自己的open函数,找到open函数的定义

hook io异常注入_第1张图片

自定义open函数,注意函数传参要与原open函数一致

// hook_lib.cpp
#include   
#include  
#include 
#include 

typedef int(*OPEN)(const char *__path, int __oflag, ...);

int open(const char *__path, int __oflag, ...){
    std::cout << "!!! open函数被拦截了" << std::endl;
    errno = 2;
    return -1;
}

正常编译并运行main.cpp

g++ main.cpp -o main && ./main

输出是正常的

在这里插入图片描述

下面拦截注入自己的open函数

# 把自己的函数文件编译成.so文件
g++ --shared -fPIC  hook_lib.cpp -o hook_lib.so -ldl
# 通过LD_PRELOAD拦截调用链启动main函数
LD_PRELOAD=./hook_lib.so ./main

将hook函数的文件编译成.so文件

g++ --shared -fPIC  hook_lib.cpp -o hook_lib.so -ldl

在启动时通过LD_PRELOAD指定hook的库文件

g++ main.cpp -o main
LD_PRELOAD=./hook_lib.so ./main

在这里插入图片描述

进一步的做更多自定义的逻辑

这次以write函数为例

返回正常的write函数

可以定义在某些情况下返回错误码,某些情况下返回正常的write函数。这里通过随机概率返回两者。

hook逻辑

// hook_lib
extern ssize_t std_write (int __fd, __const void *__buf, size_t __n) {
    static void *handle = NULL;
    static WRITE old_write = NULL;
    if (!handle) {
        handle = dlopen("libc.so.6", RTLD_LAZY);
        old_write = (WRITE)dlsym(handle, "write");
    }
    return old_write(__fd, __buf, __n);
}
// 模拟的write函数
extern ssize_t write (int __fd, __const void *__buf, size_t __n) {
    if (rand() % 100 / 100.0 > 0.5) {
        errno = 2;
        return -1;
    }
    return std_write(__fd, __buf, __n);
}

main函数

// main
int main(int argc, char *argv[]){
    srand(time(NULL));
    const char *f_path = "test.txt";
    int fd = open(f_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);

    for (int i = 0; i < 10; i++){
        int ret = write(fd, "HelloWorld", 10);
        if (ret < 0){
            std::cout << "write(), with ret: " << ret << ", err_info: " << strerror(errno) << std::endl;
        }else{
            std::cout << "write(), with ret: " << ret << std::endl;
        }
    }

    close(fd);
    return 0;
}

执行结果

$ LD_PRELOAD=./hook_lib.so ./main
write(), with ret: -1, err_info: No such file or directory
write(), with ret: 10
write(), with ret: -1, err_info: No such file or directory
write(), with ret: -1, err_info: No such file or directory
write(), with ret: 10
write(), with ret: -1, err_info: No such file or directory
write(), with ret: -1, err_info: No such file or directory
write(), with ret: 10
write(), with ret: -1, err_info: No such file or directory
write(), with ret: 10

控制注入异常的path

hook逻辑

// hook_lib
// 检查当前操作的文件是否是要注入异常的文件
bool is_current_path(int fd, std::string path){    
    if(path==""){
        return false;
    }

    // get current path
    char buf[256] = {'\0'};
    char _file_path[256] = {'\0'};
    std::string file_path;
    snprintf(buf, sizeof (buf), "/proc/self/fd/%d", fd);
    if (readlink(buf, _file_path, sizeof(_file_path) - 1) != -1) {
        file_path = _file_path;
    }
    if(file_path.find(path) != std::string::npos){  // 路径中包含${path}即被命中
        return true;
    }
    return false;
}

extern ssize_t write (int __fd, __const void *__buf, size_t __n) {
    if (is_current_path(__fd, "test")) {
        errno = 2;
        return -1;
    }
    return std_write(__fd, __buf, __n);
}

main函数

// main
int main(int argc, char *argv[]){

    const char *f_path = argv[1];
    int fd = open(f_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);

    int ret = write(fd, "HelloWorld", 10);
    if (ret < 0){
        std::cout << "write(), with ret: " << ret << ", err_info: " << strerror(errno) << std::endl;
    }else {
        std::cout << "write(), with ret: " << ret << std::endl;
    }

    close(fd);
    return 0;
}

执行结果

$ LD_PRELOAD=./hook_lib.so ./main test.txt   
write(), with ret: -1, err_info: No such file or directory

$ LD_PRELOAD=./hook_lib.so ./main newfile.txt
write(), with ret: 10

延时返回

这里比较简单不再做代码示例

sleep(time_s);		// 秒
usleep(time_ms);	// 微秒

动态控制异常注入

希望能从第三方位置读取配置,通过变更配置动态的对指定path注入指定的错误(码)类型。

从文件获得配置

hook逻辑

// hook_lib
void get_ctrl_var_file(std::string *path, int *eno, int *sleep_time){
    std::ifstream ifs("conf.txt");
    ifs >> *path;
    ifs >> *eno;
    ifs >> *sleep_time;
    ifs.close();
}

extern ssize_t write (int __fd, __const void *__buf, size_t __n) {
    std::string epath;
    int eno, ehang_time;
    get_ctrl_var_file(&epath, &eno, &ehang_time);
    if (is_current_path(__fd, epath)) {
        errno = eno;
        hang_sleep(ehang_time);
        return -1;
    }
    return std_write(__fd, __buf, __n);
}

main函数

// main
int main(int argc, char *argv[]){

    const char *f_path = argv[1];
    int fd = open(f_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);

    int ret = write(fd, "HelloWorld", 10);
    if (ret < 0){
        std::cout << "write(), with ret: " << ret << ", err_info: " << strerror(errno) << std::endl;
    }else {
        std::cout << "write(), with ret: " << ret << std::endl;
    }

    close(fd);
    return 0;
}

conf.txt

test.txt 2 1000000

执行结果

$ LD_PRELOAD=./hook_lib.so ./main test.txt   
write(), with ret: -1, err_info: No such file or directory

$ LD_PRELOAD=./hook_lib.so ./main newfile.txt
write(), with ret: 10

从redis获得配置

hook逻辑

#include 
// hook_lib
void get_ctrl_var_redis(std::string *path, int *eno, int *sleep_time){
    redisContext *conn  = redisConnect("127.0.0.1", 6379);
    if(conn != NULL && conn->err)
    {
        printf("connection error: %s\n",conn->errstr);
        return;
    }

    redisReply *reply = (redisReply*)redisCommand(conn,"get %s", "/hook/write/epath");
    *path = reply->str;
    reply = (redisReply*)redisCommand(conn,"get %s", "/hook/write/eno");
    *eno = std::atoi(reply->str);
    reply = (redisReply*)redisCommand(conn,"get %s", "/hook/write/ehang");
    *sleep_time = std::atoi(reply->str);
     
    freeReplyObject(reply);
    redisFree(conn);
}

extern ssize_t write (int __fd, __const void *__buf, size_t __n) {
    std::string epath;
    int eno, ehang_time;
    get_ctrl_var_redis(&epath, &eno, &ehang_time);
    if (is_current_path(__fd, epath)) {
        errno = eno;
        hang_sleep(ehang_time);
        return -1;
    }
    return std_write(__fd, __buf, __n);
}

main函数

// main
int main(int argc, char *argv[]){

    const char *f_path = argv[1];
    int fd = open(f_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);

    int ret = write(fd, "HelloWorld", 10);
    if (ret < 0){
        std::cout << "write(), with ret: " << ret << ", err_info: " << strerror(errno) << std::endl;
    }else {
        std::cout << "write(), with ret: " << ret << std::endl;
    }

    close(fd);
    return 0;
}

在redis中添加如下变量

set /hook/write/epath test.txt
set /hook/write/eno 5
set /hook/write/ehang 1000000

执行结果

$ LD_PRELOAD=./hook_lib.so ./main test.txt 
write(), with ret: -1, err_info: Input/output error

$ LD_PRELOAD=./hook_lib.so ./main newfile.txt
write(), with ret: 10

in mac os

在mac os中需要使用其他的环境变量进行注入,简单试了下没能成功,抛砖引玉

https://stackoverflow.com/questions/34114587/dyld-library-path-dyld-insert-libraries-not-working

参考

https://blog.51cto.com/u_15703183/5464438

https://sq.sf.163.com/blog/article/173506648836333568

https://xz.aliyun.com/t/6883

https://www.cnblogs.com/wainiwann/p/3340277.html

你可能感兴趣的:(测试开发,hook,io异常,LD_PRELOAD,测试开发,异常测试)