Skip to content

Commit fa8bd00

Browse files
committed
test: cover hardened runtime and artifact regressions
1 parent 4ded1f8 commit fa8bd00

35 files changed

Lines changed: 2338 additions & 77 deletions

core-engine/src/test/java/io/github/hht0rro/javashroud/LinuxR1LoadProbe.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ private LinuxR1LoadProbe() {}
1313

1414
static native boolean nativeInstallAkenSessionNonce(byte[] startupNonce);
1515

16+
static native int nativeInstallAkenCatalog(byte[] directory, byte[] bundle);
17+
1618
static native Object nativeExecuteAkenVmPage(
1719
long entryToken,
1820
byte[] encodedHandle,
@@ -27,12 +29,19 @@ static native Object nativeExecuteAkenVmPage(
2729

2830
static native void nativeConsumeAkenNativeChunk(byte[] encodedHandle, int pageIndex, byte[] callSiteProof);
2931

32+
static native int nativeInitializeDefense(String surface, String profile);
33+
34+
static native int nativeProbeDefense(String surface, String point);
35+
36+
static native byte[] nativeTransformDefense(byte[] material, String binding);
37+
3038
public static void main(String[] args) {
3139
if (args.length < 1 || args.length > 2) {
3240
System.err.println("usage: LinuxR1LoadProbe <libjsrt_ffi.so> [catalog-sidecar]");
3341
System.exit(2);
3442
}
3543
System.setProperty("j.l", "io/github/hht0rro/javashroud/LinuxR1LoadProbe");
44+
System.setProperty("j.m", bindingMap());
3645
if (args.length == 2) {
3746
System.setProperty("j.c", args[1]);
3847
}
@@ -48,6 +57,36 @@ public static void main(String[] args) {
4857
}
4958
}
5059

60+
private static String bindingMap() {
61+
String owner = "io/github/hht0rro/javashroud/transforms/protection/JniMicrokernelHelper";
62+
String[][] methods = {
63+
{"nativeInit", "(Ljava/lang/String;)I", "nativeInit"},
64+
{"nativeHeartbeat", "()I", "nativeHeartbeat"},
65+
{"nativeInstallAkenSessionNonce", "([B)Z", "nativeInstallAkenSessionNonce"},
66+
{"nativeInstallAkenCatalog", "([B[B)I", "nativeInstallAkenCatalog"},
67+
{"nativeExecuteAkenVmPage", "(J[BI[B[Ljava/lang/Object;)Ljava/lang/Object;", "nativeExecuteAkenVmPage"},
68+
{"nativeOpenAkenString", "([BI[B)Ljava/lang/String;", "nativeOpenAkenString"},
69+
{"nativeReadAkenClassPage", "([BI[B)[B", "nativeReadAkenClassPage"},
70+
{"nativeConsumeAkenNativeChunk", "([BI[B)V", "nativeConsumeAkenNativeChunk"},
71+
{"nativeInitializeDefense", "(Ljava/lang/String;Ljava/lang/String;)I", "nativeInitializeDefense"},
72+
{"nativeProbeDefense", "(Ljava/lang/String;Ljava/lang/String;)I", "nativeProbeDefense"},
73+
{"nativeTransformDefense", "([BLjava/lang/String;)[B", "nativeTransformDefense"},
74+
};
75+
StringBuilder result = new StringBuilder();
76+
for (String[] method : methods) {
77+
try {
78+
byte[] digest = java.security.MessageDigest.getInstance("SHA-256")
79+
.digest(("AKEN-BINDING-V1|" + owner + "#" + method[0] + "#" + method[1])
80+
.getBytes(java.nio.charset.StandardCharsets.US_ASCII));
81+
for (int i = 0; i < 8; i++) result.append(String.format("%02x", digest[i] & 0xff));
82+
} catch (java.security.NoSuchAlgorithmException error) {
83+
throw new IllegalStateException(error);
84+
}
85+
result.append('=').append(method[2]).append('\n');
86+
}
87+
return result.toString();
88+
}
89+
5190
private static byte[] readAll(String name) {
5291
String root = System.getProperty("j.c");
5392
try {

core-engine/src/test/java/io/github/hht0rro/javashroud/R1RenamedNativeSurface.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,19 @@ private R1RenamedNativeSurface() {}
1414

1515
static native boolean nNonce(byte[] startupNonce);
1616

17+
static native int nCatalog(byte[] directory, byte[] bundle);
18+
1719
static native Object nVm(long entryToken, byte[] encodedHandle, int pageIndex, byte[] callSiteProof, Object[] args);
1820

1921
static native String nStr(byte[] encodedHandle, int pageIndex, byte[] callSiteProof);
2022

2123
static native byte[] nCls(byte[] encodedHandle, int pageIndex, byte[] callSiteProof);
2224

2325
static native void nNat(byte[] encodedHandle, int pageIndex, byte[] callSiteProof);
26+
27+
static native int nDefenseInit(String surface, String profile);
28+
29+
static native int nDefenseProbe(String surface, String point);
30+
31+
static native byte[] nDefenseTransform(byte[] material, String binding);
2432
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package io.github.hht0rro.javashroud;
2+
3+
/**
4+
* Fresh-JVM probe for protected string-page call overhead against a trivial baseline.
5+
*/
6+
public final class WindowsR1OverheadProbe {
7+
private WindowsR1OverheadProbe() {}
8+
9+
static native int nativeInit(String platform);
10+
11+
static native int nativeHeartbeat();
12+
13+
static native boolean nativeInstallAkenSessionNonce(byte[] startupNonce);
14+
15+
static native int nativeInstallAkenCatalog(byte[] directory, byte[] bundle);
16+
17+
static native Object nativeExecuteAkenVmPage(
18+
long entryToken,
19+
byte[] encodedHandle,
20+
int pageIndex,
21+
byte[] callSiteProof,
22+
Object[] args
23+
);
24+
25+
static native String nativeOpenAkenString(byte[] encodedHandle, int pageIndex, byte[] callSiteProof);
26+
27+
static native byte[] nativeReadAkenClassPage(byte[] encodedHandle, int pageIndex, byte[] callSiteProof);
28+
29+
static native void nativeConsumeAkenNativeChunk(byte[] encodedHandle, int pageIndex, byte[] callSiteProof);
30+
31+
static native int nativeInitializeDefense(String surface, String profile);
32+
33+
static native int nativeProbeDefense(String surface, String point);
34+
35+
static native byte[] nativeTransformDefense(byte[] material, String binding);
36+
37+
public static void main(String[] args) {
38+
if (args.length != 1) {
39+
System.err.println("usage: WindowsR1OverheadProbe <jsrt_ffi.dll>");
40+
System.exit(2);
41+
}
42+
System.setProperty("j.l", "io/github/hht0rro/javashroud/WindowsR1OverheadProbe");
43+
System.setProperty("j.m", bindingMap());
44+
long startupStart = System.nanoTime();
45+
System.load(args[0]);
46+
if (nativeInit("windows-x64") != 0 || nativeHeartbeat() < 0) {
47+
System.err.println("native init failed");
48+
System.exit(3);
49+
}
50+
byte[] nonce = new byte[32];
51+
new java.security.SecureRandom().nextBytes(nonce);
52+
if (!nativeInstallAkenSessionNonce(nonce)) {
53+
System.err.println("native nonce install failed");
54+
System.exit(3);
55+
}
56+
java.util.Arrays.fill(nonce, (byte) 0);
57+
if (nativeInitializeDefense("os-anti-debug", "balanced") != 0) {
58+
System.err.println("native defense init failed");
59+
System.exit(3);
60+
}
61+
long startupNs = System.nanoTime() - startupStart;
62+
for (int i = 0; i < 8; i++) {
63+
if (nativeProbeDefense("os-anti-debug", "warmup") != 0) {
64+
System.err.println("native defense warmup failed");
65+
System.exit(3);
66+
}
67+
}
68+
int iterations = 32;
69+
long nativeNs = time(iterations, () -> {
70+
if (nativeProbeDefense("os-anti-debug", "steady-state") != 0) {
71+
throw new IllegalStateException("native defense probe failed");
72+
}
73+
});
74+
long baselineNs = time(iterations, () -> { String ignored = "steady-state"; });
75+
double ratio = (double) nativeNs / (double) Math.max(1L, baselineNs);
76+
System.out.println("STARTUP_NS=" + startupNs);
77+
System.out.println("NATIVE_NS=" + nativeNs);
78+
System.out.println("BASELINE_NS=" + baselineNs);
79+
System.out.println("RATIO=" + ratio);
80+
System.out.println("BUDGET=" + 3.0);
81+
if (nativeNs / iterations >= 50_000_000L) {
82+
System.err.println("protected call exceeded 50ms sanity ceiling");
83+
System.exit(4);
84+
}
85+
System.out.println("OVERHEAD_OK");
86+
}
87+
88+
private static byte[] buildCatalogBundle() {
89+
String[] paths = {"pages/page-3.bin", "pages/page-4.bin", "pages/page-5.bin", "pages/page-6.bin"};
90+
try {
91+
java.io.ByteArrayOutputStream body = new java.io.ByteArrayOutputStream();
92+
java.io.DataOutputStream out = new java.io.DataOutputStream(body);
93+
out.writeInt(paths.length);
94+
for (String path : paths) {
95+
byte[] name = path.getBytes(java.nio.charset.StandardCharsets.UTF_8);
96+
byte[] page = readAll(path);
97+
out.writeInt(name.length);
98+
out.write(name);
99+
out.writeInt(page.length);
100+
out.write(page);
101+
java.util.Arrays.fill(name, (byte) 0);
102+
java.util.Arrays.fill(page, (byte) 0);
103+
}
104+
out.flush();
105+
return body.toByteArray();
106+
} catch (java.io.IOException error) {
107+
throw new IllegalStateException("catalog bundle", error);
108+
}
109+
}
110+
111+
private static String bindingMap() {
112+
String owner = "io/github/hht0rro/javashroud/transforms/protection/JniMicrokernelHelper";
113+
String[][] methods = {
114+
{"nativeInit", "(Ljava/lang/String;)I", "nativeInit"},
115+
{"nativeHeartbeat", "()I", "nativeHeartbeat"},
116+
{"nativeInstallAkenSessionNonce", "([B)Z", "nativeInstallAkenSessionNonce"},
117+
{"nativeInstallAkenCatalog", "([B[B)I", "nativeInstallAkenCatalog"},
118+
{"nativeExecuteAkenVmPage", "(J[BI[B[Ljava/lang/Object;)Ljava/lang/Object;", "nativeExecuteAkenVmPage"},
119+
{"nativeOpenAkenString", "([BI[B)Ljava/lang/String;", "nativeOpenAkenString"},
120+
{"nativeReadAkenClassPage", "([BI[B)[B", "nativeReadAkenClassPage"},
121+
{"nativeConsumeAkenNativeChunk", "([BI[B)V", "nativeConsumeAkenNativeChunk"},
122+
{"nativeInitializeDefense", "(Ljava/lang/String;Ljava/lang/String;)I", "nativeInitializeDefense"},
123+
{"nativeProbeDefense", "(Ljava/lang/String;Ljava/lang/String;)I", "nativeProbeDefense"},
124+
{"nativeTransformDefense", "([BLjava/lang/String;)[B", "nativeTransformDefense"},
125+
};
126+
StringBuilder result = new StringBuilder();
127+
for (String[] method : methods) {
128+
byte[] digest;
129+
try {
130+
digest = java.security.MessageDigest.getInstance("SHA-256")
131+
.digest(("AKEN-BINDING-V1|" + owner + "#" + method[0] + "#" + method[1])
132+
.getBytes(java.nio.charset.StandardCharsets.US_ASCII));
133+
} catch (java.security.NoSuchAlgorithmException error) {
134+
throw new IllegalStateException(error);
135+
}
136+
for (int i = 0; i < 8; i++) result.append(String.format("%02x", digest[i] & 0xff));
137+
result.append('=').append(method[2]).append('\n');
138+
}
139+
return result.toString();
140+
}
141+
142+
private static long time(int iterations, Runnable block) {
143+
long start = System.nanoTime();
144+
for (int i = 0; i < iterations; i++) {
145+
block.run();
146+
}
147+
return System.nanoTime() - start;
148+
}
149+
150+
private static byte[] readAll(String name) {
151+
String root = System.getProperty("j.c");
152+
try {
153+
return java.nio.file.Files.readAllBytes(java.nio.file.Path.of(root, name));
154+
} catch (Exception error) {
155+
throw new IllegalStateException(name, error);
156+
}
157+
}
158+
}

core-engine/src/test/kotlin/io/github/hht0rro/javashroud/AkenNativeLocatorTest.kt

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import io.github.hht0rro.javashroud.transforms.protection.RuntimeArtifactSealing
1010
import io.github.hht0rro.javashroud.transforms.protection.VBC4_LAYOUT_DIGEST_SIZE
1111
import io.github.hht0rro.javashroud.transforms.protection.VBC4_MASTER_KEY_SIZE
1212
import io.github.hht0rro.javashroud.transforms.protection.Vbc4BuildContext
13+
import io.github.hht0rro.javashroud.transforms.protection.requireVbc4BuildContext
1314
import io.github.hht0rro.javashroud.transforms.protection.withVbc4BuildContext
1415
import java.nio.charset.StandardCharsets
1516
import java.security.MessageDigest
@@ -157,6 +158,53 @@ class AkenNativeLocatorTest {
157158
}
158159
}
159160

161+
@Test
162+
fun catalogBindingInputs_matches_library_sha256_and_nonzero_abi_digest() {
163+
val storedBytes = byteArrayOf(1, 2, 3, 4)
164+
val entry = AkenNativeLocator.entry(
165+
platform = "windows-x64",
166+
resourcePath = "META-INF/final.dll",
167+
fileSuffix = ".dll",
168+
storedBytes = storedBytes,
169+
)
170+
AkenNativeLocator.catalogBindingInputs(listOf(entry)).use { inputs ->
171+
assertTrue(
172+
MessageDigest.getInstance("SHA-256").digest(storedBytes).contentEquals(inputs.nativeSha256),
173+
"catalog native SHA-256 must match sha256(storedBytes)",
174+
)
175+
assertEquals(32, inputs.abiDigest.size)
176+
assertTrue(inputs.abiDigest.any { it != 0.toByte() }, "catalog ABI digest must not be all-zero")
177+
assertEquals("x86_64-pc-windows-gnu", inputs.targetTriple)
178+
assertEquals("aken-r1-rust-ffi-v1", inputs.payloadProfile)
179+
assertEquals("windows-x64", inputs.platform)
180+
}
181+
}
182+
183+
@Test
184+
fun catalogBindingInputs_rejects_empty_entries() {
185+
assertFailsWith<IllegalArgumentException> {
186+
AkenNativeLocator.catalogBindingInputs(emptyList())
187+
}
188+
}
189+
190+
@Test
191+
fun catalogBindingInputs_wipe_zeros_owned_copies() {
192+
val entry = AkenNativeLocator.entry(
193+
platform = "linux-x64",
194+
resourcePath = "META-INF/final.so",
195+
fileSuffix = ".so",
196+
storedBytes = byteArrayOf(7, 8, 9),
197+
)
198+
val inputs = AkenNativeLocator.catalogBindingInputs(listOf(entry))
199+
val nativeSha256 = inputs.nativeSha256
200+
val abiDigest = inputs.abiDigest
201+
assertTrue(nativeSha256.any { it != 0.toByte() })
202+
assertTrue(abiDigest.any { it != 0.toByte() })
203+
inputs.wipe()
204+
assertTrue(nativeSha256.all { it == 0.toByte() })
205+
assertTrue(abiDigest.all { it == 0.toByte() })
206+
}
207+
160208
@Test
161209
fun sealing_rejects_macos_native_entries_before_rewriting() {
162210
assertFailsWith<IllegalStateException> {
@@ -180,7 +228,14 @@ class AkenNativeLocatorTest {
180228
masterKey = ByteArray(VBC4_MASTER_KEY_SIZE) { (it * 29 + 7).toByte() },
181229
nativeSeed = 0x5A17C0DEL,
182230
jarLayoutDigest = ByteArray(VBC4_LAYOUT_DIGEST_SIZE) { (it * 17 + 3).toByte() },
183-
),
231+
).also { context ->
232+
context.publishNativeSpecializationDigests(
233+
mapOf(
234+
"windows-x64" to ByteArray(32) { (it + 1).toByte() },
235+
"linux-x64" to ByteArray(32) { (it + 2).toByte() },
236+
),
237+
)
238+
},
184239
) {
185240
val helperName = "io/github/hht0rro/javashroud/transforms/protection/JniMicrokernelHelper"
186241
val helperBytes = checkNotNull(javaClass.classLoader.getResourceAsStream("$helperName.class")).use { it.readBytes() }
@@ -197,6 +252,12 @@ class AkenNativeLocatorTest {
197252
JarEntryData("META-INF/jsrt/linux-x64/libjsrt_ffi.so", linuxBytes),
198253
) + listOfNotNull(additionalNativeEntry),
199254
)
255+
requireVbc4BuildContext().publishNativeSpecializationDigests(
256+
mapOf(
257+
"windows-x64" to ByteArray(32) { (it + 1).toByte() },
258+
"linux-x64" to ByteArray(32) { (it + 2).toByte() },
259+
),
260+
)
200261
RuntimeArtifactSealing.seal(artifact, seed = 0x5A17C0DEL, rewritesVmRuntime = false)
201262
}
202263

0 commit comments

Comments
 (0)