Skip to content

Add handling for possible null return to avoid NullPointerException #3391

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

arthurscchan
Copy link

This is a proposed fix to stability issue discovered by OSS-Fuzz when fuzzing the powsybl-core module. The original OSS-Fuzz issue can be found in https://issues.oss-fuzz.com/u/1/issues/406830033.

@arthurscchan
Copy link
Author

arthurscchan commented Mar 31, 2025

A NullPointerException can occur in the deserializeCommonAttributes method of AbstractEquipmentCriterionContingencyListDeserializer class when deserializing JSON input with a null value for the "type" field.

case "type" -> {
if (!parser.nextTextValue().equals(expectedType)) {
throw new IllegalStateException("type should be: " + expectedType);
}
return true;
}

The method parser.nextTextValue() may return null when the input JSON contains:

{
  "type": null,
  ...
}

In such a case, the call to .equals(expectedType) becomes:

null.equals("HvdcLineCriterionContingencyList")

This results in a NullPointerException because the equals(...) method is invoked on a null reference.

@arthurscchan
Copy link
Author

This is a stability issue due to a lack of validation for null checking from parsing of untrusted JSON. Here is a simple proof of concept to trigger the problem.

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.DefaultDeserializationContext;
import com.powsybl.contingency.contingency.list.HvdcLineCriterionContingencyList;
import com.powsybl.contingency.json.HvdcLineCriterionContingencyListDeserializer;
import java.io.StringReader;

public class ProofOfConcept {
    public static void main(String[] args) throws Exception {
        String json = "{\"type\": null, \"name\": \"test-list\"}";

        JsonFactory factory = new JsonFactory();
        JsonParser parser = factory.createParser(new StringReader(json));
        ObjectMapper mapper = new ObjectMapper();
        DeserializationContext ctx = new DefaultDeserializationContext.Impl(
            mapper.getDeserializationContext().getFactory()
        );

        HvdcLineCriterionContingencyListDeserializer deserializer = new HvdcLineCriterionCon>
        parser.nextToken();
        deserializer.deserialize(parser, ctx);
    }
}

To execute and test the PoC, follow the steps below. It is assumed that OpenJDK 17.0.2 and Maven 3.9.9 is used.

# Prepare OpenJDK 17.0.2
wget https://download.java.net/java/GA/jdk17.0.2/dfd4a8d0985749f896bed50d7138ee7f/8/GPL/openjdk-17.0.2_linux-x64_bin.tar.gz && tar zxvf openjdk-17.0.2_linux-x64_bin.tar.gz && rm openjdk-17.0.2_linux-x64_bin.tar.gz
export JAVA_HOME=./jdk-17.0.2
export PATH=$JAVA_HOME/bin:$PATH

# Prepare Maven 3.9.9
wget https://dlcdn.apache.org/maven/maven-3/3.9.9/binaries/apache-maven-3.9.9-bin.tar.gz && tar zxvf apache-maven-3.9.9-bin.tar.gz && rm apache-maven-3.9.9-bin.tar.gz
export PATH_TO_MVN=./apache-maven-3.9.9/bin/mvn

# Build Powsybl-core
git clone https://github.com/powsybl/powsybl-core
cd powsybl-core
$PATH_TO_MVN clean package -DskipTests

# Group jar files
mkdir jar
for jar in $(find ./ -type f -name "*.jar"); do cp $jar jar/; done

# Build and run PoC
javac -cp "jar/*" ProofOfConcept.java
java -cp "jar/*:./" ProofOfConcept

You will get the following exception stack trace.

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.equals(Object)" because the return value of "com.fasterxml.jackson.core.JsonParser.nextTextValue()" is null
        at com.powsybl.contingency.json.AbstractEquipmentCriterionContingencyListDeserializer.deserializeCommonAttributes(AbstractEquipmentCriterionContingencyListDeserializer.java:72)
        at com.powsybl.contingency.json.HvdcLineCriterionContingencyListDeserializer.lambda$deserialize$0(HvdcLineCriterionContingencyListDeserializer.java:32)
        at com.powsybl.commons.json.JsonUtil.parseObject(JsonUtil.java:517)
        at com.powsybl.commons.json.JsonUtil.parsePolymorphicObject(JsonUtil.java:495)
        at com.powsybl.contingency.json.HvdcLineCriterionContingencyListDeserializer.deserialize(HvdcLineCriterionContingencyListDeserializer.java:32)
        at ProofOfConcept.main(ProofOfConcept.java:23)

@arthurscchan
Copy link
Author

The root cause is down at the AbstractEquipmentCriterionContingencyListDeserializer ::deserializeCommonAttributes method. The fix is simply adding a null checking before calling to the String::equals method to avoid NPE from direct chain invocation.

Signed-off-by: Arthur Chan <[email protected]>
@@ -69,7 +70,9 @@ protected boolean deserializeCommonAttributes(JsonParser parser, Deserialization
return true;
}
case "type" -> {
if (!parser.nextTextValue().equals(expectedType)) {
// parser.nextTextValue() could returns null
String typeStr = Objects.requireNonNull(parser.nextTextValue());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your contribution!

Here, calling Objects.requireNonNull(...) will also throw a NPE if the type is null (but the cause will indeed be different).
It may be better to reverse the test (!expectedType.equals(typeStr)) because expectedType is not an input. In this case, an IllegalStateException with a clear message will be thrown.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the prompt reply. I have fixed that by reversing the equals method. I have also add a null check for safety.

Signed-off-by: Arthur Chan <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants