Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add capabilities to a native library but not to the executable running it?

Context

I've done a java library that uses a C library with JNI. The C library is compiled in linux into a .so file. This library needs cap_net_raw capabilities.

Goal

Execute a java process without additional privileges, that uses said java library. The actual processes that are going to use the library are existing processes already in prod and we don't want to give them more rights.

To test that, I've created a jar and run it both with and without sudo. As expected, it succeeds with but fail without it.

Steps to reproduce the test

  1. Create a java class with a native method, let's call it SocketTester.java
static {
    System.loadLibrary("SocketTester");
}
private native int socketTest();
  1. Generate socketTester.h file with the command
javac -h . SocketTester.java
  1. Create socketTester.c file that implements socketTester.h and which needs the cap_net_raw capabitily
  2. Compile with
gcc -o libSocketTester.so socketTester.c -shared -I/usr/lib/jvm/java-14-openjdk-amd64/include -I/usr/lib/jvm/java-14-openjdk-amd64/include/linux
  1. Move libSocketTester.so to /usr/lib
  2. Run
sudo ldconfig
  1. Set the cap
cd /usr/lib
sudo setcap cap_net_raw=epi libSocketTester.so
  1. Create a Test.java class
public static void main(final String[] args) {
    SocketTester tester = new SocketTester();
    tester.socketTest();
}
  1. Create a jar with SocketTester.java and Test.java
  2. Run the test
java -cp socketTester.jar Test

What I've already tried

Adding cap to the .so lib

sudo setcap cap_net_raw=epi libSocketTester.so

Result: Failure

Adding cap to java

sudo setcap cap_net_raw=epi /usr/lib/jvm/java-14-openjdk-amd64/bin/java

Result: It works, but it's not what I want because now all java process have the capability (see bold in goal section).

The question

Why is adding the cap to the .so doesn't work? How else can I accomplish the goal?

like image 284
Christophe Broeckx Avatar asked Dec 28 '20 13:12

Christophe Broeckx


People also ask

Where do I put the native libraries?

Typically the native libraries are in the RID specific section, so for example in ".NETCoreApp,Version=v2.1/ubuntu.14.04-x64", so that on a any given platform the right native library is picked. Oh okay, could you make that work without copying the file to the output directory?

How to reference native libraries in managed assemblies?

Managed assemblies are referenced like: Typically the native libraries are in the RID specific section, so for example in ".NETCoreApp,Version=v2.1/ubuntu.14.04-x64", so that on a any given platform the right native library is picked. Sorry, something went wrong. This is the default behavior.

How do I deploy a native library to an android project?

To deploy a native library with a Xamarin.Android library project, add the library binary to the project and set its Build Action to EmbeddedNativeLibrary. Note that since Android supports multiple Application Binary Interfaces (ABIs), Xamarin.Android must know which ABI the native library is built for.

How do I add a native library to eclipse?

If you want to add a native library without interfering with java.library.path at development time in Eclipse (to avoid including absolute paths and having to add parameters to your launch configuration), you can supply the path to the native libraries location for each Jar in the Java Build Path dialog under Native library location.


Video Answer


1 Answers

A zillion years ago I figured out how to have PAM modules fork a helper program to do privileged things from unprivileged contexts. This is how pam_unix.so is able to invoke unix_chkpwd to help an unprivileged application (a screensaver, or screen) accept a user password to unlock under Linux.

More recently, I learned the trick to making shared library objects (libcap.so, pam_cap.so etc) work as standalone binaries. I've since been thinking about combining both of these techniques... Researching that, I came across this question. Since I was able to do it for the example task of an unprivileged program binding to port 80, I thought it might be of interest as an answer here.

I've done a full write up of how it works on the Fully Capable libcap distribution site, but it essentially boils down to three things:

  • make the .so file executable as a stand alone program, with its own file capability
  • include some code in the .so file that figures out its own filename when linked into another program (this uses a _GNU_SOURCE extension: dladdr())
  • create some mechanism for the library itself to fork/exec itself (I use libcap:cap_launch()) with a private communication mechanism (I use a Unix domain socket generated with socketpair()) back to the app-linked shared library code.

The flow is basically, app calls the .so function, that function invokes the .so file as a forked child and performs the privileged operation. It then returns the result to the app over the Unix domain socket and exits.

like image 66
Andrew G Morgan Avatar answered Oct 16 '22 22:10

Andrew G Morgan