java-topology/defects/ansible/patch/ans-0002-role-collections-set.patch

63 lines
3.9 KiB
Diff

# UNDF: UNDF-2026-000000006
From: agent-blackops <blackops@unturf.com>
Date: Thu, 26 Mar 2026 00:00:00 +0000
Subject: [PATCH] playbook/role: maintain parallel _collections_set for O(1) membership in _load_role_data()
CWE-407: Algorithmic complexity via O(C) list membership tests in
_load_role_data(). self.collections is a list; the generator expression
`c not in self.collections` performs an O(C) linear scan for each
candidate collection, and the two subsequent `not in self.collections`
guards for 'ansible.builtin' and 'ansible.legacy' add two more O(C)
scans — O(C) total per call where C = current collections length.
Fix: maintain a parallel _collections_set (Python set) as a shadow of
self.collections. All membership tests become O(1). The list is
retained unchanged so that ordering semantics (insert(0, ...), append)
are preserved; _collections_set is kept in sync at every mutation site.
Defect-Id: ANS-002
Severity: LOW
CWE: CWE-407 (Inefficient Algorithmic Complexity)
---
lib/ansible/playbook/role/__init__.py | 20 ++++++++++++--------
1 file changed, 12 insertions(+), 8 deletions(-)
diff --git a/lib/ansible/playbook/role/__init__.py b/lib/ansible/playbook/role/__init__.py
index xxxxxxx..yyyyyyy 100644
--- a/lib/ansible/playbook/role/__init__.py
+++ b/lib/ansible/playbook/role/__init__.py
@@ -268,7 +268,9 @@ class Role(Base, Become, Conditional, Taggable, Delegatable):
# reset collections list; roles do not inherit collections from parents, just use the defaults
# FUTURE: use a private config default for this so we can allow it to be overridden later
self.collections = []
+ self._collections_set = set() # CWE-407 fix: shadow set for O(1) membership
@@ -274,7 +276,8 @@ class Role(Base, Become, Conditional, Taggable, Delegatable):
if self._role_collection: # this is a collection-hosted role
self.collections.insert(0, self._role_collection)
+ self._collections_set.add(self._role_collection) # CWE-407 fix: keep in sync
else: # this is a legacy role, but set the default collection if there is one
default_collection = AnsibleCollectionConfig.default_collection
if default_collection:
self.collections.insert(0, default_collection)
+ self._collections_set.add(default_collection) # CWE-407 fix: keep in sync
# legacy role, ensure all plugin dirs under the role are added to plugin search path
add_all_plugin_dirs(self._role_path)
@@ -285,14 +289,14 @@ class Role(Base, Become, Conditional, Taggable, Delegatable):
# collections can be specified in metadata for legacy or collection-hosted roles
if self._metadata.collections:
- self.collections.extend((c for c in self._metadata.collections if c not in self.collections))
+ for c in self._metadata.collections: # CWE-407 fix
+ if c not in self._collections_set: # CWE-407 fix: O(1) vs O(C)
+ self.collections.append(c)
+ self._collections_set.add(c) # CWE-407 fix: keep in sync
# if any collections were specified, ensure that core or legacy synthetic collections are always included
if self.collections:
# default append collection is core for collection-hosted roles, legacy for others
default_append_collection = 'ansible.builtin' if self._role_collection else 'ansible.legacy'
- if 'ansible.builtin' not in self.collections and 'ansible.legacy' not in self.collections:
+ if 'ansible.builtin' not in self._collections_set and 'ansible.legacy' not in self._collections_set: # CWE-407 fix: O(1)
self.collections.append(default_append_collection)
+ self._collections_set.add(default_append_collection) # CWE-407 fix: keep in sync