Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8279618

Deserializing HashMap throws access denied suppressAccessChecks

XMLWordPrintable

    • b03
    • Verified

        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

              robm Robert Mckenna
              chegar Chris Hegarty
              Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

                Created:
                Updated:
                Resolved: