Bug 11643 - ldopen failing with relative path ($ORIGIN) when a capability is set
Summary: ldopen failing with relative path ($ORIGIN) when a capability is set
Status: RESOLVED INVALID
Alias: None
Product: glibc
Classification: Unclassified
Component: libc (show other bugs)
Version: unspecified
: P2 critical
Target Milestone: ---
Assignee: Ulrich Drepper
URL:
Keywords:
Depends on: 4177
Blocks:
  Show dependency treegraph
 
Reported: 2010-05-28 16:28 UTC by Jean-Baptiste BUGEAUD
Modified: 2014-06-30 17:55 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Last reconfirmed:
fweimer: security-


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Jean-Baptiste BUGEAUD 2010-05-28 16:28:46 UTC
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. But other modular softwares in C/C++ are 
impacted as well. 

See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6919633
And https://bugs.launchpad.net/ubuntu/+source/glibc/+bug/565002

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 ($ORIGIN see 
http://sources.redhat.com/bugzilla/show_bug.cgi?id=4177 )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
Comment 1 Roland McGrath 2010-05-28 18:29:59 UTC
This is not a bug.  It's a security feature.  $ORIGIN can be abused to load
different libraries into the process and effect a privilege escalation.  So,
like LD_LIBRARY_PATH, it is disabled in a process that is setuid or similarly
privileged.

The Linux kernel decides what constitutes "setuid-like" by setting the AT_SECURE
parameter at exec time.  libc just follows that.  If you want the rules for that
changed, take it up with the kernel people.
Comment 2 Jean-Baptiste BUGEAUD 2010-05-28 22:11:33 UTC
My understanding is that, when AT_SECURE is set it is up to the glibc to decide 
what to do with it, and as in the example given UID=EUID there is no superuser 
escalation possible. So $ORIGIN chould be safe, as the only extra feature granted 
on the process is set using the capabilities (file system level granted by root) 
and no other capabilities can be added by the user.

In that context, this means that when AT_SECURE is set glibc should perform its 
own check. Something like : if EUID==UID then grantOriginEscaping else 
forbidOriginEscaping
Comment 3 Roland McGrath 2010-05-28 22:31:04 UTC
You seem to misunderstand the nature of security checks and privilege escalation
risks.  This is not the place to get educated.  Do not reopen this bug.
Comment 4 Jean-Baptiste BUGEAUD 2010-05-29 09:43:45 UTC
Either #4177 is obsolete, or my guess is that I will be quite difficult for anyone 
to "get educated" on $ORIGIN behaviour "nominal behavior" and the expected impact 
on security when wokring with POSIX capabilities.

I know "code is the reference" but having to dig in glibc internals and even 
kernel (!!) for only making sure how something should behave "by design" always 
taste bitter to me. So, I will have to a pickaxe and dig the Web & the ML ;)

Anyway, thanks Roland.
Comment 5 Petr Baudis 2010-05-31 17:13:08 UTC
Roland's point is that bugzilla is for actual bugs, this is not an obvious bug
more of a discussion point; if you'd have questions about it, you should ask at
libc-help.

(Before you do that, consider that AT_SECURE is set by the kernel when the
process has more privileges than the user starting it, and thus means for the
user to plug in custom code to the process context should be limited - more
privileges does not just mean "superuser", the whole point of capabilities is
that specific privileges can be abused as well. The moment you allow $ORIGIN for
a process with a certain capability, it's just as if you'd simply give all users
on the system the capability right away.)
Comment 6 Jean-Baptiste BUGEAUD 2010-07-28 14:07:24 UTC
Hello Petr,

Thanks for this explanation, this helps to solve the puzzle.

To me this is an obvious Bug : I can not use POSIX capabilities and the only 
workaround is to give SUID !

My understanding of Capabilities was that this is something "less harmfull" than 
SUID. Because, if you are SUID you don't need them, you own all the caps ! My 
understanding was also that they are implemented in a secured way. Which means 
that if I have given somebody a right, he can not goes any further and get 
nother one "for free" and thus gaining a complete SU status. Am I correct ?

In a way, we could rephrase your points by asking :
Is POSIX capabilities secured ?
Should we use POSIX capabilities as a way of securing Linux based system and 
removing as much as SU/Sticky bits headaches ?

I will try to question the ML, but keep the possibility of reopening this if 
nobody clarify this security situation.

I understand that this is not a simple issue, but I would not have bugged people 
with something that RFM would solve.

Cheers,
JB