Wednesday, March 5, 2014

Privileged ports, Linux capabilities and Java

Under Unices (including Linux), running a listening service on low numbered privileged TCP ports (< 1024) can usually only be done by root (superuser). This does not necessarily mean that service needs to run with effective user id of root, but at service port binding time, user running the program needs to have sufficient privileges. As lot of low numbered ports are commonly used for many standard services (HTTP is usually over port 80 and HTTPS port 443, etc), the notion of privileged ports can be considered "security through convention" approach, preventing random user with UNIX shell account popping up some apparently "official" service. On the other hand, requirement for privileged account can become a security issue, if the program that needs to be run on privileged port is in some aspects untrusted.

privbind/authbind

Debian packages include privbind utility that allows to execute application as an unprivileged user with extra privilege of binding to reserved ports. Its manual is contradictory, specifying both that:

  • privbind has no SUID parts, and runs within the confines of a single process
  • privbind works by starting two processes. One drops privileges and runs (exec(2)) the command, the other remains as root.
Contradictions aside, privbind is unable to work with many current Java Virtual Machines (JVMs), as it is "using LD_PRELOAD to intercept every call to bind(2) made by the program"1, while JVM implementations can and do binding calls directly to kernel. If it works, usage can be as easy as: "privbind path_to_binary".

Similar package available on Debian is authbind, also using LD_PRELOAD mechanism and thus suffering from the same limitations.

setcap + chrpath

However, on Linux kernels with capabilities support (2.2+), using per-thread NET_BIND_SERVICE capability can be used to run service on privileged port without runtime superuser privileges. This capability can be enabled with setcap "eip" flags (effective, inheritable and permitted, execute as root), e.g. on Java binaries:

setcap cap_net_bind_service=+eip $JAVA_HOME/bin/java
setcap cap_net_bind_service=+eip $JAVA_HOME/jre/bin/java

While necessary, setcap might not be sufficient (as unprivileged user):

java: error while loading shared libraries: libjli.so: cannot open shared object file: No such file or directory
Examining the java binary with readelf shows:
xyz@sdk$ readelf -d java
Dynamic section at offset 0x71c contains 28 entries:
  Tag        Type                         Name/Value
 ... skipped ...
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
 0x0000000e (SONAME)                     Library soname: [lib.so]
 0x0000000f (RPATH)                      Library rpath: [$ORIGIN/../lib/amd64/jli]
According to thread in openjdk distro-pkg-dev list, glibc disallows relative paths and $ORIGIN expansion when running binary with capabilities as ordinary user (for security reasons). One solution to that is to change the RPATH encoded in ELF binary to absolute path. From Debian packages chrpath can be used for this:
chrpath -r $JAVA_HOME/lib/amd64/jli/ $JAVA_HOME/bin/java
chrpath -r $JAVA_HOME/jre/lib/amd64/jli/ $JAVA_HOME/jre/bin/java

setcap + patchelf

Changing RPATH can also be done with patchelf, not currently included in Debian packages. Sylvain Duloutre's blog post describes granting identical port binding capabilities to JVM binaries with setcap and patchelf, with RPATH change performed like this:

patchelf --set-rpath $JAVA_HOME/lib/amd64/jli $JAVA_HOME/bin/java
patchelf --set-rpath $JAVA_HOME/jre/lib/amd64/jli $JAVA_HOME/jre/bin/java

NB!

Granting capabilities to (JVM) binaries can introduce security concerns and should only be done where access to such binaries is suitably restricted.


1 privbind-devel: privbind not working with Java

No comments:

Post a Comment