Federico Mariani created CAMEL-24164:
----------------------------------------
Summary: camel-jaxb - CamelJaxbPartClass header permanently
mutates the shared data format (state pollution + data race)
Key: CAMEL-24164
URL: https://issues.apache.org/jira/browse/CAMEL-24164
Project: Camel
Issue Type: Bug
Components: camel-jaxb
Reporter: Federico Mariani
Attachments: JaxbPartClassHeaderStatePollutionIssueTest.java
*Severity:* High (correctness + security-adjacent)
*Affects:* 4.x (predates the 2020 reifier split)
h3. Problem
{{JaxbDataFormat}} unmarshal and marshal assign the header-derived part class
into the *instance field*:
{code:java}
partClass =
camelContext.getClassResolver().resolveMandatoryClass(partClassFromHeader, ...);
{code}
A {{DataFormat}} is shared across all exchanges of a route step, so:
* *Sticky*: after any one exchange carries the {{CamelJaxbPartClass}} header,
{{partClass != null}} forever — every subsequent header-less exchange is
silently unmarshalled as a partial {{JAXBElement}} bound to the leaked class.
* *Race*: two concurrent exchanges with different {{CamelJaxbPartClass}}
headers can unmarshal into each other's class.
h3. Security note
{{CamelJaxbPartClass}}/{{CamelJaxbPartNamespace}} are plain message headers. On
a consumer that does not filter {{Camel*}} headers, an untrusted sender can
pick the unmarshal target class and, via the stickiness, poison all later
exchanges (header-abuse family, CVE-2025-27636 lineage). Mitigated by a
compliant default {{HeaderFilterStrategy}}, but the persistence makes any
single leak durable.
(XXE was reviewed and is not affected — all unmarshal paths go through the
hardened {{StaxConverter}}.)
h3. Failure scenario
Route {{from(direct).unmarshal(jaxb)}} with no partClass configured. Exchange 1
arrives with header {{CamelJaxbPartClass=Partial}} -> unmarshals as {{Partial}}
(fine). Exchange 2 (a normal {{<purchaseOrder>}} document, *no header*) ->
unmarshalled as {{Partial}} instead of {{PurchaseOrder}}.
h3. Suggested fix
Resolve the header-derived class into a *local variable* per call ({{Class<?>
effectivePartClass = partClassFromHeader != null ? resolve(...) :
this.partClass;}}); never assign the instance field from exchange data.
h3. Reproducer
Attached {{JaxbPartClassHeaderStatePollutionIssueTest.java}}: single-threaded,
deterministic; exchange 2's body leaks to {{org.apache.camel.example.Partial}}
(expected {{PurchaseOrder}}). RED on current code.
----
_Reported by Claude Code on behalf of Federico Mariani (Croway). A failing
JUnit reproducer (confirmed RED on 4.22.0-SNAPSHOT) is attached._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)