-
Bug
-
Resolution: Duplicate
-
P4
-
None
-
8, 11, 17, 18, 19
-
generic
-
generic
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
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
- duplicates
-
JDK-8276665 ObjectInputStream.GetField.get(name, object) should throw ClassNotFoundException
-
- Closed
-