java-topology/defects/wildfly/patch/wildfly-0003-transactionrollbacksetupaction-depth-remove.patch
russell@unturf.com 2eea7d128c
4 follow-up patches shipped: wildfly-0002 + wildfly-0003 + log4j2-0001 + nakama-0001
Acting on the 4 borderline candidates flagged in the session-summary intel.
All 4 surfaced after the unmoad scanner enhancements cleared M3/M4 noise.

UNDF-1306 wildfly-0002 (HIGH) - ElytronSecurityDomainContextImpl.isValid()
  sets currentIdentity ThreadLocal with no paired cleanup contract. Subject
  populated at line 69 is the canonical handover; the ThreadLocal stash leaks
  to next request on the pool thread. Fix: drop the .set(identity) line.

UNDF-1307 wildfly-0003 (LOW) - TransactionRollbackSetupAction.depth.set(null)
  should be depth.remove() to fully delete the ThreadLocal entry; current
  pattern leaves null binding pinning the WildFly classloader during
  undeploy/redeploy. Functional clear, classloader-retention only.

UNDF-1308 log4j2-0001 (HIGH) - Log4jMDCAdapter.clear() only clears the
  log4j ThreadContext map, NOT the SLF4J pushByKey/popByKey stacks
  (mapOfStacks ThreadLocal). SLF4J spec mandates clear() means "clear
  all MDC". Per-key Deques accumulate across requests. Fix: add clear()
  to ThreadLocalMapOfStacks (calls tlMapOfStacks.remove()) and call from
  the public clear().

UNDF-1309 nakama-0001 (HIGH MOAD-0004) - social/social.go logs OAuth
  access tokens, ID tokens, oauth2.Token objects (incl. refresh tokens),
  Steam publisherKey + ticket at debug level via zap.String/zap.Any.
  11 call sites. Fix: replace value-logging with shape-logging (token_len,
  has_token bool) — preserves debug value, redacts secret bytes.

First MOAD-0004 patch this session. Companion to the 3 MOAD-0003 patches
(wildfly-0001/0002/0003) extending the inverse-pipeline pattern across
projects: scanner enhancement -> noise reduction -> human triage finds
defects that were buried.

Total session flagships: 9 (was 6) — 5 CWE-407 + 3 MOAD-0003 + 1 MOAD-0004.
2026-04-26 12:30:28 -04:00

45 lines
2.2 KiB
Diff

# UNDF: UNDF-2026-000001307
# CWE-668 / MOAD-0003: A Leaked Context (minor) — TransactionRollbackSetupAction
# uses depth.set(null) instead of depth.remove() when the depth counter
# hits zero. Functional clear (depth.get() returns null on next read),
# but the ThreadLocal entry remains in the thread's threadLocals map,
# pinning the (now-null) holder reference until the thread dies.
#
# Defect: transactions/src/main/java/org/jboss/as/txn/deployment/
# TransactionRollbackSetupAction.java:102
#
# holder.depth += increment;
# if (holder.depth == 0) {
# depth.set(null); // <-- should be depth.remove()
# return holder.actuallyCleanUp;
# }
#
# Rationale: ThreadLocal.set(null) writes a null value into the entry but
# leaves the entry itself (with the ThreadLocal key reference) alive in
# the Thread's internal threadLocals map. Across a Java EE thread pool's
# lifetime, this accumulates one entry per ThreadLocal-holding class.
# ThreadLocal.remove() actually deletes the entry, allowing the
# threadLocals map to shrink and (importantly) decoupling the deployed
# WildFly classloader from the thread's reachability graph during
# undeploy/redeploy cycles.
#
# This is a minor MOAD-0003 instance — no value-leak (the next caller
# does `depth.get() == null` and re-initializes correctly). The defect
# is classloader retention during deployment churn, not security.
#
# No bench: lifecycle defect, not algorithmic.
--- a/transactions/src/main/java/org/jboss/as/txn/deployment/TransactionRollbackSetupAction.java
+++ b/transactions/src/main/java/org/jboss/as/txn/deployment/TransactionRollbackSetupAction.java
@@ -99,7 +99,9 @@ public class TransactionRollbackSetupAction implements SetupAction, Service {
holder.depth += increment;
if (holder.depth == 0) {
- depth.set(null);
+ // Use remove() rather than set(null) so the ThreadLocal entry
+ // is actually deleted from the thread's threadLocals map. set(null)
+ // leaves a null binding that pins the WildFly classloader during
+ // undeploy/redeploy cycles in app servers with persistent thread pools.
+ depth.remove();
return holder.actuallyCleanUp;
}
return false;