Skip to content

Add handling for possible null return to avoid NullPointerException in AbstractEquipmentCriterionContingencyListDeserializer #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

Merged
merged 6 commits into from
Jun 6, 2025

Conversation

arthurscchan
Copy link
Contributor

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
Contributor 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
Contributor 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
Contributor 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]>
Copy link

sonarqubecloud bot commented Jun 6, 2025

@olperr1 olperr1 changed the title Add handling for possible null return to avoid NullPointerException Add handling for possible null return to avoid NullPointerException in AbstractEquipmentCriterionContingencyListDeserializer Jun 6, 2025
@olperr1 olperr1 merged commit 2b0c6dd into powsybl:main Jun 6, 2025
9 checks passed
@arthurscchan arthurscchan deleted the fix-npe-json-null branch June 10, 2025 10:38
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