有没有办法让 dlopen 故意失败

is there a way to make dlopen fail intentionally

我希望能够在不使用异常的情况下从我的库内部控制是否允许加载它,这意味着在某些情况下我希望 dlopen("mylib.so") 到 return NULL,只有当所有条件都正确时,它才会成功。

许多人询问了动机,在我的库中我多次使用 dlopen,我想确保在加载我的库之前已加载所有需要的组件。

请记住,我必须使用标准解决方案,这意味着我不能使用外部插件或做重写 dlopen 之类的事情。

你总是可以把它包装成一个函数

void *dlopen2(const char *filename, int flags){
    if(/*your conditions*/)
        return void *dlopen(filename, flags);
    return (void *) NULL;
}

这大概是一些XY problem。我们无法猜测您的动机和目标(这些才是真正重要的)。你想做的是不可能 Linux。

仔细阅读并多次dlopen(3) man page. You'll notice that since "mylib.so" has no / it is handled specifically by using the LD_LIBRARY_PATH environment variable. That is why I generally use an absolute file path for dlopen. See e.g. realpath(3), glob(3), wordexp(3)。请注意,没有记录的方法可以使 dlopen 在记录的失败案例之外失败 ,即:

On success, dlopen() and dlmopen() return a non-NULL handle for the loaded library. On error (file could not be found, was not readable, had the wrong format, or caused errors during loading), these functions return NULL.

然后可以使用dlerror(3)了解错误原因。

所以你可以用那个 LD_LIBRARY_PATH 玩一些疯狂的把戏,但你不应该这样做。你可能很奇怪(实际上是疯了),例如使用 putenv(3) on that, or add into that path a directory inside some FUSE filesystem managed by your program, or do some LD_PRELOAD trick。但是你真的不应该做这种疯狂的把戏.

所以要合理:通过其他一些方式解决您的实际需求。期望 dlopen 的行为与记录的一样(因此通常会成功),如果您不想,请不要调用它。编码时,使用常识很重要。

注意 rpath (it could be explicitly set at link time), and read carefully Program Library HowTo and Drepper's How To Write Shared Libraries. Read also the C++ dlopen minihowto and be aware of name mangling

请注意,实际上 dlopen 是您 C standard library on Linux, and that libc and ld-linux.so(8) is generally some free software (e.g. GNU glibc or musl-libc 的一部分)。所以如果你对系统的 dlopen 不满意,原则上你可以改变它(但我不建议这样做,因为 libc 是每个 Linux 系统的基石)。

你可以考虑(可能不是一个好主意)使用一些 ELF 解析库(比如 libelf, libbfd, ...) or some ELF analyzing program (like readelf(1) or objdump(1) ...) on that shared object before your dlopen (but a malicious process or user might still alter the shared library after the analysis but before dlopen). You could study the elf(5) 自己格式化并手动进行这样的解析(可能更糟糕)。

如果您正在编写 mylib.so 库(在 Linux 上,也许还有一些其他类似的操作系统;但这种行为是非标准的,因为在 POSIX dlopen), you could be interested by function attributes like __attribute__((constructor)) & __attribute__((visibility)) (see also this and that 中未指定)。如果你想 "reject" 从 你的 mylib.sodlopen 编辑(当满足某些条件时),你可以考虑使用一些构造函数来测试这些条件并在失败时调用 exit。如果您的 mylib.so 是从您可以改进的其他程序加载的插件,您可以简单地使用 dlsym 寻找一些初始化函数,在 dlopen 之后调用它,如果该初始化函数失败。顺便说一句,从这样的构造函数(或一些过时的 _init 函数)中抛出一些 C++ 异常是不明智的,因为 dlopen 机制可能会消耗在这种情况下不会释放的内部资源。

最后,理论上,您可以自己重新实现 dlopen(在 open(2), mmap(2) etc... and care about the relocations in ELF explicitly). That could take a few years (and is processor specific). Study the appropriate x86 ABI.

之上

你可能可以通过使用通常的 dlopen 来实现你的(未说明的)目标,并在它之前做一些测试,也许在它之后做一些测试(使用 dlsym)。大多数使用插件的程序都是这样做的。

也许您可以让 mylib.so 的每个导出函数在 运行 时进行适当的检查。也许你可以通过一些带有 __attribute__((constructor)) 的函数设置一些静态布尔标志(因此它会在 dlopen 时间被调用一次)并让其他 public 函数检查该标志。

在最近的编辑中你终于解释了:

inside my library i use dlopen several times and i want to make sure all needed components have been loaded before my library can be loaded.

无需使用 dlopen 或其构造函数(并且您可能不需要在库中使用 dlopen;如果您这样做,则需要解释原因、方式和地点)。您只需 link mylib.so 及其使用的所有必需的共享库(请参阅 this). If they are not loadable or accessible at dlopen time the entire dlopen of mylib.so fails (intuitively, on Linux, dynamic loading 不知何故 "recursive")。

顺便说一句,如果您确实在 mylib.so 中调用了 dlopen,那么 dlopen 发生在 之后 mylib.so 已经 dlopen-ed(除非你从 mylib.so 的某个构造函数调用 dlopen,这很奇怪但应该没问题,并且提出了 不同的 问题)。

处理这种情况的正确方法是利用 seccomp 并限制 dlopen 调用允许执行的操作。

http://man7.org/linux/man-pages/man3/seccomp_rule_add.3.html

当然这样的配置需要root权限。

据我所知,不,你不能,至少以正常的方式。如果库存在于给定路径,它将被加载。 dlopen 它并非旨在为您自行决定做任何 "business checks"。最多,如果进程无法访问文件,它将遵守任何文件系统 permissions/etc 和 return 错误,仅此而已。

如果您可以完全控制将加载您的库的代码,那么按照 Atterson 的建议将其包装并使用 dlopen2 即可完成。

如果您没有完全控制权,那么 dlopen2 仍然不会阻止任何人使用原始 dlopen 并绕过检查。你可以试着让它更聪明,例如,让你的 dlopen2 做一些可检测的事情,这样如果库是由 dlopen 而不是 dlopen2 打开的,那么库可以拒绝工作,但是然后。 . 有人可以伪造 "something detectable",然后使用 dlopen,完成。然后归结为使 "something detectable" 难以被攻击者复制。

很简单,它不是为了做这些事情。如果 OS 允许(~文件系统权限等),它意味着加载库。

对于任何其他 "access checks",例如 "do you have license? no? then go away",您必须 在库中实现它 。让他们通过 dlopen 加载它,然后让库检查权限,例如,在每次调用其函数时。一定要使用异常,或者什么都不做和 return NULL。或者甚至更好,您可能可以使用 initialization 函数(请参阅 + http://tldp.org/HOWTO/Program-Library-HOWTO/miscellaneous.html#INIT-AND-CLEANUP)并在加载库时进行一次检查。请注意,此函数采用 return void,因此仍然无法使 dlopen 失败,但至少库可以有时间禁用其函数。