Skip to content

Commit 7e201e4

Browse files
authored
Move to Unsafe, which does not require module opens. (#11)
Added system property to specify which accessor to use. -Dsecurejarhandler.useUnsafeAccessor=false (defaults to true)
1 parent 7a898ac commit 7e201e4

10 files changed

Lines changed: 168 additions & 99 deletions

File tree

src/main/java/cpw/mods/jarhandling/impl/Jar.java

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public class Jar implements SecureJar {
2929
private static final UnionFileSystemProvider UFSP = (UnionFileSystemProvider) FileSystemProvider.installedProviders().stream().filter(fsp->fsp.getScheme().equals("union")).findFirst().orElseThrow(()->new IllegalStateException("Couldn't find UnionFileSystemProvider"));
3030
private final Manifest manifest;
3131
private final Hashtable<String, CodeSigner[]> pendingSigners = new Hashtable<>();
32-
private final Hashtable<String, CodeSigner[]> existingSigners = new Hashtable<>();
32+
private final Hashtable<String, CodeSigner[]> verifiedSigners = new Hashtable<>();
3333
private final ManifestVerifier verifier = new ManifestVerifier();
3434
private final Map<String, StatusData> statusData = new HashMap<>();
3535
private final JarMetadata metadata;
@@ -84,16 +84,16 @@ public Jar(final Supplier<Manifest> defaultManifest, final Function<SecureJar, J
8484
}
8585
} else {
8686
try (var jis = new JarInputStream(Files.newInputStream(path))) {
87-
var jv = SecureJarVerifier.jarVerifier.get(jis);
87+
var jv = SecureJarVerifier.getJarVerifier(jis);
8888
if (jv != null) {
89-
while ((Boolean)SecureJarVerifier.parsingMeta.get(jv)) {
89+
while (SecureJarVerifier.isParsingMeta(jv)) {
9090
jis.getNextJarEntry();
9191
}
9292

93-
if (SecureJarVerifier.anyToVerify.getBoolean(jv)) {
94-
pendingSigners.putAll((Hashtable<String, CodeSigner[]>) SecureJarVerifier.sigFileSigners.get(SecureJarVerifier.jarVerifier.get(jis)));
95-
existingSigners.put(JarFile.MANIFEST_NAME, ((Hashtable<String, CodeSigner[]>) SecureJarVerifier.existingSigners.get(SecureJarVerifier.jarVerifier.get(jis))).get(JarFile.MANIFEST_NAME));
96-
StatusData.add(JarFile.MANIFEST_NAME, Status.VERIFIED, existingSigners.get(JarFile.MANIFEST_NAME), this);
93+
if (SecureJarVerifier.hasSignatures(jv)) {
94+
pendingSigners.putAll(SecureJarVerifier.getPendingSigners(jv));
95+
verifiedSigners.put(JarFile.MANIFEST_NAME, SecureJarVerifier.getVerifiedSigners(jv).get(JarFile.MANIFEST_NAME));
96+
StatusData.add(JarFile.MANIFEST_NAME, Status.VERIFIED, verifiedSigners.get(JarFile.MANIFEST_NAME), this);
9797
}
9898
}
9999

@@ -105,8 +105,6 @@ public Jar(final Supplier<Manifest> defaultManifest, final Function<SecureJar, J
105105
}
106106
}
107107
this.manifest = mantmp == null ? defaultManifest.get() : mantmp;
108-
} catch (IllegalAccessException e) {
109-
throw new RuntimeException(e);
110108
} catch (IOException e) {
111109
throw new UncheckedIOException(e);
112110
}
@@ -146,7 +144,7 @@ public synchronized CodeSigner[] verifyAndGetSigners(final String name, final by
146144
if (!hasSecurityData()) return null;
147145
if (statusData.containsKey(name)) return statusData.get(name).signers;
148146

149-
var signers = verifier.verify(this.manifest, pendingSigners, existingSigners, name, bytes);
147+
var signers = verifier.verify(this.manifest, pendingSigners, verifiedSigners, name, bytes);
150148
if (signers == null) {
151149
StatusData.add(name, Status.INVALID, null, this);
152150
return null;
@@ -193,7 +191,7 @@ public Attributes getTrustedManifestEntries(final String name) {
193191
}
194192
@Override
195193
public boolean hasSecurityData() {
196-
return !pendingSigners.isEmpty() || !this.existingSigners.isEmpty();
194+
return !pendingSigners.isEmpty() || !this.verifiedSigners.isEmpty();
197195
}
198196

199197
@Override

src/main/java/cpw/mods/jarhandling/impl/ManifestVerifier.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,4 @@ record Expected(MessageDigest hash, byte[] value){};
8181
verified.put(name, signers);
8282
return Optional.ofNullable(signers);
8383
}
84-
}
84+
}
Lines changed: 123 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,18 @@
11
package cpw.mods.jarhandling.impl;
22

3+
import sun.misc.Unsafe;
4+
35
import java.lang.reflect.Field;
4-
import java.net.spi.URLStreamHandlerProvider;
6+
import java.security.CodeSigner;
57
import java.util.Locale;
8+
import java.util.Map;
69
import java.util.jar.JarInputStream;
710

811
public class SecureJarVerifier {
9-
static final Field jarVerifier;
10-
static final Field parsingMeta;
11-
static final Field existingSigners;
12-
static final Field sigFileSigners;
13-
static final Field anyToVerify;
14-
15-
static {
16-
final var moduleLayer = ModuleLayer.boot();
17-
final var gj9hOptional = moduleLayer.findModule("cpw.mods.securejarhandler");
18-
if (gj9hOptional.isPresent()) {
19-
final var gj9h = gj9hOptional.get();
20-
moduleLayer
21-
.findModule("java.base")
22-
.filter(m-> m.isOpen("java.util.jar", gj9h) && m.isExported("sun.security.util", gj9h))
23-
.orElseThrow(()->new IllegalStateException("""
24-
Missing JVM arguments. Please correct your runtime profile and run again.
25-
--add-opens java.base/java.util.jar=cpw.mods.securejarhandler
26-
--add-exports java.base/sun.security.util=cpw.mods.securejarhandler"""));
27-
} else if (Boolean.parseBoolean(System.getProperty("securejarhandler.throwOnMissingModule", "true"))) {
28-
// Hack for JMH benchmark: in JMH, SecureJarHandler does not load as a module, but we add-open to all unnamed in the jvm args
29-
throw new RuntimeException("Failed to find securejarhandler module!");
30-
}
31-
try {
32-
jarVerifier = JarInputStream.class.getDeclaredField("jv");
33-
sigFileSigners = jarVerifier.getType().getDeclaredField("sigFileSigners");
34-
existingSigners = jarVerifier.getType().getDeclaredField("verifiedSigners");
35-
parsingMeta = jarVerifier.getType().getDeclaredField("parsingMeta");
36-
anyToVerify = jarVerifier.getType().getDeclaredField("anyToVerify");
37-
jarVerifier.setAccessible(true);
38-
sigFileSigners.setAccessible(true);
39-
existingSigners.setAccessible(true);
40-
parsingMeta.setAccessible(true);
41-
anyToVerify.setAccessible(true);
42-
} catch (NoSuchFieldException e) {
43-
throw new IllegalStateException("Missing essential fields", e);
44-
}
45-
}
12+
private static final boolean USE_UNSAAFE = Boolean.parseBoolean(System.getProperty("securejarhandler.useUnsafeAccessor", "true"));
13+
private static IAccessor ACCESSOR = USE_UNSAAFE ? new UnsafeAccessor() : new Reflection();
4614

4715
private static final char[] LOOKUP = "0123456789abcdef".toCharArray();
48-
4916
public static String toHexString(final byte[] bytes) {
5017
final var buffer = new StringBuffer(2*bytes.length);
5118
for (int i = 0, bytesLength = bytes.length; i < bytesLength; i++) {
@@ -65,9 +32,9 @@ public static boolean isSigningRelated(String path) {
6532
if (filename.indexOf('/') != -1) // Can't be a sub-directory
6633
return false;
6734
if ("manifest.mf".equals(filename) || // Main manifest, which has the file hashes
68-
filename.endsWith(".sf") || // Signature file, which has hashes of the entries in the manifest file
69-
filename.endsWith(".dsa") || // PKCS7 signature, DSA
70-
filename.endsWith(".rsa")) // PKCS7 signature, SHA-256 + RSA
35+
filename.endsWith(".sf") || // Signature file, which has hashes of the entries in the manifest file
36+
filename.endsWith(".dsa") || // PKCS7 signature, DSA
37+
filename.endsWith(".rsa")) // PKCS7 signature, SHA-256 + RSA
7138
return true;
7239

7340
if (!filename.startsWith("sig-")) // Unspecifed signature format
@@ -85,4 +52,118 @@ public static boolean isSigningRelated(String path) {
8552
}
8653
return true;
8754
}
55+
56+
public static Object getJarVerifier(Object inst) {
57+
return ACCESSOR.getJarVerifier(inst);
58+
}
59+
public static boolean isParsingMeta(Object inst) { return ACCESSOR.isParsingMeta(inst); }
60+
public static boolean hasSignatures(Object inst) { return ACCESSOR.hasSignatures(inst); }
61+
public static Map<String, CodeSigner[]> getVerifiedSigners(Object inst){ return ACCESSOR.getVerifiedSigners(inst); }
62+
public static Map<String, CodeSigner[]> getPendingSigners(Object inst){ return ACCESSOR.getPendingSigners(inst); }
63+
64+
private interface IAccessor {
65+
Object getJarVerifier(Object inst);
66+
boolean isParsingMeta(Object inst);
67+
boolean hasSignatures(Object inst);
68+
Map<String, CodeSigner[]> getVerifiedSigners(Object inst);
69+
Map<String, CodeSigner[]> getPendingSigners(Object inst);
70+
}
71+
72+
private static class Reflection implements IAccessor {
73+
private static final Field jarVerifier;
74+
private static final Field parsingMeta;
75+
private static final Field verifiedSigners;
76+
private static final Field sigFileSigners;
77+
private static final Field anyToVerify;
78+
79+
static {
80+
final var moduleLayer = ModuleLayer.boot();
81+
final var myModule = moduleLayer.findModule("cpw.mods.securejarhandler");
82+
if (myModule.isPresent()) {
83+
final var gj9h = myModule.get();
84+
moduleLayer
85+
.findModule("java.base")
86+
.filter(m-> m.isOpen("java.util.jar", gj9h) && m.isExported("sun.security.util", gj9h))
87+
.orElseThrow(()->new IllegalStateException("""
88+
Missing JVM arguments. Please correct your runtime profile and run again.
89+
--add-opens java.base/java.util.jar=cpw.mods.securejarhandler
90+
--add-exports java.base/sun.security.util=cpw.mods.securejarhandler"""));
91+
} else if (Boolean.parseBoolean(System.getProperty("securejarhandler.throwOnMissingModule", "true"))) {
92+
// Hack for JMH benchmark: in JMH, SecureJarHandler does not load as a module, but we add-open to all unnamed in the jvm args
93+
throw new RuntimeException("Failed to find securejarhandler module!");
94+
}
95+
try {
96+
jarVerifier = JarInputStream.class.getDeclaredField("jv");
97+
sigFileSigners = jarVerifier.getType().getDeclaredField("sigFileSigners");
98+
verifiedSigners = jarVerifier.getType().getDeclaredField("verifiedSigners");
99+
parsingMeta = jarVerifier.getType().getDeclaredField("parsingMeta");
100+
anyToVerify = jarVerifier.getType().getDeclaredField("anyToVerify");
101+
jarVerifier.setAccessible(true);
102+
sigFileSigners.setAccessible(true);
103+
verifiedSigners.setAccessible(true);
104+
parsingMeta.setAccessible(true);
105+
anyToVerify.setAccessible(true);
106+
} catch (NoSuchFieldException e) {
107+
throw new IllegalStateException("Missing essential fields", e);
108+
}
109+
}
110+
111+
@Override public Object getJarVerifier(Object inst) {
112+
return getField(jarVerifier, inst);
113+
}
114+
@Override public boolean isParsingMeta(Object inst) { return (Boolean)getField(parsingMeta, inst); }
115+
@Override public boolean hasSignatures(Object inst) { return (Boolean)getField(anyToVerify, inst); }
116+
@SuppressWarnings("unchecked")
117+
@Override public Map<String, CodeSigner[]> getVerifiedSigners(Object inst){ return (Map<String, CodeSigner[]>)getField(verifiedSigners, inst); }
118+
@SuppressWarnings("unchecked")
119+
@Override public Map<String, CodeSigner[]> getPendingSigners(Object inst){ return (Map<String, CodeSigner[]>)getField(verifiedSigners, inst); }
120+
121+
private static Object getField(Field f, Object inst) {
122+
try {
123+
return f.get(inst);
124+
} catch (IllegalAccessException e) {
125+
throw new RuntimeException(e);
126+
}
127+
}
128+
129+
}
130+
131+
private static class UnsafeAccessor implements IAccessor {
132+
private static final Unsafe UNSAFE;
133+
private static final Class<?> JV_TYPE;
134+
static {
135+
try {
136+
var f = Unsafe.class.getDeclaredField("theUnsafe");
137+
f.setAccessible(true);
138+
UNSAFE = (Unsafe)f.get(null);
139+
JV_TYPE = JarInputStream.class.getDeclaredField("jv").getType();
140+
} catch (Exception e) {
141+
throw new RuntimeException("Unable to get Unsafe reference, this should never be possible," +
142+
" be sure to report this will exact details on what JVM you're running.", e);
143+
}
144+
}
145+
146+
private static final long jarVerifier = getOffset(JarInputStream.class, "jv");
147+
private static final long sigFileSigners = getOffset(JV_TYPE, "sigFileSigners");
148+
private static final long verifiedSigners = getOffset(JV_TYPE, "verifiedSigners");
149+
private static final long parsingMeta = getOffset(JV_TYPE, "parsingMeta");
150+
private static final long anyToVerify = getOffset(JV_TYPE, "anyToVerify");
151+
152+
private static long getOffset(Class<?> clz, String name) {
153+
try {
154+
return UNSAFE.objectFieldOffset(clz.getDeclaredField(name));
155+
} catch (Exception e) {
156+
throw new RuntimeException("Unable to get index for " + clz.getName() + "." + name + ", " +
157+
" be sure to report this will exact details on what JVM you're running.", e);
158+
}
159+
}
160+
161+
@Override public Object getJarVerifier(Object inst) { return UNSAFE.getObject(inst, jarVerifier); }
162+
@Override public boolean isParsingMeta(Object inst) { return UNSAFE.getBoolean(inst, parsingMeta); }
163+
@Override public boolean hasSignatures(Object inst) { return UNSAFE.getBoolean(inst, anyToVerify); }
164+
@SuppressWarnings("unchecked")
165+
@Override public Map<String, CodeSigner[]> getVerifiedSigners(Object inst) { return (Map<String, CodeSigner[]>)UNSAFE.getObject(inst, verifiedSigners); }
166+
@SuppressWarnings("unchecked")
167+
@Override public Map<String, CodeSigner[]> getPendingSigners(Object inst) { return (Map<String, CodeSigner[]>)UNSAFE.getObject(inst, sigFileSigners); }
168+
}
88169
}

src/main/java/module-info.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
module cpw.mods.securejarhandler {
66
exports cpw.mods.jarhandling;
7-
exports cpw.mods.jarhandling.impl;
7+
exports cpw.mods.jarhandling.impl; // TODO - Bump version, and remove this export, you don't need our implementation
88
exports cpw.mods.cl;
99
requires jdk.unsupported;
1010
requires org.objectweb.asm;

0 commit comments

Comments
 (0)