Race Condition on a Shared XML Processor
ID |
java.race_condition_xml_processor |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
low |
Remediation Effort |
medium |
Resource |
Synchronization |
Language |
Java |
Tags |
ASVS50:v15.4.1, CWE:362, NIST.SP.800-53, PCI-DSS:6.5.6 |
Description
Concurrent use of a shared XML processor that is not thread-safe, without synchronization ('Race Condition').
The rule reports a call made on a field whose type is a non-thread-safe XML processor — javax.xml.xpath.XPath and javax.xml.validation.SchemaFactory — when the call sits outside any synchronized block or method. A local instance, or one guarded by a monitor, is not reported.
Other non-thread-safe shared types have their own rule in the same family: race_condition_format for the JDK formatters (SimpleDateFormat and friends), race_condition_matcher for java.util.regex.Matcher.
Rationale
XPath and SchemaFactory are both expensive to create and both explicitly documented as not thread-safe. That combination is a trap: the natural optimisation is to build one instance and cache it in a field, which is exactly what breaks once two request threads reach it.
Nothing fails loudly. Under concurrency the symptoms are a mangled or empty evaluation result, a schema that validates the wrong document, or an exception thrown from deep inside the implementation — all load-dependent, none reproducible in a single-threaded test.
public class Lookup {
private final XPath xpath = XPathFactory.newInstance().newXPath();
public String read(String expression, Document doc) throws Exception {
return (String) xpath.evaluate(expression, doc); // shared, unsynchronized
}
}
Remediation
Prefer creating the processor where it is used — the cost is real but bounded, and correctness comes first:
public String read(String expression, Document doc) throws Exception {
XPath xpath = XPathFactory.newInstance().newXPath();
return (String) xpath.evaluate(expression, doc);
}
When the creation cost genuinely matters, give each thread its own instance rather than sharing one:
private static final ThreadLocal<XPath> XPATH =
ThreadLocal.withInitial(() -> XPathFactory.newInstance().newXPath());
public String read(String expression, Document doc) throws Exception {
return (String) XPATH.get().evaluate(expression, doc);
}
Compiling the expression once into an XPathExpression does not make it safe to share: XPathExpression is not thread-safe either. Synchronizing every use is correct but serialises the work, so it is the last resort.