ldopen failing with relative path when linux capability is set

Bug #565002 reported by BJB
30
This bug affects 5 people
Affects Status Importance Assigned to Milestone
glibc (Ubuntu)
Confirmed
Undecided
Unassigned

Bug Description

Call to ldopen is failing when a POSIX capability is set on the file and a $ORIGIN relative path is given.

Relative path is used in some major tool such as the Java VM when required to grant bind on port less than 1024 without giving the su rights (for obvious security reasons). The impacted applications are : applications server, mail servers, etc ... all running Java.
See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6919633

For capability priviledge port bind, the only work-around are :
 - patch the binary containing the $ORIGIN an replace it with code absolute path (ugly, isn't it?)
 - use a iptable PAT redirection (prevent dynamic bind as you have to know beforehand the port value to create a rule) : make configuration complex and bring some limitations.

For other capabilities, there might be no workaround but to go with the setuid :(

Here is example of a program helping to reveal the potential bug :

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <unistd.h>

int main(int argc, char *argv[]){
    void *handle;
    double (*cosine)(double);
    char *error;

    printf("UID=%d EffUID=%d\n", getuid(), geteuid());

    handle = dlopen (argv[1], RTLD_LAZY);
    if (!handle) {
        fprintf (stderr, "%s\n", dlerror());
        exit(1);
    }
    dlerror(); /* Clear any existing error */
    cosine = dlsym(handle, argv[2]);
    if ((error = dlerror()) != NULL) {
        fprintf (stderr, "%s\n", error);
        exit(1);
    }
    printf ("res:%f\n", (*cosine)(2.0));
    dlclose(handle);
    return 0;
}

Let's compile that :
gcc -o bug-cap-origin -ldl bug-cap-origin.c

cp bug-cap-origin ~
cd ~

Step 1 : Initial checkings

getcap bug-cap-origin
 -> empty result (= no capabilities set at this time)
./bug-cap-origin '/usr/lib64/libm.so' sin
 -> OK: display the sin result
./bug-cap-origin '$ORIGIN/../../usr/lib64/libm.so' sin
 -> OK: display the sin result
sudo ./bug-cap-origin '$ORIGIN/../../usr/lib64/libm.so' sin
 -> OK: display the sin result

Step 2 : Assign a capability to the file and check again ... to get the problem

sudo setcap cap_net_bind_service=+epi bug-cap-origin
 --> empty result (=capability was set)
./bug-cap-origin '/usr/lib64/libm.so' sin
 -> OK: display the sin result
./bug-cap-origin '$ORIGIN/../../usr/lib64/libm.so' sin
 -> KO !!! : Message displayed = "$ORIGIN/../../usr/lib64/libm.so: cannot open shared object file: No such file or directory"
sudo ./bug-cap-origin '$ORIGIN/../../usr/lib64/libm.so' sin
 -> OK: display the sin result

Step 3 : Removing the capability solve the problem

sudo setcap -r bug-cap-origin
 -> empty result (= capability was removed)
./bug-cap-origin '/usr/lib64/libm.so' sin
 -> OK: display the sin result
./bug-cap-origin '$ORIGIN/../../usr/lib64/libm.so' sin
 -> OK: display the sin result
sudo ./bug-cap-origin '$ORIGIN/../../usr/lib64/libm.so' sin
 -> OK: display the sin result

Am I wrong somewhere or is the ldopen having issues with relative path when Linux capabilities are set ?

Test was done on :
 ld-2.11.1
 gcc 4.3.3
 Linux 2.6.32-20-generic
 Ubuntu 10.04 (latest dev)

Revision history for this message
Dana Goyette (danagoyette) wrote :

I can confirm.

I need to give Firefox CAP_FOWNER to get it to stop failing to install extensions when utime() fails on ntfs-3g -- and then I get "libxul.so ... no such file or directory".

Also, do you mean dlopen, not ldopen?

Revision history for this message
BJB (bugeaud) wrote :

Sure it was LD open (aka dlopen) ...

Anybody has the skills to fix this one ?

Revision history for this message
Launchpad Janitor (janitor) wrote :

Status changed to 'Confirmed' because the bug affects multiple users.

Changed in glibc (Ubuntu):
status: New → Confirmed
Revision history for this message
Douglas Leeder (ubuntu-leeder) wrote :

I have something very similar on Ubuntu 18.04 where the RUNPATH in the binary isn't working for normal linking.

Revision history for this message
Douglas Leeder (ubuntu-leeder) wrote :

I've created a git repo to reproduce this issue without using ldopen:

https://github.com/paperclip/origin_experiment

However it now looks like this might be deliberate behaviour:
https://seclists.org/fulldisclosure/2010/Oct/257

Tavis says that expanding $ORIGIN allows attacks against SUID binaries, and the ELF specification ( http://web.archive.org/web/20041026003725/http://www.caldera.com/developers/gabi/2003-12-17/ch5.dynamic.html#substitution ) suggests not expanding $ORIGIN for any privileged binaries.

Revision history for this message
Florian Weimer (fweimer) wrote :

Yes, this is deliberate behavior for execve with an AT_SECURE transition. Environmental parameters such as the path to the executable are not trustworthy in that case.

For safe use of Java with capabilities you will have to use a restricted, custom launcher. I don't think such a thing exists today. That launcher should be able to avoid the use of $ORIGIN, too.

Revision history for this message
Douglas Leeder (ubuntu-leeder) wrote :

I've managed to hack something together for a launcher:

https://github.com/paperclip/origin_experiment/tree/launcher

The main pain point is that you have to put the permission in both the inheritable set, and the ambient set before it'll get passed to the new process (that can use $ORIGIN).

Revision history for this message
BJB (bugeaud) wrote :

Douglas, thanks for your contrib 👍 The security issue was raised by couples of guys in the ML back in 2010 about this one. But actually I still think this might be more an "ICMP syndrom" now day. Say, ICMP had a security issue back in the day, that was fixed but that people used to quickfix by disabling ICMP on the network devices. Decades after everything is fixed, there will still be people arguing to disable ICMP for safety reason. They mostly don't even remember why, but only that it might have been a threat. Meanwhile, everyday lots of people are "enjoying" the ugly side-effect of such a mindset by bringing more boilerplate to workaround ICMP beeing disabled on some machine.

Florian, is there in 2020 any real security reason for not having relative path working with capabilities ?
I mean in worst case, sanitization of input is a usual task nowadays to ensure value are safe before entering into a key process.

For priviledged port bind, the NAT is abused as a workaround especialy within containers. For the same deamon servicing you can end up having multiple NAT at the various level of the containerization stack : completely a waste of CPU cycles. But a quickfix for such a conundrum.

Revision history for this message
Douglas Leeder (ubuntu-leeder) wrote :

Certainly Tavis's original attack is impossible, since you can't create the hardlink:

$ ln /bin/ping /tmp/target
ln: failed to create hard link '/tmp/target' => '/bin/ping': Operation not permitted

I don't know when the change was made to prevent hard-linking files the user doesn't own, but it isn't specific to suid binaries:

$ ln /bin/bash /tmp/target
ln: failed to create hard link '/tmp/target' => '/bin/bash': Operation not permitted

Revision history for this message
Douglas Leeder (ubuntu-leeder) wrote :

Looks like the change was in kernel 3.6:
/proc/sys/fs/protected_hardlinks
http://man7.org/linux/man-pages/man5/proc.5.html

Revision history for this message
Douglas Leeder (ubuntu-leeder) wrote :

This is the commit that added the protection:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=800179c9b8a1e796e441674776d11cd4c05d61d7

Ubuntu must turn it on by default.

The risk, without that kernel option turned on, is:

1. High priv (setuid etc) binary with $ORIGIN runpath
2. Low priv user hard-links binary into a directory they control
3. Create malicious shared library that binary will load from $ORIGIN runpath.

I think that the kernel protection prevents 2. And I don't know if there's any other exploits.

To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.