Somehow, lately I’ve had a few more inquiries about runpaths than usual — all for different packages, which makes it quite a bit more interesting. Even though not all the questions regarded Gentoo’s “insecure runpaths” handling, I think it might be well worth it to write a bit more about it. Before going into details about it, though, I’ll point you to my previous post for a description of what runpaths (and rpath) are, instead of repeating it all here.
So, what is an insecure runpath? Basically any runpath set to a directory where an attacker may have control is insecure, because it allows an attacker to load an arbitrary shared object which can then run arbitrary code. The definition of attacker, as I already discussed is flexible depending on the situation.
Since runpaths are, as the name suggest, paths on the filesystem, there are two main starting points that would cause a path to become insecure: runpaths derived from the current working directory, and runpaths derived from any world-writable directory. The ability for an attacker to place the correct object in the correct path varies considerably, but it’s a good rule of thumb to consider the both of them just as bad. What happens for packages built in Gentoo often enough, is for the runpath to include the build directory in its runpath, which could be either /var/tmp
or /tmp
(if you, for instance, build in tmpfs — please tell me you’re not using /dev/shm
for that!).
Depending on how your system is set up, this might not be as insecure as it might sound at first, because for instance in my laptop, /var/tmp/portage
is always present, and always owned by the portage user, which makes it less vulnerable to a random user wanting to move there a particularly nasty shared object… on the other hand, assuming that it’s secure enough is the wrong move, full stop.
Before I start panicking people — Portage is smarter than you think and it has been, by default, stripping insecure runpaths for a while. Which means that you don’t have to fret about fixing the issues when you see the warning — but you really should look into fixing them for good. I would also argue that since Portage already strips the runpaths at install time, you shouldn’t just use chrpath
to remove the insecure paths after the build, but you should either fix it properly or leave it be, in my opinion. This opinion might be a bit too personal, as I don’t know if pkgcore or paludis support the same kind of fixing.
So let’s go back to the two sources of insecurity in the runpaths. In the case of a current directory being the base for the insecure path, which means having an rpath of either .
or ../something
, this is usually bad logic in the way the package tries to set its rpath, as instead of the current work directory, the developers most likely wanted to refer to where the executable itself is installed, which is, instead, $ORIGIN
literally. Another common situation is for that literal to be treated as a variable name, so that $ORIGIN/../mylibs
becomes something like /../mylibs
which is also wrong.
But a much more common situation arises when our build directory is injected into the runpath. Something along the lines of /var/tmp/portage/pkg-category/package-0.0.0/image/usr/lib64/mypkgslibs
— more rarely it would point to the build directory rather than the image directory. In many cases this happens because the upstream build system does not know about, or mishandles, the DESTDIR
variable.
The DESTDIR
variable is commonly used to install a software package at a given offset — binary distributions will then generate a binary package out of it, source distributions like Gentoo will then merge the installed copy to the live filesystem after recording which files have been installed. In either case, the understanding behind this variable is that the final location of the executables will not include it. Unfortunately not all build systems do support it, so in some cases we end up doing something a bit more hackish by replacing /usr
with ${D}/usr
in the definition of the install prefix. The prefix is, though, commonly used to identify where the executables will be at the end, so it would be possible for a build system to have, in the parameters, -rpath ${prefix}/lib/mylibs
which would then inject ${D}
on the runpath.
As you can see, for most common situations it’s a matter of getting upstream to fix their build system. In other cases, the problem is that the ebuild is installing files without going through the build system’s install phase, which, at least with libtool
, would often re-link the object files to make sure the rpath is handled correctly.
Beside this, there isn’t much more I can add, I’m afraid.
Wouldn’t you be able to inject a runpath by simply setting LD_LIBRARY_PATH?
> Wouldn’t you be able to inject a runpath by simply setting LD_LIBRARY_PATH?From man ld.so: LD_LIBRARY_PATH A colon-separated list of directories in which to search for ELF libraries at execution-time. Similar to the PATH environment variable. Ignored in set-user-ID and set- group-ID programs.Thus, you can add arbitrary code to non-set-user-id programs, but there are plenty of ways to do that, so there is no additional security problem from allowing LD_LIBRARY_PATH to add code to non-set-user-id programs. You cannot use LD_LIBRARY_PATH to add code to set-user-id programs.
Well, you can of course set LD_LIBRARY_PATH to include . and then run the program from some insecure path like /tmp or some remote mount.Then you actually did add a vulnerability, allowing anyone to inject arbitrary code into your binaries, similarly to the kind of things that can happen if you set PATH to include .But that would just be you being stupid, the bigger problem is if rpath is set like that, it would be a permanent hole that does not rely on someone setting an environment variable to some bad value.For extra joy, Windows still defaults to having . in PATH and the dlopen path, so any program that doesn’t want to be “unsafe by default” has to start with about 5 magic lines of “request OS to be secure” code.