You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:06:52