0

I would like to be able to control from inside my library if it is allowed to be loaded or not without using exceptions, meaning for some cases i would like dlopen("mylib.so") to return NULL and only if all conditions are right it will succeed.

Many have asked about the motivation, 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.

Take in mind i have to use standard solutions, meaning i can't use external plugins or do things like rewriting dlopen.

Pavel
  • 31
  • 6
  • 2
    why would you need that don't call it if you don't need it to succeed – Tyker Jun 14 '18 at 07:14
  • By the way, I take it you are trying to test/debug something, maybe force a code path. This feature will not make it to production, will it? – Attersson Jun 14 '18 at 07:20
  • Right your solution to create a fake or mocked return is the same as what i thought. wrappers should work for testing purposes like this. – Hello Everyone Jun 14 '18 at 07:25
  • Without explicit motivations, the question is unclear. – Basile Starynkevitch Jun 14 '18 at 07:58
  • 1
    Solaris provides the capability of using a shared object as a filter to load other shared object(s). I'm pretty sure Solaris uses this internally to provide hardware-specific features/optimizations in system libraries. See https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter4-4.html I'm not sure what the equivalent Linux functionality is, but you can probably do some explicit loading of libraries in your own library: `if ( x ) dlopen( "/path/to/libA.so" ) else dlopen( "/path/to/libB.so" );` – Andrew Henle Jun 14 '18 at 11:15
  • Am I the only one to find the question very unclear, because it lacks motivation? I need to guess why the OP is asking it to write some answer! I feel this question should either be improved a lot by the OP (who should give detailed explanations about motivations and context), or closed since unclear and useless. – Basile Starynkevitch Jun 14 '18 at 12:08
  • Even with the recent edit, your question is unclear and you should build some [MCVE] in it. It could be that you don't realize that a shared object can be linked to other shared objects. – Basile Starynkevitch Jun 16 '18 at 10:36

4 Answers4

3

This is probably some XY problem. We cannot guess your motivation and goals (and these are what really matters). What you want to do is not possible on Linux.

But read carefully and several times the 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). Notice that there is no documented way to make the dlopen fail outside of the documented failure cases, that is:

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.

Then you can use dlerror(3) to understand the error cause.

So you could play wild tricks with that LD_LIBRARY_PATH but you should not. You might be weird (actually crazy) enough to e.g. use 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. But you really should not do such insane tricks.

So be reasonable: solve your actual need in some other ways. Expect dlopen to behave as documented (so to usually succeed), and don't call it if you don't want to. When coding, it is important to use your common sense.

Be aware of 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.

Notice that in practice dlopen is part of your 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). So if you are not happy with the system's dlopen, you could in principle change it (but I don't recommend doing that, since libc is the cornerstone of every Linux system).

You could consider (probably not a good idea) to use some ELF parsing library (like 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) format yourself and do such a parsing by hand (probably even more bad idea).

If you are writing that mylib.so library (on Linux, and perhaps some other similar OSes; but this behaviour is non-standard since non-specified in POSIX dlopen), you could be interested by function attributes like __attribute__((constructor)) & __attribute__((visibility)) (see also this and that). If you want to "reject" being dlopen-ed (when some conditions are met) from your mylib.so you could consider having some constructor function testing these conditions and calling exit when they fail. If your mylib.so is a plugin loaded from some other program that you could improve, you might simply seek some initialization function with dlsym, call it after the dlopen, and fail the main program if that initialization function failed. BTW, throwing some C++ exception from such a constructor function (or some obsolete _init one) is unwise, because the dlopen machinery might consume internal resources that won't be released in that case.

At last, in theory, you could re-implement dlopen yourself (above 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.

You probably can achieve your (unstated) goal by just using the usual dlopen, and do some test before it, and perhaps some test (using dlsym) after it. Most programs using plugins are doing that.

Perhaps you might have every exported function of mylib.so do appropriate checks when running. Maybe you could have some static boolean flag set by some function with __attribute__((constructor)) (so it would be called once at dlopen time) and have other public functions check that flag.

In a recent edit you explain at last:

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.

There is no need to play with dlopen or its constructors (and you probably don't need to use dlopen inside your library; and if you do that, you need to explain why, how, and where). You just link mylib.so with all the required shared libraries it uses (see this). If they are not loadable or accessible at dlopen time the entire dlopen of mylib.so fails (intuitively, on Linux, dynamic loading is somehow "recursive").

BTW, if you indeed call dlopen inside your mylib.so, that dlopen happens after mylib.so has been dlopen-ed (unless you call dlopen from some constructor function of mylib.so, which is weird but should be ok and makes a different question).

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • 1
    +1. Plugins often have a single identifiable exported symbol, which is either a structure describing the plugin or an initialization function that will return/fill such a structure. Aborting plugin load is done there. – spectras Jun 14 '18 at 09:52
  • This is exactly what i do now, i use a constructor for the library ( __attribute__((constructor)) ) and i have an init function (extracted with dlsym) that will return whether the library has loaded correctly or not. This is the trivial solution which i wanted to shorten by failing the constructor and causing dlopen to return NULL. Many have asked about the motivation, 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. – Pavel Jun 16 '18 at 09:42
  • You need to improve your question by telling all this (and give some [MCVE]) – Basile Starynkevitch Jun 16 '18 at 09:44
  • Also, I don't understand exactly what you mean by "failing the constructor" – Basile Starynkevitch Jun 16 '18 at 09:50
  • __attribute__((constructor)) void myConstructor() { if(something failed) return failure but cant because this is a void function; } – Pavel Jun 16 '18 at 09:53
  • Please edit your question and give some [MCVE]. Your comments are not understandable. You probably need to add several paragraphs into your question to make it understandable. A constructor function cannot fail; it could `exit(EXIT_FAILURE)` (but you don't want it to `throw` some C++ exception) – Basile Starynkevitch Jun 16 '18 at 09:54
2

As far as I remember, no, you can't, at least in a normal way. If the library exists at given path, it will be loaded. The dlopen it is not designed to do any "business checks" for you at your discretion. At most, it will abide any filesystem permissions/etc and return error if process has no access to the file, and that's it.

If you have full control on the code that will load your library, then wrap it like Atterson suggested and use dlopen2 and it's done.

If you don't have full control, then dlopen2 would still not prevent anyone from using the original dlopen and bypass the checks. You could try to make it more smart, for example, make your dlopen2 do something detectable so then the library can deny working if it was opened by dlopen instead of dlopen2, but then.. someone could fake up that "something detectable", then use dlopen, done. Then it boils down to making that "something detectable" hard to reproduce by attackers.

Simply, it was not intended to do these things. It's meant to load the library if the OS allows (~filesystem permissions, etc).

For any other "access checks" like "do you have license? no? then go away" you have to implement it inside the library. Let them load it via dlopen, then make the library check the permissions for example, at each call to its functions. Do use exceptions, or just do-nothing and return NULLs. Or even better, you probably could use initialization function (see https://stackoverflow.com/a/1602459/717732 + http://tldp.org/HOWTO/Program-Library-HOWTO/miscellaneous.html#INIT-AND-CLEANUP) and do the check once when the lib gets loaded. Note that this functions take and return void so there's still no way to make the dlopen fail, but at least the library can have its moment to disable its functions.

quetzalcoatl
  • 32,194
  • 8
  • 68
  • 107
  • Very right. I think this kind of question easily carries one away... while all OP was trying to do is some debug. Or he can edit dlfcn source and recompile. Boom. Custom solution :) (actually it's not that easy, just saying) – Attersson Jun 14 '18 at 07:50
1

You could always wrap it into a function

void *dlopen2(const char *filename, int flags){
    if(/*your conditions*/)
        return void *dlopen(filename, flags);
    return (void *) NULL;
}
Attersson
  • 4,755
  • 1
  • 15
  • 29
  • And make everyone in the whole world use it instead of true `dlopen`? – quetzalcoatl Jun 14 '18 at 07:16
  • The point is, it cannot be done to full extent. It's not the purpose of dlopen. – quetzalcoatl Jun 14 '18 at 07:17
  • 1
    I think it's an option still worth considering since OP said "inside my library"... and this question smells like debug. Doesn't it? – Attersson Jun 14 '18 at 07:19
  • 1
    `#define dlopen(filename, flags) dlopen2(filename, flags)` Oh yes, that's ugly as ****. The only way to call the original dlopen is to do as follow : `(dlopen)(filename, flags);` – Tom's Jun 14 '18 at 07:23
  • hmm.. re-reading `meaning for some cases i would like` a few times, yeah, maybe `dlopen2` is enough.. if it was important, I think he'd say something like "license check" etc. – quetzalcoatl Jun 14 '18 at 07:24
  • `dlopen` is anyway defined a weak symbol. So creating a library with a function by the same name and using while linking should also do it. No need for `#define`s – Ajay Brahmakshatriya Jun 14 '18 at 07:44
1

The correct way to deal with this kind of scenarios, is to make use of seccomp and limit what the dlopen call is allowed to do.

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

Naturally such configuration requires root access.

Paulo Pinto
  • 632
  • 4
  • 10
  • 1
    Interesting.. if I understand well, that's a tool that allows a process with admin access to install a security filter rule in the OS that will selectively allow/prevent doing things, like, using dlopen-on-my-library. That would work, but that requires that to properly handle 'permissions' for loading my foo.so I have to install the filter. What will happen if I don't? `foo.so` would be loaded just like normal. `foo.so` would then need to check at its startup if the seccomp rule was installed, and if not, it would need to fail somehow - so we're back to square one like I wrote in my answer? – quetzalcoatl Jun 14 '18 at 07:39