Add README and SecureBuffer memory locking

This commit is contained in:
Oracle 2026-04-29 19:13:48 +02:00
parent bc1a7a8952
commit 7c6642085f
Signed by: Oracle
SSH key fingerprint: SHA256:x4/RtnjUyuHkdvmwNDsWSfcfF1V5PNr3OpriZqOvCX8
7 changed files with 829 additions and 143 deletions

View file

@ -3,8 +3,10 @@ package ai.nomyo;
import lombok.Getter;
import lombok.Setter;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Map;
/**
@ -21,12 +23,49 @@ public final class SecureMemory {
@Setter
private static volatile boolean secureMemoryEnabled = true;
private static final MethodHandle MLOCK_HANDLE;
private static final MethodHandle MUNLOCK_HANDLE;
static {
boolean locking = false;
boolean zeroing = false;
MethodHandle mlockHandle = null;
MethodHandle munlockHandle = null;
try {
locking = initMemoryLocking();
Linker linker = Linker.nativeLinker();
SymbolLookup stdLib = linker.defaultLookup();
var mlockOpt = stdLib.find("mlock");
var munlockOpt = stdLib.find("munlock");
if (mlockOpt.isPresent() && munlockOpt.isPresent()) {
MemorySegment mlockAddr = mlockOpt.get();
MemorySegment munlockAddr = munlockOpt.get();
FunctionDescriptor mlockDesc = FunctionDescriptor.of(
ValueLayout.JAVA_INT,
ValueLayout.ADDRESS,
ValueLayout.JAVA_LONG);
FunctionDescriptor munlockDesc = FunctionDescriptor.of(
ValueLayout.JAVA_INT,
ValueLayout.ADDRESS,
ValueLayout.JAVA_LONG);
mlockHandle = linker.downcallHandle(mlockAddr, mlockDesc);
munlockHandle = linker.downcallHandle(munlockAddr, munlockDesc);
locking = true;
}
} catch (Throwable t) {
// Degrade gracefully
}
MLOCK_HANDLE = mlockHandle;
MUNLOCK_HANDLE = munlockHandle;
try {
locking = initMemoryLocking(locking);
zeroing = true; // Secure zeroing is always available at the JVM level
} catch (Throwable t) {
// Degrade gracefully
@ -36,9 +75,18 @@ public final class SecureMemory {
HAS_SECURE_ZEROING = zeroing;
}
private static boolean initMemoryLocking() {
// FFM doesn't support memory locking at this point in time
return false;
private static boolean initMemoryLocking(boolean preCheck) {
if (MLOCK_HANDLE == null || MUNLOCK_HANDLE == null || !preCheck) {
return false;
}
try (Arena arena = Arena.ofConfined()) {
MemorySegment testSegment = arena.allocate(1);
long result = (long) MLOCK_HANDLE.invokeExact(testSegment, (long) 1);
return result == 0;
} catch (Throwable t) {
return false;
}
}
/**
@ -93,7 +141,7 @@ public final class SecureMemory {
private boolean closed;
/**
* @param data byte array to wrap
* @param data byte array to wrap (zeroed after copy to off-heap memory)
* @param lock whether to attempt memory locking
*/
public SecureBuffer(byte[] data, boolean lock) {
@ -102,6 +150,7 @@ public final class SecureMemory {
if (data != null) {
this.data.asByteBuffer().put(data);
Arrays.fill(data, (byte) 0);
}
this.size = this.data.byteSize();
@ -119,16 +168,40 @@ public final class SecureMemory {
* Locks buffer in memory (prevents disk swapping). Returns false if unavailable.
*/
public boolean lock() {
return false;
if (this.locked || this.address == 0) {
return this.locked;
}
try {
long result = (long) MLOCK_HANDLE.invokeExact(
MemorySegment.ofAddress(this.address),
this.size);
this.locked = result == 0;
} catch (Throwable t) {
this.locked = false;
}
return this.locked;
}
/**
* Unlocks buffer (allows disk swapping).
*/
public boolean unlock() {
if (!locked) return false;
locked = false;
return false;
if (!locked || this.address == 0) {
return false;
}
try {
long result = (long) MUNLOCK_HANDLE.invokeExact(
MemorySegment.ofAddress(this.address),
this.size);
locked = result != 0;
return result == 0;
} catch (Throwable t) {
locked = true;
return false;
}
}
/**