LD_PRELOAD库拦截execve时环境变量过滤的问题求助
问题分析与修复方案
嘿,我来帮你搞定这两个问题——咱们先解决最紧急的内存错误,再处理LD_PRELOAD重复拼接的问题。
1. 内存错误:munmap_chunk(): invalid pointer
你代码里的free(string);是致命问题!envp数组里的字符串是操作系统维护的进程环境变量内存,根本不是你用malloc分配的,直接free会破坏进程的内存结构,触发内存错误。把这行代码删掉,这个问题就解决了。
2. LD_PRELOAD重复拼接的问题
你的逻辑是把当前进程的LD_PRELOAD路径和原envp里的LD_PRELOAD值拼接,但没做去重检查,导致每次execve都会重复添加。另外还有几个小细节需要修正:
核心修正点:
- 避免重复初始化
real_execve:用static变量存储原函数指针,第一次调用时才初始化,避免多次调用dlsym。 - 正确处理LD_PRELOAD的去重与继承:直接复用当前进程的
LD_PRELOAD值(这个值已经包含我们的库),替换原envp里的LD_PRELOAD,或者在原envp没有时新增,彻底避免重复拼接。 - 内存泄漏修复:
execve成功后系统会接管新envp的内存,但如果调用失败,要手动释放我们动态分配的内存。
修复后的完整execve代码
#define _GNU_SOURCE #include <dlfcn.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> static int (*real_execve)(const char *filename, char *const argv[], char *const envp[]); int execve(const char *filename, char *const argv[], char *const envp[]) { // 仅初始化一次原execve函数指针 if (!real_execve) { real_execve = dlsym(RTLD_NEXT, "execve"); if (!real_execve) { fprintf(stderr, "dlsym error: %s\n", dlerror()); exit(EXIT_FAILURE); } } const char *our_preload = getenv("LD_PRELOAD"); if (!our_preload) { // 当前进程无LD_PRELOAD,直接调用原函数 return real_execve(filename, argv, envp); } fprintf(stderr, "INTERCEPTED execve, env:\n"); // 统计原envp的长度 int env_len = 0; while (envp[env_len] != NULL) env_len++; // 分配新的envp数组 char **nenvp = malloc((env_len + 1) * sizeof(char*)); if (!nenvp) { perror("malloc nenvp"); return -1; } nenvp[env_len] = NULL; int found_preload = 0; for (int i = 0; envp[i] != NULL; i++) { char *env_str = envp[i]; // 复制一份字符串用于拆分,避免修改原系统内存 char *buf = strdup(env_str); if (!buf) { perror("strdup buf"); // 出错时清理已分配内存 for (int j = 0; j < i; j++) { if (nenvp[j] != envp[j]) free(nenvp[j]); } free(nenvp); return -1; } char *name = strtok(buf, "="); if (name && strcmp(name, "LD_PRELOAD") == 0) { found_preload = 1; // 直接使用当前进程的LD_PRELOAD值,确保我们的库存在且不重复 char *new_preload = strdup(our_preload); if (!new_preload) { perror("strdup new_preload"); free(buf); // 清理已分配内存 for (int j = 0; j < i; j++) { if (nenvp[j] != envp[j]) free(nenvp[j]); } free(nenvp); return -1; } nenvp[i] = new_preload; fprintf(stderr, " FIXED LD_PRELOAD to: '%s'\n", new_preload); } else { // 其他环境变量直接复用原指针 nenvp[i] = env_str; fprintf(stderr, " LEFT: '%s'\n", env_str); } free(buf); } // 如果原envp没有LD_PRELOAD,就新增一个 if (!found_preload) { char **new_nenvp = realloc(nenvp, (env_len + 2) * sizeof(char*)); if (!new_nenvp) { perror("realloc new_nenvp"); free(nenvp); return -1; } nenvp = new_nenvp; char *preload_entry = malloc(strlen("LD_PRELOAD=") + strlen(our_preload) + 1); if (!preload_entry) { perror("malloc preload_entry"); free(nenvp); return -1; } strcpy(preload_entry, "LD_PRELOAD="); strcat(preload_entry, our_preload); nenvp[env_len] = preload_entry; nenvp[env_len + 1] = NULL; fprintf(stderr, " ADDED LD_PRELOAD: '%s'\n", preload_entry); } fprintf(stderr, " CALLING execve for: %s\n", filename); int ret = real_execve(filename, argv, nenvp); // 如果execve失败,释放我们分配的内存 if (ret == -1) { for (int i = 0; nenvp[i] != NULL; i++) { if (nenvp[i] != envp[i]) free(nenvp[i]); } free(nenvp); } return ret; }
关键说明
- 内存安全:只释放我们自己用
malloc/strdup分配的内存,绝不触碰系统提供的envp指针。 - 去重逻辑:直接复用当前进程的
LD_PRELOAD值,确保我们的库始终存在且不会重复拼接。 - 基础线程安全:用
static变量存储原函数指针,避免多线程下多次调用dlsym的问题(如需更严格的线程安全,可改用pthread_once)。
编译与测试建议
编译时记得链接dl库:
gcc -shared -fPIC -o preload_blocker.so preload_blocker.c -ldl
测试时可以用:
LD_PRELOAD=./preload_blocker.so bash
之后在bash里执行echo $LD_PRELOAD或启动其他进程,就能看到LD_PRELOAD被正确继承了。
内容的提问来源于stack exchange,提问作者nonchip
相关产品推荐
相关产品推荐

