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());
+    }
 }

Reply via email to