Piotr Karwasz created CXF-9248:
----------------------------------
Summary: Delegate JAXP parser configuration to Apache Commons
Secure XML
Key: CXF-9248
URL: https://issues.apache.org/jira/browse/CXF-9248
Project: CXF
Issue Type: Improvement
Reporter: Piotr Karwasz
CXF configures JAXP factories at roughly 58 places in {{src/main/java}}. Each
one re-implements the same hardening by hand: {{FEATURE_SECURE_PROCESSING}},
then {{ACCESS_EXTERNAL_DTD}} / {{ACCESS_EXTERNAL_SCHEMA}} /
{{ACCESS_EXTERNAL_STYLESHEET}}, each in its own try/catch that logs a warning
and carries on.
Against the JDK's own implementations that hardening is tight, and this issue
is not a claim otherwise. The problem is the {{catch}} block.
{{ACCESS_EXTERNAL_\*}} are JAXP 1.5 *properties*. An implementation that does
not recognise one throws an exception; CXF logs and continues with an
unhardened factory. The posture therefore depends on which JAXP implementation
wins on the classpath — a decision the integrator makes, not CXF. Applications
built on CXF are large, and a transitively pulled Xerces, Xalan or Saxon is an
ordinary occurrence rather than a hypothetical. Every one of those ~58 sites
fails open in that case, silently.
There is a second gap that is not about foreign implementations at all: a
registered {{LSResourceResolver}} or {{URIResolver}} takes precedence over
{{ACCESS_EXTERNAL_*}} by design. Where CXF must resolve something and so
installs a resolver, the properties it also set no longer decide anything. This
is documented JAXP behavior, and it means the two mechanisms cannot be combined
the way the current code assumes.
Apache Commons Secure XML installs its floor as a *resolver* instead — a
mechanism every implementation is required to support — with inverted
semantics: returning {{null}} denies, returning non-{{null}} allows that one
reference. The floor cannot be removed by a caller-supplied resolver, and
unresolved references are answered with empty content rather than fetched. The
same posture then holds on whatever implementation the deployment actually has.
We have driven the migration through on a branch and fixed the sharp edges
upstream rather than working around them in CXF; the 1.0.1 changelog records
them:
[https://github.com/apache/commons-secure-xml/blob/main/src/changes/changes.xml]
The one worth naming here, because CXF depends on it:
{{SAXTransformerFactory.newXMLFilter(Templates)}} in Commons Secure XML accepts
foreign {{Templates}}, which most TrAX implementations reject.
{{XSLTJaxbProvider}} needs exactly that.
The branch is 12 commits, 78 files, +2,457/-445, split as the two parts below.
The additional lines are mostly due to OASIS Catalog files and don't increase
the complexity of CXF:
https://github.com/ppkarwasz/cxf/tree/feat/use-commons-secure-xml
h2. Part 1 — Block external XML access by default, replacing the manual
hardening
Replace the per-site hardening with factories from Commons Secure XML, so the
floor holds regardless of the JAXP implementation on the classpath and cannot
be dismantled by a caller-supplied resolver.
* {{SecureXxxFactory.newInstance()}} at the call site. No CXF wrapper class —
adding one would put us back in the business of configuring parsers.
* CXF's own tightenings compose on top and are kept: {{disallow-doctype-decl}}
in {{DOMUtils}}, and in {{StaxUtils}} {{SUPPORT_DTD=false}},
{{IS_SUPPORTING_EXTERNAL_ENTITIES=false}}, the throwing {{XMLResolver}} and the
Woodstox depth/element limits. The library reserves its settings against
*loosening* only, so tightening is sanctioned rather than tolerated.
* {{SAFE_INPUT_FACTORY}} and the {{createWoodstoxFactory()}} fallback chain in
{{StaxUtils}} are untouched; the thread-safe-implementation fast path still
works.
h2. Part 2 — Unblock the paths that legitimately need an external resource
Denying everything is only half the work: CXF has roughly ten places where
resolving an external schema, stylesheet or import is the feature. These are
opted back in *per reference*, through one resolver that already knows CXF's
policy.
{{org.apache.cxf.catalog.XmlCatalogResolver}} — one class implementing all four
JAXP resolver interfaces ({{EntityResolver}}, {{LSResourceResolver}},
{{javax.xml.transform.URIResolver}}, {{XMLResolver}}), answering from the bus
{{OASISCatalogManager}} first and a local resource second. {{Remote.DENIED}} is
the default; {{Remote.ALLOWED}} is the explicit opt-in for developer-trusted
input (tools, codegen), which makes every remote-capable site greppable.
The resolver is load-bearing, not decoration: {{SecureSchemaFactory}} alone
fails where {{SecureSchemaFactory}} + {{XmlCatalogResolver}} succeeds.
h3. 2a — URIResolver: coarse-grained policy and archive schemes
{{org.apache.cxf.resource.URIResolver}} is where scheme policy belongs, and it
now owns it. Two parts, one done and one proposed:
*Done.* {{schemeOf(String)}} unwraps the archive schemes — {{jar:}},
{{wsjar:}}, {{zip:}} — down to the scheme that actually decides, because those
are wrappers, not protocols. Only the innermost one counts. The previous check
looked at the outermost, so {{jar:gopher://...}} passed as "jar"; it no longer
does.
*Proposed, not implemented.* {{getAllowedSchemes()}} can currently only widen
the allowlist: it seeds from {{DEFAULT_ALLOWED_URL_SCHEMES}} and only
{{add()}}s the configured entries, so no configuration can take a scheme away.
A coarse-grained selector — {{ALL}} / {{LOCAL}} / {{CLASSPATH_ONLY}} — would
give integrators one comprehensible knob instead of a scheme list that cannot
restrict anything.
h3. 2b — Declare the bundled schemas in catalogs
Most of what CXF resolves it already ships. The branch adds 23
{{META-INF/jax-ws-catalog.xml}} files ({{core}} plus 22 modules) declaring each
module's bundled schemas by namespace and by system id, so
{{OASISCatalogManager}} answers an {{xs:import}} from the JAR instead of its
canonical {{http://}} location. Catalogs chain, which matches how the modules
depend on each other — {{wsrm.xsd}} importing {{addressing.xsd}} from a
different JAR resolves through the other module's catalog.
Three prerequisites fell out of this and are on the branch:
* {{xml-resolver}} is no longer {{<optional>true</optional>}}. While optional
it was absent at runtime in most modules, which made {{OASISCatalogManager}}
silently inert and {{setCatalogLocation}} a no-op. Worth knowing independently
of this issue.
* {{OASISCatalogManager}} references {{Catalog}} and {{CatalogResolver}}
directly now that the dependency is guaranteed, instead of reflectively behind
{{catch (Throwable)}}.
* {{loadCatalogs(ClassLoader, String)}} is used rather than {{loadCatalog}}, so
all same-named catalogs on the classpath are picked up instead of whichever one
happens to be first.
*Optional follow-up.* {{xml-resolver}} 1.2 is a 2006 artifact, still supported
by the Xerces PMC. Swapping it for xmlresolver.org would be a separate,
independently reviewable change; nothing above depends on it.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)