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

1

6

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?

Burlesque answered 28/12, 2020 at 13:43 Comment(4)
When you add the capability to the library, have you tried to load it through the classpath directly instead of loading from a jar containing the library?Mikiso
@NicolasHenneaux I edited my question to add the steps to reproduce the test, so you can see exactly what I'm doing.Burlesque
From man capabilities, it seems capabilities can be set on an executable file. You could try to build a JNI library calling an executable file with the capability needed. File capabilities > Since kernel 2.6.24, the kernel supports associating capability sets with an executable file using setcap(8).Mikiso
It might be possible to directly execute from Java the executable file with proper capabilities using java.lang.Runtime#exec()Mikiso
M
1

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.

Malawi answered 11/11, 2021 at 7:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.