Skip to content

Commit 48e06bb

Browse files
fix(aop): add JCE crypto-policy read exemption to the AspectJ backend
1 parent a225957 commit 48e06bb

4 files changed

Lines changed: 88 additions & 29 deletions

File tree

src/main/java/de/tum/cit/ase/ares/api/aop/java/aspectj/adviceandpointcut/JavaAspectJAbstractAdviceDefinitions.aj

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,29 @@ public abstract aspect JavaAspectJAbstractAdviceDefinitions {
354354
});
355355
}
356356

357+
/**
358+
* Returns {@code true} when the current call stack is inside
359+
* {@code javax.crypto.JceSecurity}'s own JCE jurisdiction-policy scan (its
360+
* static initialiser calls {@code Files.newDirectoryStream} with the fixed
361+
* {@code {default,exempt}_*.policy} glob against the real {@code java.home}
362+
* policy directory). The signal is precise and cannot be spoofed: a student
363+
* calling the same JDK file-system APIs directly, even with an identical file
364+
* name or glob argument, has no {@code JceSecurity} frame between their own
365+
* code and the read, so such an access stays blocked. Because
366+
* {@code JceSecurity} never takes a student-influenceable path/glob argument,
367+
* this frame check alone precisely identifies the genuine JVM-triggered scan —
368+
* no additional location check on the intercepted argument is needed or
369+
* possible, since the bare glob argument resolves against the working
370+
* directory, not {@code java.home}, when converted to a path.
371+
*
372+
* @return {@code true} if a {@code javax.crypto.JceSecurity} frame is present
373+
* on the current stack
374+
*/
375+
static boolean isJceCryptoPolicyScanInProgress() {
376+
return STACK_WALKER.walk(
377+
frames -> frames.map(StackWalker.StackFrame::getClassName).anyMatch(className -> className.startsWith("javax.crypto.JceSecurity")));
378+
}
379+
357380
/**
358381
* Returns {@code true} only while Ares itself is reading framework support
359382
* files for structural and architecture test setup through one of its trusted

src/main/java/de/tum/cit/ase/ares/api/aop/java/aspectj/adviceandpointcut/JavaAspectJFileSystemAdviceDefinitions.aj

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,32 +1215,39 @@ public aspect JavaAspectJFileSystemAdviceDefinitions extends JavaAspectJAbstract
12151215
* infrastructure, not student file access; blocking it makes cryptography fail
12161216
* to initialise ({@code NoClassDefFoundError} / "Can not initialize
12171217
* cryptographic mechanism"). The instrumentation backend applies this same
1218-
* exemption, so this keeps the two backends consistent. The match requires the
1219-
* path to resolve under the trusted JDK installation ({@code java.home}) in
1220-
* addition to the JCE policy naming scheme, so a student-controlled file
1221-
* living outside {@code java.home} cannot bypass the read policy merely by
1222-
* being given one of the exempt names.
1218+
* exemption, so this keeps the two backends consistent. The bare
1219+
* {@code {default,exempt}_*.policy} directory-scan glob is not itself a real
1220+
* file path — it resolves against the working directory, not
1221+
* {@code java.home}, when converted to a path — so a location check on the
1222+
* intercepted argument cannot validate it. The match instead requires the
1223+
* access to originate from {@code javax.crypto.JceSecurity}'s own scan (see
1224+
* {@link #isJceCryptoPolicyScanInProgress()}), which a student cannot spoof
1225+
* and which never takes a student-influenceable path/glob argument, so a
1226+
* student-controlled file (or a directly-called {@code newDirectoryStream}
1227+
* with the same glob) cannot bypass the read policy merely by matching one of
1228+
* the exempt names.
12231229
*
12241230
* @param path the already-resolved path string under inspection
1225-
* @return true if the path is a JCE crypto policy file or scan glob
1231+
* @return true if the path is a JCE crypto policy file or scan glob read by a
1232+
* genuine JCE-triggered scan
12261233
*/
12271234
private static boolean isCryptoPolicyPath(@Nullable String path) {
12281235
if (path == null) {
12291236
return false;
12301237
}
1231-
// SECURITY: The name match alone is not sufficient — a student-controlled file
1232-
// living outside java.home could be given one of these exact names to bypass the
1233-
// read policy. Require the path to actually resolve under the trusted JDK
1234-
// installation first, the same trust root isExemptSystemFileAccess uses.
1235-
if (!isPathWithin(path, TRUSTED_JAVA_HOME)) {
1236-
return false;
1237-
}
12381238
int slash = path.lastIndexOf('/');
12391239
String name = slash >= 0 ? path.substring(slash + 1) : path;
12401240
// Match only the fixed JCE jurisdiction-policy file names and the exact
12411241
// directory-scan glob JceSecurity uses, never an arbitrary "default_*"/"exempt_*"
12421242
// prefix, so a student-named file such as "default_tokens.policy" is NOT exempt.
1243-
return CRYPTO_POLICY_NAMES.contains(name);
1243+
if (!CRYPTO_POLICY_NAMES.contains(name)) {
1244+
return false;
1245+
}
1246+
// SECURITY: the name match alone is not sufficient — a student-controlled file
1247+
// (or a directly student-called newDirectoryStream) could bear one of these
1248+
// exact names/glob to bypass the read policy. Require the read to actually
1249+
// originate from JCE's own scanning code.
1250+
return isJceCryptoPolicyScanInProgress();
12441251
}
12451252

12461253
/**

src/main/java/de/tum/cit/ase/ares/api/aop/java/instrumentation/advice/JavaInstrumentationAdviceAbstractToolbox.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,29 @@ static boolean isClassLoadingInProgress() {
343343
});
344344
}
345345

346+
/**
347+
* Returns {@code true} when the current call stack is inside
348+
* {@code javax.crypto.JceSecurity}'s own JCE jurisdiction-policy scan (its
349+
* static initialiser calls {@code Files.newDirectoryStream} with the fixed
350+
* {@code {default,exempt}_*.policy} glob against the real {@code java.home}
351+
* policy directory). The signal is precise and cannot be spoofed: a student
352+
* calling the same JDK file-system APIs directly, even with an identical file
353+
* name or glob argument, has no {@code JceSecurity} frame between their own
354+
* code and the read, so such an access stays blocked. Because
355+
* {@code JceSecurity} never takes a student-influenceable path/glob argument,
356+
* this frame check alone precisely identifies the genuine JVM-triggered scan —
357+
* no additional location check on the intercepted argument is needed or
358+
* possible, since the bare glob argument resolves against the working
359+
* directory, not {@code java.home}, when converted to a path.
360+
*
361+
* @return {@code true} if a {@code javax.crypto.JceSecurity} frame is present
362+
* on the current stack
363+
*/
364+
static boolean isJceCryptoPolicyScanInProgress() {
365+
return STACK_WALKER.walk(frames -> frames.map(StackWalker.StackFrame::getClassName)
366+
.anyMatch(className -> className.startsWith("javax.crypto.JceSecurity")));
367+
}
368+
346369
/**
347370
* Returns {@code true} only while Ares itself is reading framework support
348371
* files for structural and architecture test setup through one of its trusted

src/main/java/de/tum/cit/ase/ares/api/aop/java/instrumentation/advice/JavaInstrumentationAdviceFileSystemToolbox.java

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,35 +1333,41 @@ private static boolean isJdkNativeLibraryLoad(@Nonnull String path) {
13331333
* infrastructure, not student file access; blocking it makes cryptography fail
13341334
* to initialise ({@code NoClassDefFoundError} / "Can not initialize
13351335
* cryptographic mechanism"). The AspectJ backend applies this same exemption,
1336-
* so this keeps the two backends consistent. The match requires the path to
1337-
* resolve under the trusted JDK installation ({@code java.home}) in addition to
1338-
* the JCE policy naming scheme, so a student-controlled file living outside
1339-
* {@code java.home} cannot bypass the read policy merely by being given one of
1340-
* the exempt names.
1336+
* so this keeps the two backends consistent. The bare
1337+
* {@code {default,exempt}_*.policy} directory-scan glob is not itself a real
1338+
* file path — it resolves against the working directory, not {@code java.home},
1339+
* when converted to a path — so a location check on the intercepted argument
1340+
* cannot validate it. The match instead requires the access to originate from
1341+
* {@code javax.crypto.JceSecurity}'s own scan (see
1342+
* {@link #isJceCryptoPolicyScanInProgress()}), which a student cannot spoof and
1343+
* which never takes a student-influenceable path/glob argument, so a
1344+
* student-controlled file (or a directly-called {@code newDirectoryStream} with
1345+
* the same glob) cannot bypass the read policy merely by matching one of the
1346+
* exempt names.
13411347
*
13421348
* @param path the already-resolved path string under inspection
1343-
* @return true if the path is a JCE crypto policy file or scan glob
1349+
* @return true if the path is a JCE crypto policy file or scan glob read by a
1350+
* genuine JCE-triggered scan
13441351
*/
13451352
private static boolean isCryptoPolicyPath(@Nullable String path) {
13461353
if (path == null) {
13471354
return false;
13481355
}
1349-
// SECURITY: The name match alone is not sufficient — a student-controlled file
1350-
// living outside java.home could be given one of these exact names to bypass
1351-
// the
1352-
// read policy. Require the path to actually resolve under the trusted JDK
1353-
// installation first, the same trust root isExemptSystemFileAccess uses.
1354-
if (!isPathWithin(path, TRUSTED_JAVA_HOME)) {
1355-
return false;
1356-
}
13571356
int slash = path.lastIndexOf('/');
13581357
String name = slash >= 0 ? path.substring(slash + 1) : path;
13591358
// Match only the fixed JCE jurisdiction-policy file names and the exact
13601359
// directory-scan glob JceSecurity uses, never an arbitrary
13611360
// "default_*"/"exempt_*"
13621361
// prefix, so a student-named file such as "default_tokens.policy" is NOT
13631362
// exempt.
1364-
return CRYPTO_POLICY_NAMES.contains(name);
1363+
if (!CRYPTO_POLICY_NAMES.contains(name)) {
1364+
return false;
1365+
}
1366+
// SECURITY: the name match alone is not sufficient — a student-controlled file
1367+
// (or a directly student-called newDirectoryStream) could bear one of these
1368+
// exact names/glob to bypass the read policy. Require the read to actually
1369+
// originate from JCE's own scanning code.
1370+
return isJceCryptoPolicyScanInProgress();
13651371
}
13661372

13671373
/**

0 commit comments

Comments
 (0)