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

javax.security.auth.Subject.SecureSet.readObject() NPE hides other exception

XMLWordPrintable

      ADDITIONAL SYSTEM INFORMATION :
      This should be reproducible everywhere, as it is part of core libraries.

      A DESCRIPTION OF THE PROBLEM :
      Sorry, the subject is character limited. My preferred title:
      javax.security.auth.Subject.SecureSet.readObject() throws a NullPointerException that hides another ClassNotFoundException

      Code ref: https://github.com/openjdk/jdk17/blob/jdk-17+35/src/java.base/share/classes/javax/security/auth/Subject.java#L1495

      I found a very rare corner case in this de-serialization logic. It was triggered by Apache Ignite when making a "thick client" connection to a server. Upon authentication, the server serializes an instance of Subject.SecureSet and sends it to the client. Field LinkedList<Principal> elements contained implementations of interface Principal that did not exist on the client. During the de-serialization process, a ClassNotFoundException was caught and stored. However, during readObject(), a NullPointerException is thrown which hides the root cause ClassNotFoundException.

      In my tests, I was able to reliable reproduce using Java 8 on both server and client. Looking at latest Java 17 source code, I believe the same issue will occur.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Assume we have two independent JVMs that are communicating by sockets to send serialized objects.

      JVM.Server: "class MyPrincipal implements Principal, Serializable" exists. Instances of this class are inserted into a Subject.SecureSet, then serialized and sent to JVM.Client.
      JVM.Client: Does not have "class MyPrincipal". During de-serialization of Subject, then Subject.SecureSet, ClassNotFoundException is caught and stored. When Subject.SecureSet.readObject() is called, LinkedList<E> tmp = (LinkedList<E>) fields.get("elements", null); will be null, due to stored ClassNotFoundException. Next lines will throw NullPointExcpetion. Root cause ClassNotFoundException is hidden.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Ideally, rethrow ClassNotFoundException instead of NullPointerException.

      I am not an expert in Java de-/serialization design, but it appears that the caught ClassNotFoundException *could* be thrown after all readObject() methods are complete. However, if a readObject() throws a RuntimeException, this ClassNotFoundException would be hidden.

      Idea: Upgrade Subject.SecureSet.readObject() to handle gracefully. If ClassNotFoundException was previously caught, re-throw it immediately. Do not continue. I'm not sure if this belongs in ObjectInputStream.readFields(), but these are very old, standard APIs. It might be impossible to change it.
      ACTUAL -
      NullPointerException is thrown, which hides underlying ClassNotFoundException.

      ---------- BEGIN SOURCE ----------
      Sorry, I do not have it. If my steps to reproduce are insufficient, please ask questions. I can add more details.

      Probably you can just serialize and write to a file, then de-serialize in another JVM that is missing a class definition. It should trigger identical behavior.
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      To be clear: The behavior is /somewhat/ correct. The de-serialization fails, but with the wrong exception (NPE instead of ClassNotFoundException). There is no workaround.

      FREQUENCY : always


            weijun Weijun Wang
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: