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.
This commit is contained in:
parent
d149f2aaf0
commit
2eea7d128c
11 changed files with 771 additions and 0 deletions
|
|
@ -0,0 +1,55 @@
|
|||
# UNDF: UNDF-2026-000001306
|
||||
# CWE-668 / MOAD-0003: A Leaked Context — ElytronSecurityDomainContextImpl.isValid()
|
||||
# sets ThreadLocal currentIdentity with no
|
||||
# paired cleanup contract
|
||||
#
|
||||
# Defect: webservices/server-integration/src/main/java/org/jboss/as/webservices/security/
|
||||
# ElytronSecurityDomainContextImpl.java:68
|
||||
#
|
||||
# SecurityIdentity identity = authenticate(username, (String) password);
|
||||
# if (identity == null) {
|
||||
# return false;
|
||||
# }
|
||||
# this.currentIdentity.set(identity); // <-- here
|
||||
# SubjectUtil.fromSecurityIdentity(identity, subject);
|
||||
# return true;
|
||||
#
|
||||
# The class has three currentIdentity-setter call sites:
|
||||
# - line 68: isValid() -> NO contractual paired cleanup
|
||||
# - line 80: runAs() -> properly clears via try/finally
|
||||
# - line 96: pushSubjectContext() -> paired with cleanupSubjectContext()
|
||||
#
|
||||
# isValid() exists to validate credentials and populate the caller's Subject
|
||||
# (line 69). Once it returns true the caller already has the SecurityIdentity
|
||||
# inside the populated Subject — no need to also stash a copy in the
|
||||
# per-thread currentIdentity. JBossWS/CXF callers using isValid() purely for
|
||||
# credential validation (without proceeding to runAs() or
|
||||
# pushSubjectContext()/cleanupSubjectContext()) leak the previous request's
|
||||
# SecurityIdentity into the next Work item on the same pool thread.
|
||||
#
|
||||
# Fix: drop the currentIdentity.set(identity) line in isValid(). Callers that
|
||||
# actually need the per-thread identity install should use pushSubjectContext()
|
||||
# (paired with cleanupSubjectContext()) or runAs() (paired with try/finally
|
||||
# currentIdentity.remove() inside the same method). The Subject populated at
|
||||
# line 69 remains the canonical handover for credential-validation callers.
|
||||
#
|
||||
# Companion to wildfly-0001 (UNDF-1305): same MOAD-0003 family, different
|
||||
# entry point, surfaced after the same scanner improvement (unmoad commit
|
||||
# 1f48798) cleared 99.2% of the M3 noise that had buried both findings.
|
||||
--- a/webservices/server-integration/src/main/java/org/jboss/as/webservices/security/ElytronSecurityDomainContextImpl.java
|
||||
+++ b/webservices/server-integration/src/main/java/org/jboss/as/webservices/security/ElytronSecurityDomainContextImpl.java
|
||||
@@ -65,7 +65,11 @@ public class ElytronSecurityDomainContextImpl implements SecurityDomainContext {
|
||||
if (identity == null) {
|
||||
return false;
|
||||
}
|
||||
- this.currentIdentity.set(identity);
|
||||
+ // Do NOT stash identity into the per-thread currentIdentity here.
|
||||
+ // isValid() exists to validate credentials and populate the caller's
|
||||
+ // Subject (next line). Stashing into the ThreadLocal without a paired
|
||||
+ // cleanup contract leaks this identity into the next Work item that
|
||||
+ // runs on the same pool thread. Callers that need per-thread identity
|
||||
+ // install should use pushSubjectContext() / cleanupSubjectContext()
|
||||
+ // (paired) or runAs() (auto-cleared in finally).
|
||||
SubjectUtil.fromSecurityIdentity(identity, subject);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# 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;
|
||||
Loading…
Add table
Add a link
Reference in a new issue