SparseMatrixDeserializationTest.java

/**
 * Copyright (c) 2025, RTE (http://www.rte-france.com)
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * SPDX-License-Identifier: MPL-2.0
 */
package com.powsybl.math.matrix;

import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;

/**
 * @author Olivier Perrin {@literal <olivier.perrin at rte-france.com>}
 */
class SparseMatrixDeserializationTest {
    private static FileSystem fileSystem;
    protected static Path testDir;

    @BeforeAll
    static void setUp() throws Exception {
        fileSystem = Jimfs.newFileSystem(Configuration.unix());
        testDir = fileSystem.getPath("/tmp");
        Files.createDirectories(testDir);
    }

    @AfterAll
    static void tearDown() throws Exception {
        fileSystem.close();
    }

    @Test
    void testSecureDeserialization() throws IOException {
        // Prepare exploit payload
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
            oos.writeObject(new Exploit());
        }

        // Try to deserialize the false SparseMatrix object
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        assertThrows(UncheckedIOException.class, () -> SparseMatrix.read(bais), "Exploit may be present.");

        // Confirm there is no exploit: the "rce" file should not exist
        Path rceFile = testDir.resolve("rce");
        assertFalse(Files.exists(rceFile), "The exploit is present.");
    }

    static class Exploit implements Serializable {
        @Serial
        private static final long serialVersionUID = 1L;

        @Serial
        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
            // Emulate a security problem: when reading the object, create a new file.
            // If this file is indeed created when deserializing the payload, then an attacker
            // may perform dangerous operations (download and install a malware, connect to an external server, ...)
            in.defaultReadObject();
            Path rceFile = testDir.resolve("rce");
            Files.writeString(rceFile, "Security problem", StandardCharsets.UTF_8);
        }
    }
}