-
Bug
-
Resolution: Fixed
-
P2
-
7u321, 8u311
-
b03
-
Verified
Issue | Fix Version | Assignee | Priority | Status | Resolution | Resolved In Build |
---|---|---|---|---|---|---|
JDK-8280759 | 8u321 | Robert Mckenna | P2 | Closed | Fixed | b33 |
JDK-8280042 | 7u341 | Robert Mckenna | P2 | Closed | Fixed | b03 |
The Oracle JDK 1.8.0_311 has a regression when deserializing the serial
form of a HashMap, when run with a security manager. The fix for bugNo.
8266097 introduced the regression by performing a setAcessible(true)
without being encapsulated in a doPrivilege. This results in the caller
of ObjectInputStream.readObject being required to hold
java.lang.reflect.ReflectPermission "suppressAccessChecks", which is a
bug.
The issue is only applicable to Oracle JDK. Other distributions based on
the source code in OpenJDK 8u-dev do NOT suffer this issue.
The issue is only applicable to the 8u release train, since 8u311. More
modern JDK releases, like say, JDK 11u and 17u, do NOT suffer this
issue.
The issue is caused by the following code in HashMap (as can be seen in
src.zip):
private static final Field f = getLoadFactorField();
private static final long LF_OFFSET = unsafe.objectFieldOffset(f);
static Field getLoadFactorField() {
try {
Field f = HashMap.class.getDeclaredField("loadFactor");
f.setAccessible(true); // <<< HERE
return f;
} catch (NoSuchFieldException e) {
return null;
}
}
Whereas the OpenJDK version of this code is as follows:
LF_OFFSET = unsafe.objectFieldOffset(HashMap.class.getDeclaredField("loadFactor"));
The f.setAccessible(true) is simply unnecessary, and is the root cause
of the issue.
FYI - More modern JDKs all use Unsafe, the two arg version of
objectFieldOffset that accepts a class and string field name.
The remainer of the information here details how to reproduce the issue.
Simple test case that serializes and deserializes a HashMap:
---
import java.io.*;
import java.util.*;
public class Test {
public static void main(String... args) throws Exception {
Map<String,String> map = new HashMap<>();
map.put("foo", "bar");
byte[] serialBytes = serialize(map);
Map<String,String> map2 = deserialize(serialBytes);
System.out.println(map2);
}
@SuppressWarnings("unchecked")
static byte[] serialize(Object object) throws Exception {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(object);
return baos.toByteArray();
}
}
static <T> T deserialize(byte[] bytes) throws Exception {
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
return (T) ois.readObject();
}
}
}
---
First run with an OpenJDK distribution:
$ /Library/Java/JavaVirtualMachines/temurin-8.jdk/Contents/Home/bin/java -version
openjdk version "1.8.0_312"
OpenJDK Runtime Environment (Temurin)(build 1.8.0_312-b07)
OpenJDK 64-Bit Server VM (Temurin)(build 25.312-b07, mixed mode)
$ /Library/Java/JavaVirtualMachines/temurin-8.jdk/Contents/Home/bin/javac Test.java
$ /Library/Java/JavaVirtualMachines/temurin-8.jdk/Contents/Home/bin/java Test
{foo=bar}
$ /Library/Java/JavaVirtualMachines/temurin-8.jdk/Contents/Home/bin/java -Djava.security.manager Test
{foo=bar}
Then run with the Oracle JDK distribution:
$ /Library/Java/JavaVirtualMachines/jdk1.8.0_311.jdk/Contents/Home/bin/java -version
java version "1.8.0_311"
Java(TM) SE Runtime Environment (build 1.8.0_311-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.311-b11, mixed mode)
$ /Library/Java/JavaVirtualMachines/jdk1.8.0_311.jdk/Contents/Home/bin/java Test
{foo=bar}
$ /Library/Java/JavaVirtualMachines/jdk1.8.0_311.jdk/Contents/Home/bin/java -Djava.security.manager Test
Exception in thread "main" java.lang.ExceptionInInitializerError
at java.util.HashMap.readObject(HashMap.java:1386)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1185)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2319)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2210)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1690)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:508)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:466)
at Test.deserialize(Test.java:26)
at Test.main(Test.java:11)
Caused by: java.security.AccessControlException: access denied ("java.lang.reflect.ReflectPermission" "suppressAccessChecks")
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472)
at java.security.AccessController.checkPermission(AccessController.java:886)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
at java.lang.reflect.AccessibleObject.setAccessible(AccessibleObject.java:128)
at java.util.HashMap$UnsafeHolder.getLoadFactorField(HashMap.java:1438)
at java.util.HashMap$UnsafeHolder.<clinit>(HashMap.java:1430)
... 13 more
form of a HashMap, when run with a security manager. The fix for bugNo.
8266097 introduced the regression by performing a setAcessible(true)
without being encapsulated in a doPrivilege. This results in the caller
of ObjectInputStream.readObject being required to hold
java.lang.reflect.ReflectPermission "suppressAccessChecks", which is a
bug.
The issue is only applicable to Oracle JDK. Other distributions based on
the source code in OpenJDK 8u-dev do NOT suffer this issue.
The issue is only applicable to the 8u release train, since 8u311. More
modern JDK releases, like say, JDK 11u and 17u, do NOT suffer this
issue.
The issue is caused by the following code in HashMap (as can be seen in
src.zip):
private static final Field f = getLoadFactorField();
private static final long LF_OFFSET = unsafe.objectFieldOffset(f);
static Field getLoadFactorField() {
try {
Field f = HashMap.class.getDeclaredField("loadFactor");
f.setAccessible(true); // <<< HERE
return f;
} catch (NoSuchFieldException e) {
return null;
}
}
Whereas the OpenJDK version of this code is as follows:
LF_OFFSET = unsafe.objectFieldOffset(HashMap.class.getDeclaredField("loadFactor"));
The f.setAccessible(true) is simply unnecessary, and is the root cause
of the issue.
FYI - More modern JDKs all use Unsafe, the two arg version of
objectFieldOffset that accepts a class and string field name.
The remainer of the information here details how to reproduce the issue.
Simple test case that serializes and deserializes a HashMap:
---
import java.io.*;
import java.util.*;
public class Test {
public static void main(String... args) throws Exception {
Map<String,String> map = new HashMap<>();
map.put("foo", "bar");
byte[] serialBytes = serialize(map);
Map<String,String> map2 = deserialize(serialBytes);
System.out.println(map2);
}
@SuppressWarnings("unchecked")
static byte[] serialize(Object object) throws Exception {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(object);
return baos.toByteArray();
}
}
static <T> T deserialize(byte[] bytes) throws Exception {
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
return (T) ois.readObject();
}
}
}
---
First run with an OpenJDK distribution:
$ /Library/Java/JavaVirtualMachines/temurin-8.jdk/Contents/Home/bin/java -version
openjdk version "1.8.0_312"
OpenJDK Runtime Environment (Temurin)(build 1.8.0_312-b07)
OpenJDK 64-Bit Server VM (Temurin)(build 25.312-b07, mixed mode)
$ /Library/Java/JavaVirtualMachines/temurin-8.jdk/Contents/Home/bin/javac Test.java
$ /Library/Java/JavaVirtualMachines/temurin-8.jdk/Contents/Home/bin/java Test
{foo=bar}
$ /Library/Java/JavaVirtualMachines/temurin-8.jdk/Contents/Home/bin/java -Djava.security.manager Test
{foo=bar}
Then run with the Oracle JDK distribution:
$ /Library/Java/JavaVirtualMachines/jdk1.8.0_311.jdk/Contents/Home/bin/java -version
java version "1.8.0_311"
Java(TM) SE Runtime Environment (build 1.8.0_311-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.311-b11, mixed mode)
$ /Library/Java/JavaVirtualMachines/jdk1.8.0_311.jdk/Contents/Home/bin/java Test
{foo=bar}
$ /Library/Java/JavaVirtualMachines/jdk1.8.0_311.jdk/Contents/Home/bin/java -Djava.security.manager Test
Exception in thread "main" java.lang.ExceptionInInitializerError
at java.util.HashMap.readObject(HashMap.java:1386)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1185)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2319)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2210)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1690)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:508)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:466)
at Test.deserialize(Test.java:26)
at Test.main(Test.java:11)
Caused by: java.security.AccessControlException: access denied ("java.lang.reflect.ReflectPermission" "suppressAccessChecks")
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472)
at java.security.AccessController.checkPermission(AccessController.java:886)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
at java.lang.reflect.AccessibleObject.setAccessible(AccessibleObject.java:128)
at java.util.HashMap$UnsafeHolder.getLoadFactorField(HashMap.java:1438)
at java.util.HashMap$UnsafeHolder.<clinit>(HashMap.java:1430)
... 13 more
- backported by
-
JDK-8280042 Deserializing HashMap throws access denied suppressAccessChecks
- Closed
-
JDK-8280759 Deserializing HashMap throws access denied suppressAccessChecks
- Closed