Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stop using absolute rpaths #11746

Closed
brson opened this issue Jan 23, 2014 · 26 comments
Closed

Stop using absolute rpaths #11746

brson opened this issue Jan 23, 2014 · 26 comments
Labels
A-linkage Area: linking into static, shared libraries and binaries
Milestone

Comments

@brson
Copy link
Contributor

brson commented Jan 23, 2014

Followup to #5219. Per that issue rpaths are frowned on by linux distros. Absolute rpaths are supposed to be worse than relative rpaths (I'm not clear on why at the moment), and they are probably relatively easy to get rid of. Nominating.

@thestinger
Copy link
Contributor

For an example of why an absolute path is bad, consider building a library in a home directory or perhaps /tmp. The compiled binary now ends up with /home/foo/build or /tmp/build-14a29fp as a path to load libraries. It's very easy to deploy this library without thinking about the relative rpath and expose a significant vulnerability.

It's true that this is also possible with relative ($ORIGIN-based) paths, but it's far less likely. A relative path can also be useful in a deployed package, while an absolute path is rarely correct and at best redundant.

Linux distributions usually lint for insecure rpaths, rather than all rpaths. So, an $ORIGIN-based path will be considered okay as long as it doesn't point outside of the directories owned by the package and they do not provide write permissions to non-root users.

@pnkfelix
Copy link
Member

Accepted for 1.0, P-backcompat-lang.

@lucab
Copy link
Contributor

lucab commented Mar 7, 2014

No, relative $ORIGIN-based rpaths are also problematic from a security POV. See CVE-2009-0876 for a real world exploitation PoC due only to rpath usage.

@thestinger
Copy link
Contributor

Sure, but having no absolute rpaths is better than having both $ORIGIN and absolute paths. The absolute paths are a bigger problem because they're always wrong if you install a Rust library or binary as a package. At least the $ORIGIN-based ones can be right, and often are.

@o11c
Copy link

o11c commented Mar 7, 2014

I don't know if you can avoid all absolute rpaths if you configure with a custom BINDIR and LIBDIR. It should be possible if you only change PREFIX, or only change BINDIR and LIBDIR within prefix (i.e. ${PREFIX}/lib64).

Think especially about the relatively common case where a rust package is installed by a user in ${HOME}. This absolutely needs an rpath ... and in the case there BINDIR=/bin and LIBDIR=/.local/lib, gets kind of hairy.

In fact, now that I think about it, my ~/bin is a symlink anyway, so relative rpaths wouldn't work.

As far as I understand it, rpaths are only a security problem when running executables with a set*id bit or setcap bits.

Oh, and about the "install multiple deps when you can't write to root" - the solution to that sounds like a sysroot (which == DESTDIR).

So:
compile libfoo, install with DESTDIR=/tmp/build, the actual lib is in /tmp/build/usr/lib
compile hellofoo with sysroot=/tmp/build

@thestinger
Copy link
Contributor

That's not when rpaths are meant to be used. A user can set LD_LIBRARY_PATH to add a directory to the library loading path, just like they can modify PATH. A relative rpath is used to give a program a hidden application-specific library path that's not meant to be included in the system library search paths via ld.so.conf.d.

@o11c: It is not just a security issue with setuid binaries. Rust binaries will have rpaths based on their build directory opening up the ability for non-root users to inject code when other users run the binary, like root. Rust won't be included in Fedora or Debian if it's making this kind of security faux pas - rpaths should be opt-in.

@o11c
Copy link

o11c commented Mar 7, 2014

Well, what I always heard about LD_LIBRARY_PATH was "don't use it; use rpath instead". Particularly, asking users to set LD_LIBRARY_PATH globally just to be able to install rust packages as user is wrong. When installed as user, absolute rpaths should definitely be used.

Yes, it's wrong to have an rpath pointing to the build directory, but that's not the same as it being wrong to have an absolute rpath at all.

For the virtualbox $ORIGIN attack, I would hope that root can be trusted not to accidentally run someone else's hard link to the binary. Therefore, it should be secure to have two rpaths: an absolute one to the install directory (where bindir and libdir may have no relation), and a relative one to the build directory (where the equivalent of bindir and libdir are known) for testing.

@thestinger
Copy link
Contributor

Particularly, asking users to set LD_LIBRARY_PATH globally just to be able to install rust packages as user is wrong. When installed as user, absolute rpaths should definitely be used.

I don't understand why you would say this. It's totally fine to set LD_LIBRARY_PATH for a specific user, to load libraries from a user-controlled directory. It's really not much different than adding a local directory to PATH, as you can even mask system binaries that way (which is not a bad thing). Unlike an absolute rpath, it won't require rebuilding everything if the directory changes or the user's home directory is moved - for example, if the username needs to be changed.

I am obviously not suggesting that it be done globally. The /etc/ld.so.conf file and /etc/ld.so.conf.d directory exist for system-wide library directories controlled by root.

Yes, it's wrong to have an rpath pointing to the build directory, but that's not the same as it being wrong to have an absolute rpath at all.

It's insecure to add an absolute rpath by default. I don't know of any other compiler doing this. I doubt that the Ubuntu PPA is removing this insecure rpath, for example.

For the virtualbox $ORIGIN attack, I would hope that root can be trusted not to accidentally run someone else's hard link to the binary. Therefore, it should be secure to have two rpaths: an absolute one to the install directory (where bindir and libdir may have no relation), and a relative one to the build directory (where the equivalent of bindir and libdir are known) for testing.

I'm not talking about hard links. Adding rpaths by default with the compiler means files installed to root are going to accidentally end up with insecure rpaths. Other compilers are not doing this by default, so it makes Rust's behaviour very surprising. There's no such thing as an rpath on Windows so Rust can never provide a consistent, predictable behaviour if it uses this hack.

@thestinger
Copy link
Contributor

Rust won't be allowed in Fedora and Debian until this issue is fixed, and their policies are not going to change. At most, they'll patch this out of the compiler and Windows won't be the only platform behaving differently.

@brson
Copy link
Contributor Author

brson commented Mar 7, 2014

My understanding is Debian is likely to make at least temporary exceptions for Rust's remaining infidelities, but we should still keep trying to work toward a model that doesn't use rpath.

@thestinger
Copy link
Contributor

Static linking is already used by default and the build process no longer needs rpaths. There's nothing blocking stripping it all out today and having users set them manually if they want them, as other languages do. It's not a technical issue anymore.

@o11c
Copy link

o11c commented Mar 7, 2014

There is absolutely nothing insecure about an rpath that is writable by the same set of users (i.e. just root) who can write to the binary itself (barring the set_id thing). There is *nothing_ fundamentally wrong with rpath. Just don't use it wrong. Neither Debian nor Fedora has an absolute ban on rpath, and Rust fits quite neatly into the explicitly permitted case.

And there are very real cases where static linking simply cannot be used. One particular case I hit recently: when you need to link to an internal C shared library that is not in the default library path. I have to compile my rust library as crate type dylib and add the rpath myself. But anybody linking to my rust library shouldn't have to know about internals - they only need the rpath to my library, which rustc currently happily provides.

Everything should Just Work. If rpath isn't added by rust itself (which can be done securely), you'll just end up with a bunch of ad-hoc scripts adding it in all the packages, which is infinitely harder to make secure.

@thestinger
Copy link
Contributor

There is absolutely nothing insecure about an rpath that is writable by the same set of users (i.e. just root) who can write to the binary itself (barring the set*id thing).

Rust adds rpaths pointing to the build directory, and these are often insecure when the binary is packaged and installed to a system directory. A package is usually build in a clean chroot as non-root or by the packager in their home directory, and the rpaths will point outside of the safe system directories. It's an unsafe default and the Ubuntu PPA proves that this issue will be overlooked.

There is nothing fundamentally wrong with rpath. Just don't use it wrong.

There are correct use cases for rpaths, but adding an absolute and relative path to the build directory is not correct in many cases. It's a significant security risk. It's implicit, so "don't use it wrong" isn't an excuse. The defaults should be secure, and potentially risky options should be opt-in. You can't blame people for doing it wrong if Rust is doing it implicitly without letting them know. No other compiler seems to do this - it's a surprising default.

Neither Debian nor Fedora has an absolute ban on rpath, and Rust fits quite neatly into the explicitly permitted case.

They don't permit what Rust is doing. Fedora is certainly not going to add Rust to the repositories as long as it's implicitly adding rpaths which are not always safe. They've kept Chromium out of the repositories for smaller issues.

Everything should Just Work. If rpath isn't added by rust itself (which can be done securely), you'll just end up with a bunch of ad-hoc scripts adding it in all the packages, which is infinitely harder to make secure.

The default is static linking, and it works. Perhaps you're missing that Windows doesn't have this feature, so using it provides an inconsistent experience across platforms regardless of how it's done. Most packages work fine without using any rpaths, as they just make use of the regular system libraries or are a system library themselves.

@thestinger
Copy link
Contributor

I'm not sure what you mean by an ad-hoc script though. You can ask for an rpath by passing -rpath=foo to link-args. It should be an explicit decision rather than doing it by default when it's clearly not always safe.

The Ubuntu PPA is a good example of this - it builds in a directory, then ships the binaries and libraries as a package. However, these binaries/libraries have rpaths set outside of the system directories.

@o11c
Copy link

o11c commented Mar 7, 2014

I completely agree that rust should not use absolute rpaths to the build directory. But the other cases - relative rpath to build directory and absolute rpath to install directory - are both secure and essential.

@thestinger
Copy link
Contributor

A relative path to libraries used while building is still a security issue.

An absolute path to the location where libraries are installed is not, but rustc is not aware of an installation concept. I don't think building and installation should be strongly coupled together - it would be better to make it possible to build and then choose an install location (home directory, package, /usr/local) without rebuilding.

@o11c
Copy link

o11c commented Mar 7, 2014

A relative path to libraries used while building is still a security issue.

Can you give an example of that? The VirtualBox case doesn't count, because that relies on hard-links to set_id, and if you're using set_id it's your own responsibility to take care of things.

@thestinger
Copy link
Contributor

Adding a package-specific path like /usr/lib/package-name is an acceptable and almost always secure usage of a relative rpath. There's no problem with a tool adding that automatically, but it needs to be aware of installation and add it after building, which rustc cannot do.

rustc adds paths relative to the build directory for libraries it links against. This includes paths that should simply not be included at all like $ORIGIN/../../usr/lib/rustlib/x86_64-unknown-linux-gnu/lib along with potentially insecure paths based on the build system's directory hierarchy.

@lucab
Copy link
Contributor

lucab commented Mar 7, 2014

Uh, it looks like I ignited this discussion, so I think I should make a couple of clearer comments:

  • My comment above was just an answer to brson's Absolute rpaths are supposed to be worse than relative rpaths (I'm not clear on why at the moment): they aren't, and both of them have security issues, even if with different threat models. And we have already PoC for both cases, so it isn't just theoretical paranoia.
  • I think we are really conflating two issues here: how to generate dynamic artifacts and how to handle user-level playground. The Debian/Fedora model works because all the .so you need
    are in a (root privileged) system library path. Having an rpath there is a security issue, and as such we can't allow system-wide packaged binaries to ship with it. I'm currently packaging with -disable-rpath and everyone is happy.
    However, the issue we are trying to address with rpath here is user-level playground installation. This is the same as having a locally built but non-installed c library: you can't expect the loader to pick up the .so from your home, without resorting to dirty trick like rpath or preloading. OTOH, this is not how you would expect to ship system-wide software and libraries, and here we come back to why system dynamic libs don't have rpath. We should acknowledge the two scenarios and solve them, possibly with some help from rustpkg2.
  • I plan to do a short survey for other AOT-native-compiled languages before coming up with a final proposal. For sure rpath in system-wide libs must go away. However, the user-wide playground is still an issue that we share with other languages. For example, using a locally-built C .so (when you can't install it somewhere to LIBDIR) has the same problems.
  • Here we are also doing a bit of long-term system-wide design (rust as a system language, imagine talking about a systemd(1) or an apache2(1) replacement). I am not comfortable with @o11c arguments in this case: history has shown that rpaths have issues, and we'll probably have to fix the design again sooner or later.
  • Just forcing rpath indiscriminately is a problem. This isn't just Debian/Fedora policy nitpicking, as we can strip rpath from packages or patch around it.

In summary I'd say: in the grand final plan, rpaths are not ok and I don't plan to ship any packaged binaries with them. However, they are mostly used to ease the setup of user-level playgrounds. (Probably) this should be handled together by rust and rustpkg2 and requires a bit of careful design in the latter. I'm still far from coming up with a proper proposal for this, but I surely would contribute to rustpkg2 plans.

@o11c
Copy link

o11c commented Mar 7, 2014

we have already PoC for both cases, so it isn't just theoretical paranoia.

Every exploit I am aware of involves one of:

  • absolute or relative rpath to a world-writable directory
  • relative rpath in a set*id executable

Just stop doing those things and you'll be fine.

Also note that not using rpath means shipping 50 different hashed versions of libstd-*.so in /usr/lib itself, which frankly is more likely to upset distros than leaving them under /usr/lib/rust.

@thestinger
Copy link
Contributor

Every exploit I am aware of involves one of:

  • absolute or relative rpath to a world-writable directory
  • relative rpath in a set*id executable

It doesn't need to be a world-writable directory, it just has to be writable by an unprivileged user. Since Rust is setting paths relative to the build directory without any knowledge about the final destination, I don't think it is unrealistic for insecure rpaths to be created.

Just stop doing those things and you'll be fine.

It's Rust setting the rpath, not the programmer. Insecure defaults are not the fault of the programmer or package. It's not a matter of having people "stop doing those things" as I stated in a previous reply.

Also note that not using rpath means shipping 50 different hashed versions of libstd-*.so in /usr/lib itself, which frankly is more likely to upset distros than leaving them under /usr/lib/rust.

There's no reason Rust would have to do this. One shared object and one archive are required to support dynamic linking, link-time optimization and static linking - that's two files for each library, not fifty. Any other copies will be for cross-compilation and don't need to be available on the host itself. New directories can be added to the system's library search path by a package via ld.conf.so.d (like what Rust calls the sysroot and uses as a "fallback" rpath) but it's really overkill for this.

@o11c
Copy link

o11c commented Mar 8, 2014

It doesn't need to be a world-writable directory, it just has to be writable by an unprivileged user.
I'm simplifying. I've also stopped mentioning setcap even though it has the same issues as set*id.

Also note that not using rpath means shipping 50 different hashed versions of libstd-*.so in /usr/lib itself, which frankly is more likely to upset distros than leaving them under /usr/lib/rust.

There's no reason Rust would have to do this. One shared object and one archive are required to support dynamic linking, link-time optimization and static linking - that's two files for each library, not fifty.

For one version of a library. But consider, once rust gets out of the "recompile every day" stage:

At the API level, each library can reasonably be expected to have 3 "live" versions: oldstable, stable, and testing. There is no correlation between which level of a package is used in any particular installation.

So if:
libstd has versions 1.0, 1.1, 1.2
libfoo has versions 2.0, 2.1, 2.2 and depends on libstd
libbar has versions 3.0, 3.1, 3.2 and depends on libfoo
then libbar may have 27 different libbar-hash.so files installed.

Ideally, for a full build of a single package containing both foo and bar, the process is:

  • download source into /build
  • build foo with LIBDIR=/usr/lib, it adds an absolute rpath to the system libstd.
  • install foo with DESTDIR=/build/install, so the actual files are /build/install/usr/lib/rust/libfoo-*.so
  • build bar with SYSROOT=/build/install. When rust sees libfoo in $SYSROOT$LIBDIR/rust, it adds an absolute rpath of just $LIBDIR/rust instead of the full path. In order to run bar from the build dir (e.g. for testing), it also needs a relative rpath of install/$LIBDIR/rust in order to find libfoo. This one could be removed during installation, but since neither /usr/bin/install/ nor /usr/lib/rust/install/ is writable by user, there is no security problem except for the setuid hardlink case.
  • install bar with DESTDIR=/build/install
  • make the distro tarball for foobar out of the contents of /build/install

If foo and bar are separate packages, the process is much simplified; you can reasonably expect libfoo to be completely installed to the system before libbar begins.

New directories can be added to the system's library search path by a package via ld.conf.so.d
I'm not convinced this is portable between distros.

Finally, while user-level playground is indeed a completely different story, I am worried that attempts to curb the use of rpath for system-level problems will lead to completely breaking it for user-level. Far better to have one solution that works for both system and user, with a small set of changes for a few rare setuid programs, than to have to maintain two completely different and incompatible solutions, one for system installs and one for user ones.

@lucab
Copy link
Contributor

lucab commented Mar 18, 2014

cc @wycats @carlhuda @carllerche

@thestinger
Copy link
Contributor

Static linking is already the default, and I don't really see why dynamic linking is useful at a user level. It's simply a non-issue in my opinion. Dynamic linking with Rust is only useful as a way to reduce duplication between packages. There's no ABI compatibility at all right now (std::reflect) and there won't be for idiomatic libraries that aren't doing contortions to support it.

@o11c
Copy link

o11c commented Mar 18, 2014

Static linking is already the default, and I don't really see why dynamic linking is useful at a user level.

Because static linking doesn't work for my crate, which has moderately nasty C dependencies, unless I force all the complexity out to every single caller.

Dynamic linking with Rust is only useful as a way to reduce duplication

Nope. It's also a form of encapsulating away internal dependencies. Unless rust's #[link] becomes able to execute arbitrary scripts, rlib just can't do it.

alexcrichton added a commit to alexcrichton/rust that referenced this issue Apr 8, 2014
Concerns have been raised about using absolute rpaths in rust-lang#11746, and this is the
first step towards not relying on rpaths at all. The only current use case for
an absolute rpath is when a non-installed rust builds an executable that then
moves from is built location. The relative rpath back to libstd and absolute
rpath to the installation directory still remain (CFG_PREFIX).

Closes rust-lang#11746
Rebasing of rust-lang#12754
@Ergus
Copy link

Ergus commented Feb 18, 2016

Hi guys, I am pretty new using rust. I am wrapping a C library to rust in Linux64 bit. I am trying to set the rpath like in gcc -Wl,-rpath,the path. But in rust there is not a clear documentation about that, the -C rpath option is doing nothing. I have different versions for the library so I need the rpath variable and LD_LIBRARY_PATH is not an option because I have several executables and th shared objects are in different path. I don't use cargo either because my software runs over CMake so I am using rustc directly.
Please some exact equivalent to:
gcc -Wl,-rpath,${PWD}/../lib myfile.c -o myfile ???

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-linkage Area: linking into static, shared libraries and binaries
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants