diff --git a/core/src/main/scala/flatgraph/storage/Deserialization.scala b/core/src/main/scala/flatgraph/storage/Deserialization.scala index c7382a73..b77e5429 100644 --- a/core/src/main/scala/flatgraph/storage/Deserialization.scala +++ b/core/src/main/scala/flatgraph/storage/Deserialization.scala @@ -193,9 +193,9 @@ object Deserialization { } val manifestOffset = header.getLong() - val manifestSize = channel.size() - manifestOffset - if (manifestSize > fileSize) - throw new DeserializationException(s"corrupt file: manifest size ($manifestSize) cannot be larger than the file's size ($fileSize)") + if (manifestOffset < 0 || manifestOffset >= channel.size()) + throw new DeserializationException(s"corrupt file: manifest offset ($manifestOffset) is out of bounds (file size: ${channel.size()})") + val manifestSize = channel.size() - manifestOffset if (manifestSize > Int.MaxValue) throw new DeserializationException(s"corrupt file: unreasonably large manifest size ($manifestSize)... aborting") diff --git a/core/src/test/scala/flatgraph/SerializationTests.scala b/core/src/test/scala/flatgraph/SerializationTests.scala index 263b4dad..5a36f603 100644 --- a/core/src/test/scala/flatgraph/SerializationTests.scala +++ b/core/src/test/scala/flatgraph/SerializationTests.scala @@ -67,7 +67,25 @@ class SerializationTests extends AnyWordSpec with Matchers { // `java.lang.OutOfMemoryError: Requested array size exceeds VM limit` intercept[DeserializationException] { Deserialization.readGraph(storagePath, Option(graph.schema)) - }.getMessage should include("corrupt file: manifest size") + }.getMessage should include("corrupt file: manifest offset") + } + + /* Show that a truncated file (manifest offset beyond EOF) produces a clear error instead of + * `java.lang.IllegalArgumentException: capacity < 0` deep in ByteBuffer.allocate. + */ + "rejects a truncated file where manifest offset points beyond EOF" in { + val schema = TestSchema.make(1, 0) + val graph = Graph(schema) + val diff = DiffGraphBuilder(schema).addNode(new GenericDNode(0)) + DiffGraphApplier.applyDiff(graph, diff) + + val storagePath = Files.createTempFile(s"flatgraph-${getClass.getSimpleName}", "fg") + Serialization.writeGraph(graph, storagePath) + truncateFile(storagePath) + + intercept[DeserializationException] { + Deserialization.readGraph(storagePath, Option(graph.schema)) + }.getMessage should include("corrupt file: manifest offset") } /** manipulate file as detailed in https: //github.com/joernio/flatgraph/security/advisories/GHSA-jqmx-3x2p-69vh */ @@ -89,4 +107,11 @@ class SerializationTests extends AnyWordSpec with Matchers { } } + /** simulate a truncated file: keep only the header so the manifest offset always points past EOF */ + private def truncateFile(path: Path): Unit = { + Using.resource(new RandomAccessFile(path.toFile, "rw")) { file => + file.setLength(storage.HeaderSize) + } + } + }