undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
This commit is contained in:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
144
tests/workbench/PatchVerifier.java
Normal file
144
tests/workbench/PatchVerifier.java
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
package workbench;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Checks at runtime whether the javac topology patches are active.
|
||||
*
|
||||
* Verifies each patch by reflection — no flags, no trust-me properties.
|
||||
*
|
||||
* Patch 0001: GraphUtils$TarjanNode.active boolean field
|
||||
* (replaces O(V²) stack.contains(n) with O(1) field read)
|
||||
* Note: merged into OpenJDK 21+ upstream — will show PRESENT on JDK 21+
|
||||
* even without --patch-module. On older JDKs --patch-module is required.
|
||||
*
|
||||
* Patch 0004: Dependencies — List.contains+add → LinkedHashSet.add
|
||||
* We cannot reliably reflect on the internal node structure without
|
||||
* --add-opens; detected via the presence of a specific inner class shape.
|
||||
* Falls back to "cannot verify" if reflection is blocked.
|
||||
*
|
||||
* Requires --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
|
||||
*
|
||||
* Run standalone (unpatched runtime):
|
||||
* java --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \
|
||||
* -cp . workbench.PatchVerifier
|
||||
*
|
||||
* Run with --patch-module applied:
|
||||
* java --patch-module jdk.compiler=/tmp/jt-all-classes \
|
||||
* --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \
|
||||
* -cp . workbench.PatchVerifier
|
||||
*/
|
||||
public class PatchVerifier {
|
||||
|
||||
private static final String TARJAN_NODE = "com.sun.tools.javac.util.GraphUtils$TarjanNode";
|
||||
|
||||
public record Check(String id, boolean present, String detail) {
|
||||
@Override public String toString() {
|
||||
return (present ? "[OK] " : "[--] ") + id + ": " + detail;
|
||||
}
|
||||
}
|
||||
|
||||
public record Status(boolean allPresent, Check[] checks) {
|
||||
public boolean patched() { return allPresent; }
|
||||
|
||||
@Override public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Check c : checks) sb.append(c).append(" | ");
|
||||
if (sb.length() > 3) sb.setLength(sb.length() - 3);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String summary() {
|
||||
int ok = 0;
|
||||
for (Check c : checks) if (c.present()) ok++;
|
||||
return ok + "/" + checks.length + " patches verified";
|
||||
}
|
||||
}
|
||||
|
||||
public static Status check() {
|
||||
Check[] checks = {
|
||||
check0001(),
|
||||
check0004(),
|
||||
};
|
||||
boolean all = true;
|
||||
for (Check c : checks) if (!c.present()) all = false;
|
||||
return new Status(all, checks);
|
||||
}
|
||||
|
||||
/** 0001: GraphUtils$TarjanNode.active boolean field. */
|
||||
private static Check check0001() {
|
||||
try {
|
||||
Class<?> cls = Class.forName(TARJAN_NODE);
|
||||
try {
|
||||
Field f = cls.getDeclaredField("active");
|
||||
return new Check("0001/GraphUtils.active",
|
||||
true,
|
||||
f.getType().getSimpleName() + " field present — O(V+E) Tarjan (may be upstream JDK)");
|
||||
} catch (NoSuchFieldException e) {
|
||||
return new Check("0001/GraphUtils.active",
|
||||
false,
|
||||
"field missing — stock O(V²) stack.contains(); apply --patch-module");
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
return new Check("0001/GraphUtils.active",
|
||||
false,
|
||||
"GraphUtils not accessible — need --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED");
|
||||
} catch (Exception e) {
|
||||
return new Check("0001/GraphUtils.active", false, "reflection error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** 0004: Dependencies — LinkedHashSet instead of ArrayList for depsByKind. */
|
||||
private static Check check0004() {
|
||||
// Generic type erasure means we can't see LinkedHashSet vs ArrayList via reflection.
|
||||
// Scan the raw class bytes of the inner Node class for the type descriptor.
|
||||
// Stock JDK: "java/util/ArrayList" in constant pool
|
||||
// Patched: "java/util/LinkedHashSet" in constant pool (ArrayList removed)
|
||||
// Node is nested: Dependencies$GraphDependencies$Node
|
||||
try {
|
||||
Class<?> cls = Class.forName(
|
||||
"com.sun.tools.javac.util.Dependencies$GraphDependencies$Node");
|
||||
String resourcePath = cls.getName().replace('.', '/') + ".class";
|
||||
byte[] bytes;
|
||||
// Named module: use Module.getResourceAsStream (not ClassLoader)
|
||||
try (java.io.InputStream in = cls.getModule().getResourceAsStream(resourcePath)) {
|
||||
bytes = in == null ? null : in.readAllBytes();
|
||||
}
|
||||
if (bytes == null) {
|
||||
return new Check("0004/Dependencies.LinkedHashSet",
|
||||
false, "class bytes not readable from class loader");
|
||||
}
|
||||
String pool = new String(bytes, java.nio.charset.StandardCharsets.ISO_8859_1);
|
||||
boolean hasLinkedHashSet = pool.contains("java/util/LinkedHashSet");
|
||||
boolean hasArrayList = pool.contains("java/util/ArrayList");
|
||||
if (hasLinkedHashSet && !hasArrayList) {
|
||||
return new Check("0004/Dependencies.LinkedHashSet",
|
||||
true, "constant pool has LinkedHashSet, no ArrayList — O(1) add");
|
||||
} else if (hasArrayList) {
|
||||
return new Check("0004/Dependencies.LinkedHashSet",
|
||||
false, "constant pool has ArrayList — stock O(N) List.contains+add");
|
||||
} else {
|
||||
return new Check("0004/Dependencies.LinkedHashSet",
|
||||
false, "neither ArrayList nor LinkedHashSet in constant pool (inconclusive)");
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
return new Check("0004/Dependencies.LinkedHashSet",
|
||||
false, "Dependencies not accessible — need --add-exports");
|
||||
} catch (Exception e) {
|
||||
return new Check("0004/Dependencies.LinkedHashSet",
|
||||
false, "byte scan failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** Standalone: prints status, exits 0 if all checks pass. */
|
||||
public static void main(String[] args) {
|
||||
System.out.println("java.home: " + System.getProperty("java.home"));
|
||||
System.out.println("java.version: " + System.getProperty("java.version"));
|
||||
System.out.println();
|
||||
Status s = check();
|
||||
for (Check c : s.checks()) System.out.println(" " + c);
|
||||
System.out.println();
|
||||
System.out.println(" => " + s.summary());
|
||||
System.exit(s.allPresent() ? 0 : 1);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue