This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ws-xmlschema.git
The following commit(s) were added to refs/heads/master by this push:
new 7d42a2d0 Make sure file references are to actual files (#166)
7d42a2d0 is described below
commit 7d42a2d04b58ab2d948c2c5c2cda24c18fc51e33
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Thu Sep 24 10:11:23 2026 +0100
Make sure file references are to actual files (#166)
---
THREAT-MODEL.md | 12 +++++
.../schema/resolver/DefaultURIResolver.java | 54 +++++++++++++++++++++-
.../test/java/tests/DefaultURIResolverTest.java | 41 ++++++++++++++++
3 files changed, 105 insertions(+), 2 deletions(-)
diff --git a/THREAT-MODEL.md b/THREAT-MODEL.md
index a058cb30..7a155f9b 100644
--- a/THREAT-MODEL.md
+++ b/THREAT-MODEL.md
@@ -766,6 +766,18 @@ Revise this document when any of the following lands:
rule as first written: it tested only the URI authority, so
`file:////host/share/x.xsd`, which parses with no authority and
carries the host in its path instead, was not caught.
+- **2026-09-24** — `DefaultURIResolver` now refuses a local location that
+ exists but is not a regular file. A remote fetch is bounded in time and
+ bytes by the properties in §5a; a local read is handed to the parser as a
+ system id and is bounded by nothing, so a named pipe held the parsing
+ thread for as long as nothing wrote to it (confirmed: an indefinite hang,
+ now a refusal). Directories, character devices and sockets go out of
+ reach with it. A location that does not exist is untouched, so a missing
+ schema still reports as it always has and the §9 existence oracle is
+ unchanged. A revision trigger under the first bullet above. Note that
+ this is not a size or time bound on local reads: §9 still records that a
+ single schema document has no size limit, and a large local one can still
+ exhaust the heap.
- **2026-09-24** — `DefaultURIResolver` now refuses, before a remote fetch
and again on each redirect hop, an address in a class that can never
serve a schema document: link-local, multicast, wildcard, IPv6
diff --git
a/xmlschema-core/src/main/java/org/apache/ws/commons/schema/resolver/DefaultURIResolver.java
b/xmlschema-core/src/main/java/org/apache/ws/commons/schema/resolver/DefaultURIResolver.java
index 87260084..4329fc8b 100644
---
a/xmlschema-core/src/main/java/org/apache/ws/commons/schema/resolver/DefaultURIResolver.java
+++
b/xmlschema-core/src/main/java/org/apache/ws/commons/schema/resolver/DefaultURIResolver.java
@@ -547,9 +547,59 @@ public class DefaultURIResolver implements
CollectionURIResolver {
+ "\" would be read from the
filesystem, which "
+ ALLOW_FILE_SYSTEM_PROPERTY + " has
turned off.");
}
- if ("file".equals(scheme) && !isLocalFileUri(archive)) {
+ if ("file".equals(scheme)) {
+ if (!isLocalFileUri(archive)) {
+ throw new XmlSchemaException("The schema location \"" +
schemaLocation
+ + "\" resolves to a file URL with
a non-local"
+ + " authority.");
+ }
+ verifyRegularFile(archive, schemaLocation);
+ }
+ }
+
+ /**
+ * Refuse a local location that exists but is not a regular file. A remote
fetch is bounded in
+ * time and bytes; a local read is handed to the parser as a system id and
is bounded by
+ * nothing, so a named pipe holds the parsing thread for as long as
nothing writes to it. A
+ * schema document is a regular file, so requiring one costs nothing and
also puts directories,
+ * character devices and sockets out of reach.
+ * <p>
+ * A location that does not exist is left alone: that is an ordinary
missing-schema error and
+ * the parser reports it as it always has.
+ * </p>
+ *
+ * @param uri the resolved location, or for a <code>jar:</code> URL the
archive it names.
+ * @param schemaLocation the original schema location, for the error
message.
+ */
+ private static void verifyRegularFile(String uri, String schemaLocation) {
+ final File file = toLocalFile(uri);
+ if (file != null && file.exists() && !file.isFile()) {
throw new XmlSchemaException("The schema location \"" +
schemaLocation
- + "\" resolves to a file URL with a
non-local authority.");
+ + "\" is not a regular file. A schema
document cannot be"
+ + " a directory, a device or a pipe,
and reading one can"
+ + " block the parse for as long as
nothing writes to it.");
+ }
+ }
+
+ /**
+ * The local file a <code>file:</code> URL names, or <code>null</code> if
it cannot be mapped to
+ * one. Null means the check above does not apply rather than that the
location is safe; the
+ * scheme and authority rules have already run.
+ */
+ private static File toLocalFile(String uri) {
+ final URI parsed;
+ try {
+ parsed = new URI(uri.trim());
+ } catch (URISyntaxException e) {
+ return null;
+ }
+ try {
+ return new File(parsed);
+ } catch (IllegalArgumentException e) {
+ // File(URI) refuses an authority, even "localhost", which
isLocalFileUri allows. The
+ // path is the part that names the file.
+ final String path = parsed.getPath();
+ return path == null || path.length() == 0 ? null : new File(path);
}
}
diff --git a/xmlschema-core/src/test/java/tests/DefaultURIResolverTest.java
b/xmlschema-core/src/test/java/tests/DefaultURIResolverTest.java
index c9d8f5a1..ee150987 100644
--- a/xmlschema-core/src/test/java/tests/DefaultURIResolverTest.java
+++ b/xmlschema-core/src/test/java/tests/DefaultURIResolverTest.java
@@ -436,4 +436,45 @@ public class DefaultURIResolverTest extends Assert {
expected.getMessage().contains(expectedMessageFragment));
}
}
+
+ /**
+ * A local read is handed to the parser as a system id and bounded by
nothing, so a named pipe
+ * holds the parsing thread indefinitely. A directory exercises the same
predicate portably --
+ * Java cannot create a FIFO -- and is refused for the same reason.
+ */
+ @Test
+ public void testALocalLocationThatIsNotARegularFileIsRefused() throws
Exception {
+ File directory = existingDirectory();
+ assertTrue(directory.isDirectory());
+ String asFileUrl = directory.toURI().toString();
+
+ assertSchemeRefused(asFileUrl, null, "not a regular file");
+ assertSchemeRefused(asFileUrl, localBase(), "not a regular file");
+ // The archive of a jar: URL must be a regular file too.
+ assertSchemeRefused("jar:" + asFileUrl + "!/x.xsd", null, "not a
regular file");
+ }
+
+ @Test
+ public void testARegularFileStillResolves() throws Exception {
+ File file = File.createTempFile("schema", ".xsd");
+ try {
+ assertEquals(file.toURI().toString(),
+ new DefaultURIResolver()
+ .resolveEntity("urn:x", file.toURI().toString(),
null).getSystemId());
+ } finally {
+ assertTrue(file.delete() || !file.exists());
+ }
+ }
+
+ /**
+ * A location that does not exist is not this check's business: it is an
ordinary missing-schema
+ * error, and refusing it here would change what the parser reports for a
typo.
+ */
+ @Test
+ public void testAMissingLocalFileIsStillResolved() {
+ String missing = new File(existingDirectory(),
"no-such-schema.xsd").toURI().toString();
+
+ assertEquals(missing,
+ new DefaultURIResolver().resolveEntity("urn:x", missing,
null).getSystemId());
+ }
}