Skip to content

Commit 8ed16ce

Browse files
committed
Check object type at SparseMatrix deserialization (GHSA-f5cx-h789-j959)
Signed-off-by: Olivier Perrin <[email protected]>
1 parent d8398f6 commit 8ed16ce

File tree

2 files changed

+89
-4
lines changed

2 files changed

+89
-4
lines changed

math/src/main/java/com/powsybl/math/matrix/SparseMatrix.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
import com.powsybl.commons.exceptions.UncheckedClassNotFoundException;
1111
import com.powsybl.commons.util.trove.TDoubleArrayListHack;
1212
import com.powsybl.commons.util.trove.TIntArrayListHack;
13+
import gnu.trove.list.array.TDoubleArrayList;
14+
import gnu.trove.list.array.TIntArrayList;
1315

1416
import java.io.*;
1517
import java.nio.file.Files;
1618
import java.nio.file.Path;
17-
import java.util.Arrays;
18-
import java.util.List;
19-
import java.util.Objects;
19+
import java.util.*;
2020

2121
/**
2222
* Sparse matrix implementation in <a href="https://en.wikipedia.org/wiki/Sparse_matrix#Compressed_sparse_column_(CSC_or_CCS)">CSC</a></a> format.
@@ -26,9 +26,14 @@
2626
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
2727
*/
2828
public class SparseMatrix extends AbstractMatrix implements Serializable {
29-
3029
private static final long serialVersionUID = -7810324161942335828L;
3130

31+
// Classes allowed during deserialization
32+
private static final Set<Class<?>> ALLOWED_CLASSES = Set.of(SparseMatrix.class,
33+
int[].class, double[].class,
34+
TIntArrayListHack.class, TDoubleArrayListHack.class,
35+
TIntArrayList.class, TDoubleArrayList.class);
36+
3237
/**
3338
* Sparse Element implementation.
3439
* An element in a sparse matrix is defined by its index in the values vector.
@@ -487,6 +492,11 @@ public void write(OutputStream outputStream) {
487492
public static SparseMatrix read(InputStream inputStream) {
488493
Objects.requireNonNull(inputStream);
489494
try (ObjectInputStream objectInputStream = new ObjectInputStream(inputStream)) {
495+
// Check that the object to deserialize is really a SparseMatrix.
496+
// This check is done prior to its complete deserialization to prevent security problems (RCE).
497+
// - Check that all non-null encountered classes are among the accepted ones (the one composing a SparseMatrix).
498+
ObjectInputFilter allowedClassesFilter = ObjectInputFilter.allowFilter(ALLOWED_CLASSES::contains, ObjectInputFilter.Status.REJECTED);
499+
objectInputStream.setObjectInputFilter(allowedClassesFilter);
490500
return (SparseMatrix) objectInputStream.readObject();
491501
} catch (IOException e) {
492502
throw new UncheckedIOException(e);
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* Copyright (c) 2025, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
* SPDX-License-Identifier: MPL-2.0
7+
*/
8+
package com.powsybl.math.matrix;
9+
10+
import com.google.common.jimfs.Configuration;
11+
import com.google.common.jimfs.Jimfs;
12+
import org.junit.jupiter.api.AfterAll;
13+
import org.junit.jupiter.api.BeforeAll;
14+
import org.junit.jupiter.api.Test;
15+
16+
import java.io.*;
17+
import java.nio.charset.StandardCharsets;
18+
import java.nio.file.FileSystem;
19+
import java.nio.file.Files;
20+
import java.nio.file.Path;
21+
22+
import static org.junit.jupiter.api.Assertions.assertFalse;
23+
import static org.junit.jupiter.api.Assertions.assertThrows;
24+
25+
/**
26+
* @author Olivier Perrin {@literal <olivier.perrin at rte-france.com>}
27+
*/
28+
class SparseMatrixDeserializationTest {
29+
private static FileSystem fileSystem;
30+
protected static Path testDir;
31+
32+
@BeforeAll
33+
static void setUp() throws Exception {
34+
fileSystem = Jimfs.newFileSystem(Configuration.unix());
35+
testDir = fileSystem.getPath("/tmp");
36+
Files.createDirectories(testDir);
37+
}
38+
39+
@AfterAll
40+
static void tearDown() throws Exception {
41+
fileSystem.close();
42+
}
43+
44+
@Test
45+
void testSecureDeserialization() throws IOException {
46+
// Prepare exploit payload
47+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
48+
try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
49+
oos.writeObject(new Exploit());
50+
}
51+
52+
// Try to deserialize the false SparseMatrix object
53+
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
54+
assertThrows(UncheckedIOException.class, () -> SparseMatrix.read(bais), "Exploit may be present.");
55+
56+
// Confirm there is no exploit: the "rce" file should not exist
57+
Path rceFile = testDir.resolve("rce");
58+
assertFalse(Files.exists(rceFile), "The exploit is present.");
59+
}
60+
61+
static class Exploit implements Serializable {
62+
@Serial
63+
private static final long serialVersionUID = 1L;
64+
65+
@Serial
66+
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
67+
// Emulate a security problem: when reading the object, create a new file.
68+
// If this file is indeed created when deserializing the payload, then an attacker
69+
// may perform dangerous operations (download and install a malware, connect to an external server, ...)
70+
in.defaultReadObject();
71+
Path rceFile = testDir.resolve("rce");
72+
Files.writeString(rceFile, "Security problem", StandardCharsets.UTF_8);
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)