From a7aae3cec5768715ec32e6b70d8f2b7b665327a4 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Mon, 7 Sep 2026 12:12:17 +0100 Subject: [PATCH] Close the XsbReader when resolveHandle meets an unhandled file type SchemaTypeSystemImpl.resolveHandle opens an XsbReader, then dispatches on the file type it finds. Each finishLoadingXxx() closes the reader in a finally, but the default arm throws IllegalStateException without closing, leaking the underlying .xsb stream. The arm is reachable: FILETYPE_SCHEMAPOINTER is a file type the compiler writes but this switch does not handle, as is any type read from a corrupt or crafted .xsb. Co-Authored-By: Claude Opus 5 (1M context) --- .../impl/schema/SchemaTypeSystemImpl.java | 3 + .../schema/ResolveHandleFileTypeTest.java | 84 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/test/java/org/apache/xmlbeans/impl/schema/ResolveHandleFileTypeTest.java diff --git a/src/main/java/org/apache/xmlbeans/impl/schema/SchemaTypeSystemImpl.java b/src/main/java/org/apache/xmlbeans/impl/schema/SchemaTypeSystemImpl.java index 510d1d940..db20d41bb 100644 --- a/src/main/java/org/apache/xmlbeans/impl/schema/SchemaTypeSystemImpl.java +++ b/src/main/java/org/apache/xmlbeans/impl/schema/SchemaTypeSystemImpl.java @@ -979,6 +979,9 @@ public SchemaComponent resolveHandle(String handle) { result = reader.finishLoadingIdentityConstraint(); break; default: + // every finishLoadingXxx() above closes the reader in a finally; + // this arm has to release it before unwinding + reader.readEnd(); throw new IllegalStateException("Illegal handle type"); } diff --git a/src/test/java/org/apache/xmlbeans/impl/schema/ResolveHandleFileTypeTest.java b/src/test/java/org/apache/xmlbeans/impl/schema/ResolveHandleFileTypeTest.java new file mode 100644 index 000000000..4e37af63f --- /dev/null +++ b/src/test/java/org/apache/xmlbeans/impl/schema/ResolveHandleFileTypeTest.java @@ -0,0 +1,84 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.xmlbeans.impl.schema; + +import org.apache.xmlbeans.ResourceLoader; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ResolveHandleFileTypeTest { + + private static class TrackingInputStream extends ByteArrayInputStream { + private boolean closed; + + TrackingInputStream(byte[] bytes) { + super(bytes); + } + + @Override + public void close() throws IOException { + closed = true; + super.close(); + } + } + + // A complete, readable .xsb header for a file type that resolveHandle does not + // know how to load, followed by an empty string pool. + private static byte[] pointerFile() throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeInt(SchemaTypeSystemImpl.DATA_BABE); + dos.writeShort(SchemaTypeSystemImpl.MAJOR_VERSION); + dos.writeShort(SchemaTypeSystemImpl.MINOR_VERSION); + dos.writeShort(0); // release number + dos.writeShort(SchemaTypeSystemImpl.FILETYPE_SCHEMAPOINTER); + dos.writeShort(1); // string pool holding no entries + dos.flush(); + return bos.toByteArray(); + } + + @Test + void closesReaderOnUnhandledFileType() throws Exception { + TrackingInputStream stream = new TrackingInputStream(pointerFile()); + + SchemaTypeSystemImpl typeSystem = new SchemaTypeSystemImpl("test"); + Field loaderF = SchemaTypeSystemImpl.class.getDeclaredField("_resourceLoader"); + loaderF.setAccessible(true); + loaderF.set(typeSystem, new ResourceLoader() { + @Override + public InputStream getResourceAsStream(String resourceName) { + return stream; + } + + @Override + public void close() { + } + }); + + assertThrows(IllegalStateException.class, () -> typeSystem.resolveHandle("h")); + assertTrue(stream.closed, "the reader should have been closed before unwinding"); + } +}