Linux 不使用 chroot 临时替换C运行时库

有些时候我们需要在老旧的 Linux 系统上运行一些依赖于较新版本 C 库的应用程序或库,应用程序会因为系统中安装的C库缺少符号还启动失败。解决方法之一就是临时替换使用非系统安装的C运行时库。使用临时C库需要做些什么配置及会带来哪些问题呢?

配置步骤
1. 下载与目标应用程序版本相匹配的临时C库,解压缩到临时位置 A。
2. 需要设置 LD_LIBRARY_PATH 环境变量指向目标临时C库的存储位置 A。
3. 需要通过与临时C库匹配的 ld.so 启用应用程序。因为应用程序默认是链接了一个绝对路径的 ld.so,如 x86_64 是 /lib64/ld-linux-x86-64.so.2

衍生问题
使用临时C库的 ld.so 启动的应用程序执行系统标准命令的子进程出错,原因是因为环境变量 LD_LIBRARY_PATH 被子进程继承,从而导致子进程在执行系统C库的ld.so中加载了版本不匹配的临时C库。

解决办法
在合适的时机清除环境变量 LD_LIBRARY_PATH,最合适的时机应用就是执行目标应用程序 main 函数之前啦。这里又要用到了之前写过的方法 => Linux 平台一种进程代码注入方法

/* fakemain.c
 * Heiher <admin@heiher.info>
 */
 
#include <stdio.h>
#include <stdlib.h>
 
#define __USE_GNU
#include <dlfcn.h>
 
int
__libc_start_main(int (*main)(int, char **, char **),
			int argc, char **ubp_av, void (*init)(void),
			void (*fini)(void), void (*rtld_fini)(void),
			void (*stack_end))
{
	int (*__libc_start_main_real)(int (*main) (int, char **, char **),
				int argc, char **ubp_av, void (*init)(void),
				void (*fini)(void), void (*rtld_fini)(void),
				void (*stack_end));
 
	unsetenv ("LD_PRELOAD");
	unsetenv ("LD_LIBRARY_PATH");
 
	__libc_start_main_real = dlsym(RTLD_NEXT, "__libc_start_main");
 
	return __libc_start_main_real(main, argc, ubp_av, init, fini,
				rtld_fini, stack_end);
}
gcc -fPIC -O3 -shared -o libfakemain.so fakemain.c -ldl

设置环境变量 LD_PRELOAD=/xxx/libfakemain.so,运行目标应用程序在执行 main 之前即会清除 LD_PRELOAD 和 LD_LIBRARY_PATH 变量。

为了方便使用我还写了个 wrapper,使用方法是将真实的目标应用程序 xxx 重命令为 xxx.bin,然后创建个符号链接 xxx 指向 wrapper,执行时直接执行 xxx,wrapper 会自动设置所需要的环境变量。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
 
int
main (int argc, char *argv[])
{
	int i;
	char buf[1024], path[1024];
	char *str, *root, *args[512];
 
	/* get FAKE_ROOT */
	root = getenv ("FAKE_ROOT");
	if (!root) {
		fprintf (stderr, "Please set environment FAKE_ROOT!\n");
		return -1;
	}
 
	/* export PATH */
	str = getenv ("PATH");
	if (!str) {
		fprintf (stderr, "Get environment PATH failed!\n");
		return -2;
	}
	if (NULL == strstr (str, root)) {
		strcpy (buf, root);
		strcat (buf, "/bin:");
		strcat (buf, str);
		if (0 != setenv ("PATH", buf, 1)) {
			fprintf (stderr, "Set environment PATH failed!\n");
			return -3;
		}
	}
 
	/* export LD_PRELOAD */
	strcpy (buf, root);
	strcat (buf, "/lib64/libfakemain.so");
	if (0 != setenv ("LD_PRELOAD", buf, 1)) {
		fprintf (stderr, "Set environment LD_PRELOAD failed!\n");
		return -4;
	}
 
	/* export LD_LIBRARY_PATH */
	strcpy (buf, root);
	strcat (buf, "/lib64");
	if (0 != setenv ("LD_LIBRARY_PATH", buf, 1)) {
		fprintf (stderr, "Set environment LD_LIBRARY_PATH failed!\n");
		return -5;
	}
 
	/* set new path */
	strcpy (path, root);
	strcat (path, "/lib64/ld-2.20.so");
	args[0] = path;
 
	/* set real program path */
	strcpy (buf, root);
	strcat (buf, "/bin/");
	strcat (buf, argv[0]);
	strcat (buf, ".bin");
	args[1] = buf;
 
	/* copy arguments */
	for (i=1; i<argc; i++)
	      args[i+1] = argv[i];
	args[i+1] = NULL;
 
	/* run real program */
	return execv (path, args);;
}
gcc -O3 -o wrapper wrapper.c

Over!

Leave a Reply

Your email address will not be published. Required fields are marked *