salt-0005: UNDF-2026-000001270 (RE2 correct fix) ansible-0005: UNDF-2026-000001271 (RE2 correct fix) capistrano-0002: UNDF-2026-000001272 (RE2 correct fix) puppet-0002: UNDF-2026-000001273 (RE2 correct fix) puppet-0001 patch header: corrected UNDF ref from placeholder 226 to 1269 Total: 1272 entries
71 lines
2.9 KiB
Diff
71 lines
2.9 KiB
Diff
# UNDF: UNDF-2026-000001269
|
|
From: agent-blackops <blackops@unturf.com>
|
|
Date: Thu, 26 Mar 2026 00:00:00 +0000
|
|
Subject: [PATCH] graph/simple_graph: replace Array path with Set+Array pair in paths_in_cycle BFS
|
|
|
|
CWE-407: Algorithmic complexity via O(|cycle|^3) Array#member? in the
|
|
BFS loop of paths_in_cycle(). frame[1] is a growing Array used as both
|
|
the path record and the membership oracle. Each call to
|
|
frame[1].member?(frame[0]) is O(path_length); paths grow as BFS
|
|
expands; in the worst case (a fully connected cycle of length N) this
|
|
produces O(N^3) total comparisons.
|
|
|
|
Fix: replace the bare Array path with a two-field frame [vertex,
|
|
path_array, path_set] where path_set is a Ruby Set parallel to
|
|
path_array. Membership testing uses path_set.include?() for O(1)
|
|
average cost. path_array is retained unchanged so that found paths
|
|
preserve their ordering and the existing `found.sort` contract is
|
|
unchanged.
|
|
|
|
Ruby's Set (from 'set') gives O(1) average include? via hash-based
|
|
storage. No behaviour change; only the membership-test complexity is
|
|
affected.
|
|
|
|
Defect-Id: PUP-001
|
|
Severity: LOW (error path — cycles are uncommon in valid Puppet catalogs)
|
|
CWE: CWE-407 (Inefficient Algorithmic Complexity)
|
|
---
|
|
lib/puppet/graph/simple_graph.rb | 16 +++++++++-------
|
|
1 file changed, 9 insertions(+), 7 deletions(-)
|
|
|
|
diff --git a/lib/puppet/graph/simple_graph.rb b/lib/puppet/graph/simple_graph.rb
|
|
index xxxxxxx..yyyyyyy 100644
|
|
--- a/lib/puppet/graph/simple_graph.rb
|
|
+++ b/lib/puppet/graph/simple_graph.rb
|
|
@@ -1,3 +1,4 @@
|
|
+require 'set' # CWE-407 fix
|
|
|
|
@@ -199,17 +200,17 @@ class Puppet::Graph::SimpleGraph
|
|
def paths_in_cycle(cycle, max_paths = 1)
|
|
# TRANSLATORS "negative or zero" refers to the count of paths
|
|
raise ArgumentError, _("negative or zero max_paths") if max_paths < 1
|
|
|
|
# Calculate our filtered outbound vertex lists...
|
|
adj = {}
|
|
cycle.each do |vertex|
|
|
adj[vertex] = adjacent(vertex).select { |s| cycle.member? s }
|
|
end
|
|
|
|
found = []
|
|
|
|
- # frame struct is vertex, [path]
|
|
- stack = [[cycle.first, []]]
|
|
+ # frame struct is vertex, [path_array], path_set # CWE-407 fix
|
|
+ stack = [[cycle.first, [], Set.new]] # CWE-407 fix
|
|
while frame = stack.shift # rubocop:disable Lint/AssignmentInCondition
|
|
- if frame[1].member?(frame[0]) then
|
|
+ if frame[2].include?(frame[0]) then # CWE-407 fix: O(1) vs O(path)
|
|
found << frame[1] + [frame[0]]
|
|
break if found.length >= max_paths
|
|
else
|
|
adj[frame[0]].each do |to|
|
|
- stack.push [to, frame[1] + [frame[0]]]
|
|
+ new_path = frame[1] + [frame[0]]
|
|
+ new_set = frame[2] | Set[frame[0]] # CWE-407 fix: O(1) insert
|
|
+ stack.push [to, new_path, new_set] # CWE-407 fix
|
|
end
|
|
end
|
|
end
|
|
|
|
found.sort
|
|
end
|