Shared library -- security issues
Create shared libraries with a sequence like:
gcc -c -fPIC filename.c
gcc -fPIC -shared -o libNAME.so *.o
To link a program against a shared library you do:
gcc -o program *.o -L /path-to-dir-where-lib-is -lNAME
As mater of fact, this "linking" create in program only some "calling hooks". The code from the library is not linked into the executable,
so the program is not completed. The final linkage is done by the operating system at runtime. This require that the shared library to be
present into a location so that the operating system dynamic loader can locate it for final linkage. The runtime linker ld.so will search for the
library into the paths listed into the variable LD_LIBRARY_PATH, then look into the cashed library listing /etc/ld.so.cache
and then into the standard system libraries directories:/lib and /usr/lib. To update the cache, you run ldconfig who will update the cache
with the libraries existing into the directories listed into /etc/ld.so.conf.
In case you want to create a "self contained" program you can use:
gcc -o program *.o -static -L /path-to-dir-where-lib-is -lNAME
This will physically link the code from the library into the executable, so the resulting program is self contained, i.e. no linkage
is done(or required) at runtime.
Let's say your program use the shared library libdemo.so. You linked your program against this library by regular way:
gcc -o program *.o -L /the-right-path -ldemo. If a malicious program succeed to alter your environment, it can add in front
of the LD_LIBRARY_PATH(i.e. LD_LIBRARY_PATH=/tmp/some-fake-dir/:$LD_LIBRARY_PATH) a directory where
it saved a malicious library having the same name libdemo.so and exporting all the symbols used in your program
(to avoid runtime linkage error). When you start your program, instead of good library, it get linked with the malicious library.
Another possibility of attack involve altering the LD_PRELOAD variable. This one, hold a list of libraries which will be
loaded before any other library. The symbols present in preloaded library will NOT be loaded anymore from the good library, but
otherwise, the good library symbols are used. This make the job easier to the hacker, who is now not required to reproduce
all the good library, but he can overwrite only the function of interest. This is very handily for writting a small malicious
library who is carried as payload of a virus to perform more damaging things. For example, intercepting your stdin when
"is just happen" to type your secret PGP passphrase :-) This may be really bad !!!
Hopefully, there are defenses against this kind of attack:
- Setuid programs are not vulnerable
That is. If you have a program setuid (not necessarily root, but any other user than the one using the program) the loader
will ignore LD_LIBRARY_PATH, and will honour LD_PRELOAD only for the system trusted directories. If the user receiving a virus
doesn't have w rights into the system trusted directories, the attack can't be performed.
- Linking with the full path
If the specified library is linked directly with the full path gcc -o program *.o /the-right-path/libdemo.so instead of
gcc -o program *.o -L /the-right-path -ldemo the executable will always load specified library regardless of the content of
LD_LIBRARY_PATH variable. Unfortunate, the LD_PRELOAD attack still work, and this method require that all the libraries
to be located into standard(well know) locations. So this method is more a "curiosity" rather than having any
suitable security impact for any "real life" defense.
- Using statically linked programs
Every child know: If the subject of attack doesn't exist, the attack fail :-) Well, this is the "military" solution against shared library attack.
It is however, not applicable for every program. Linking allways statically, not just waste disk space and memory but may also have some
security issues. If a security bug is discovered into a library, updating that shared library with a new version, will fix the problem
in all the programs dynamically linked against it. But, all the statically linked programs have to be recompiled and reinstalled
to fix the problem from them.
However, this method is very suitable for small programs who need special security consideration,
as the "environment controller" presented bellow.
- Running the program into a controlled environment
That is. This method is already used by some programs to insure that the environment they run is set right so the
program will locate the resource or configuration files based on the environment variables. An example is StarOffice,
who name the executable as soffice.bin and the soffice is a shell script who set the environment for the binary file.
The only problem, is that for security issues, one must not use a shell script. The problem relies in the fact that on many Unixes
/bin/bash is a dynamically linked program against libc and use the family of exec functions who are
libc functions. This enable a virus to preload before the shell script is started, and override the libc exec functions.
When the shell call exec to start the executable, the call is intercepted by the malicious library, and the security work done
by the shell is compromised. That is the reason in Sys++ came a small program /bin/uexec who
use a configuration file to start a program into a controlled environment. This program is statically linked, so any shared library
attack against it will fail. It set the environment variable so any eventual shared library attack is eliminated, then execute the real program.
And now, the real program who use dynamically linked libraries start into a clean environment, from which any possible attack was eliminated.
So far so good :-)
Another usefull technique using shared libraries are the ability to load shared libraries on request (programmatically) rather than
link your program against it. This enable you to build programs with functionality extensible at runtime, by plug-in new library.
To achieve that, you can use the libdl.so(header file dlfcn.h), who is a library providing support for runtime shared library loading. This library export 4 functions:
- void *dlopen(const char *filename, int flag);
Use this function to dynamically load a library knowing it's name.
The function return a Library handle used later to get symbols from it. In case that in dlopen you don't specify a absolute path
the LD_LIBRARY_PATH is used. The security consideration from above should apply. However, usually you use a
configuration file for storying the plug-in's, so it will be better if absolute paths are used.
- void *dlsym(void *handle, char *symbol);
Having a handle and a symbol name, you can get the address of that symbol (variable or function) defined in the library.
- int dlclose(void *handle);
unload the library. Keep in mind that any symbol from that library will not be valid anymore
after dlclose call.
- const char *dlerror(void);
Return information related any error of the above functions.
For more information type man dlopen.
A plug-in manager, should insure not only loading/unloading the libraries but also the symbol consistency. So, prevent
calling a symbol from a unloaded library. Also, there are cases when a plug-in may be updated. In this case the reload
method of the plug-in manager, should be able to keep consistency of already taken symbols since chances are that a reload will
relocate all the symbols at different addresses.
The demo presented here, will create a plug-in manage to be used for
managing task modules for the Trusted programs server. Capabilities:
- Using full name module mapping
Each module will have associate a name. Using a separate map (in real life from a config file,
but hardcoded in the demo) each module name have associated a full path name of the shared library implementing the module.
- Using safe function wrappers
The real address of a function from a module, is hidden inside of a function object.
The function object have "intimate relationsheep" with the plug-in manager, so, when a module is unloaded any
further call to any symbol taken from that module will be denied(crash prevention).
- Dynamically modules update
The plug-in manager provide a reload method to allow reloading a library
in case it was updated on the disk. This will be a safe reload, symbols wrappers being deactivated during unload reload process,
and then updated to newly values. From the "outside of plug-in manager" the changing of module will be transparent.
- Symbol overwritting
If a function from a module need to be updated, but a new module is not available,
a workaround is possible. Implement a new module having only the updated function then declare that old_func/old_module
is replaced by new_func/new_module.
The class ModuleManager keep inside a map betwen module name and a ModuleData object.
The class ModuleData keep a map betwen function name and the function pointer. To call a function we
use an ModuleFunction function object. When a ModuleFunction object is created, it register
itself into a vector inside the ModuleData object who provide the function it wrap around. When a
module is unloaded, it can set the pointers inside the all wrappers to NULL. Any further call to that wrapper will
return imediatelly. This way, we can avoid crashes that may result if we keep pointers to functions.
When a ModuleFunction object is initialised with a module a function name, it first check if there is a
overwriting record for that function/module. If yes, the new values are used to get the pointer inside.
This is the way we can insure transparently update of some functions from some module, by adding a module
who overwrite some functions.
Thedemo downloadable here, show a minimal
implementation of this plugin manager.
Back to advanced Advanced Unix programming techniques page
Sys++ Project Home page
Visit M.T.M. Home Page