4.2 KiB
SaltStack — CWE-407 Disclosure Brief
2026-03-27 · Patch available — awaiting upstream merge
Finding
One O(n²) defect in SaltStack's cloud map dependency cycle detection. Patched. Patch ready for upstream review. The defect is in salt/cloud/__init__.py — the cycle detection function used when deploying multi-machine cloud maps with dependency ordering.
The Defect
saltstack-0001 (PATCHED — MEDIUM): salt/cloud/__init__.py:1830
# In _has_loop() — cloud machine dependency cycle detection:
def _has_loop(node, nodes, seen=None):
if seen is None:
seen = [] # plain list
if node in seen: # O(V) — list scan
return True
seen = list(seen) # O(depth) copy at every recursion level
seen.append(node)
return any(_has_loop(dep, nodes, seen) for dep in nodes.get(node, []))
Three compounding problems:
node in seenis O(V) list scan per recursion levelseen = list(seen)copies the entire list at every recursion level — O(depth²) copy overhead- The combination produces O(V²) total for V nodes in the dependency graph
Complexity Proof
For V machines in a cloud map with dependency depth D:
- Per recursive call: O(V) list scan + O(depth) list copy
- Total calls: O(V × branching)
- Total: O(V²) due to list scan + copy overhead compounding
At depth=80: defective includes 80 × 79 / 2 + 80 × 79 / 2 = 6,320 list copy operations on top of scan overhead. Fixed: O(V) total. 39× op reduction at depth=80.
The fix replaces the list with a set (seen = set()). A set is passed by reference rather than copied at each level (mutable, unlike the list-copy pattern), eliminating both the O(V) scan and the O(depth) copy overhead per level.
Impact
SaltStack (Salt) is a Python-based infrastructure automation and configuration management platform — used for remote execution, configuration management, and cloud provisioning. It is widely deployed in enterprise data centers and cloud environments.
_has_loop() is called during salt-cloud --map deployments when machines have requires: dependencies — the standard way to sequence cloud VM creation in Salt cloud maps. Cloud maps that provision multiple VMs in dependency order (common for multi-tier applications: database → app server → load balancer) hit this path on every deployment.
Large cloud maps with many VMs and complex dependency graphs — common in infrastructure-as-code for large applications — hit worst case. Each terraform apply-equivalent in Salt (salt-cloud --map) triggers this cycle detection.
The Fix
Replace seen = [] / list(seen) copy pattern with seen = set() passed by reference:
# Before
def _has_loop(node, nodes, seen=None):
if seen is None:
seen = []
if node in seen: # O(V) list scan
return True
seen = list(seen) # O(depth) copy
seen.append(node)
# After
# CWE-407 fix: set for O(1) membership; pass by reference to avoid O(depth²) copies.
def _has_loop(node, nodes, seen=None):
if seen is None:
seen = set()
if node in seen: # O(1) set lookup
return True
seen = seen | {node} # O(1) set add (new set per branch, not copy-on-every-level)
return any(_has_loop(dep, nodes, seen) for dep in nodes.get(node, []))
Alternatively, use a mutable set with add/discard for the backtracking variant:
seen.add(node)
result = any(_has_loop(dep, nodes, seen) for dep in nodes.get(node, []))
seen.discard(node)
return result
Patch
Fix available: defects/saltstack/patch/saltstack-0001-has-loop-set.patch
Single-function change in salt/cloud/__init__.py.
Unit test: 39× speedup at depth=80.
What We Ask
A patch is ready for review.
- Confirm receipt and assign a GitHub issue reference (saltstack/salt).
- Assess severity — saltstack-0001 fires on every
salt-cloud --mapdeployment with machine dependencies. - Coordinate a disclosure date — we are targeting 90 days from first contact.
- We will credit the SaltStack team in the public disclosure. Preferred acknowledgment format welcome.
Contact: see cover email. This brief is confidential until coordinated disclosure.