如何在C程序中查询当前进程的库搜索路径?
在FreeBSD的C程序中查询当前进程的库搜索路径(用于Capsicum沙箱)
问题背景
我正在尝试基于Capsicum创建沙箱来加载插件,目前大部分流程已经跑通:
- 创建通信管道
- 调用
fork()
子进程侧的操作步骤:
- 关闭不需要的资源
- 调用
cap_enter() - 调用
fdlopen()
当插件是自包含状态(所需符号都在主程序中)时,整个流程正常工作。但如果插件依赖系统库,加载就会失败。FreeBSD的rtld提供了LD_LIBRARY_PATH_FDS环境变量,作为Capsicum模式下可用的额外库路径,我可以在子进程的C程序中设置这个变量。
现在需要在调用cap_enter()前查询当前进程的库搜索路径,打开对应的文件描述符,请问如何在C程序中实现这个查询?
解决方案
方法1:用dl_iterate_phdr遍历已加载的动态库
FreeBSD提供的dl_iterate_phdr()函数可以遍历当前进程加载的所有动态链接库信息,从中提取库所在目录路径。示例代码如下:
#define _GNU_SOURCE #include <link.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> typedef struct { char **paths; size_t count; size_t capacity; } LibPathCollector; static int collect_lib_dirs(struct dl_phdr_info *info, size_t size, void *data) { LibPathCollector *collector = (LibPathCollector *)data; char *lib_path = strdup(info->dlpi_name); if (lib_path == NULL) { perror("strdup failed"); return 1; } // 跳过主程序本身的空路径 if (strlen(lib_path) == 0) { free(lib_path); return 0; } // 提取库所在的目录 char *dir_path = strdup(dirname(lib_path)); free(lib_path); if (dir_path == NULL) { perror("strdup dirname failed"); return 1; } // 去重,避免重复添加相同目录 int exists = 0; for (size_t i = 0; i < collector->count; i++) { if (strcmp(collector->paths[i], dir_path) == 0) { exists = 1; break; } } if (exists) { free(dir_path); return 0; } // 动态扩容数组 if (collector->count >= collector->capacity) { collector->capacity = collector->capacity == 0 ? 8 : collector->capacity * 2; char **new_paths = realloc(collector->paths, collector->capacity * sizeof(char *)); if (new_paths == NULL) { perror("realloc failed"); free(dir_path); return 1; } collector->paths = new_paths; } collector->paths[collector->count++] = dir_path; collector->paths[collector->count] = NULL; return 0; } char **get_current_lib_dirs(void) { LibPathCollector collector = {NULL, 0, 0}; if (dl_iterate_phdr(collect_lib_dirs, &collector) != 0) { // 遍历出错,清理已分配内存 if (collector.paths != NULL) { for (size_t i = 0; i < collector.count; i++) { free(collector.paths[i]); } free(collector.paths); } return NULL; } return collector.paths; } // 使用示例 int main() { char **lib_dirs = get_current_lib_dirs(); if (lib_dirs == NULL) { fprintf(stderr, "Failed to collect library directories\n"); return 1; } // 打开每个目录的文件描述符,用于后续设置LD_LIBRARY_PATH_FDS int *fds = malloc(sizeof(int) * 16); // 按需调整大小 size_t fd_count = 0; for (size_t i = 0; lib_dirs[i] != NULL; i++) { int fd = open(lib_dirs[i], O_RDONLY | O_DIRECTORY); if (fd != -1) { fds[fd_count++] = fd; } else { fprintf(stderr, "Failed to open %s: %s\n", lib_dirs[i], strerror(errno)); } } // 构建LD_LIBRARY_PATH_FDS的值 char fd_str[256] = {0}; for (size_t i = 0; i < fd_count; i++) { if (i > 0) strcat(fd_str, ":"); char buf[16]; snprintf(buf, sizeof(buf), "%d", fds[i]); strcat(fd_str, buf); } setenv("LD_LIBRARY_PATH_FDS", fd_str, 1); // 后续执行cap_enter()和fdlopen()... // 资源清理 for (size_t i = 0; lib_dirs[i] != NULL; i++) { free(lib_dirs[i]); } free(lib_dirs); free(fds); return 0; }
方法2:读取/proc/self/maps(需挂载procfs)
如果系统已经挂载procfs(通常在/proc目录),可以读取/proc/self/maps文件解析已加载库的路径,再提取目录:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> char **get_lib_dirs_from_proc(void) { FILE *f = fopen("/proc/self/maps", "r"); if (f == NULL) { perror("fopen /proc/self/maps failed"); return NULL; } char **dirs = NULL; size_t count = 0; char line[1024]; while (fgets(line, sizeof(line), f) != NULL) { // 定位路径起始位置(以/开头) char *path_start = strchr(line, '/'); if (path_start == NULL) continue; // 去除换行符 size_t len = strlen(path_start); if (path_start[len-1] == '\n') path_start[len-1] = '\0'; // 仅处理.so结尾的动态库文件 if (strstr(path_start, ".so") == NULL) continue; // 提取目录 char *dir_path = strdup(dirname(path_start)); if (dir_path == NULL) { perror("strdup dirname failed"); goto cleanup; } // 去重 int exists = 0; for (size_t i = 0; i < count; i++) { if (strcmp(dirs[i], dir_path) == 0) { exists = 1; break; } } if (exists) { free(dir_path); continue; } // 扩容数组 dirs = realloc(dirs, (count + 2) * sizeof(char *)); if (dirs == NULL) { perror("realloc failed"); free(dir_path); goto cleanup; } dirs[count++] = dir_path; dirs[count] = NULL; } fclose(f); return dirs; cleanup: if (dirs != NULL) { for (size_t i = 0; i < count; i++) free(dirs[i]); free(dirs); } fclose(f); return NULL; }
方法3:调用rtld内部函数_rtld_get_paths()
FreeBSD的rtld提供了内部函数_rtld_get_paths(),可以直接获取系统默认的库搜索路径,需要链接时加上-ldl参数:
#include <dlfcn.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> // 声明rtld内部函数 extern char **_rtld_get_paths(void); int main() { char **default_paths = _rtld_get_paths(); if (default_paths == NULL) { fprintf(stderr, "Failed to get default library paths\n"); return 1; } // 打开目录fd并设置LD_LIBRARY_PATH_FDS int *fds = malloc(sizeof(int) * 8); size_t fd_count = 0; for (size_t i = 0; default_paths[i] != NULL; i++) { int fd = open(default_paths[i], O_RDONLY | O_DIRECTORY); if (fd != -1) { fds[fd_count++] = fd; } } // 构建环境变量值 char fd_str[128] = {0}; for (size_t i = 0; i < fd_count; i++) { if (i > 0) strcat(fd_str, ":"); char buf[8]; snprintf(buf, sizeof(buf), "%d", fds[i]); strcat(fd_str, buf); } setenv("LD_LIBRARY_PATH_FDS", fd_str, 1); // 后续执行cap_enter()和fdlopen()... free(fds); return 0; }
注意:_rtld_get_paths()是内部函数,不同FreeBSD版本可能存在兼容性问题,需要测试验证。
内容的提问来源于stack exchange,提问作者vinipsmaker
相关产品推荐
相关产品推荐

