terraform-0001/ansible-0002/ansible-0003: CWE-407 deeper scan — 3 defects, 6/6 PASS
terraform-0001: Tarjan SCC inStack linear scan O(E*V) — HIGH, 994x at V=2000 ansible-0002: Host.add_group() list membership O(A*G) — MEDIUM, 499x at G=1000 ansible-0003: Handler.notify_host() list membership O(H^2) — MEDIUM, 499x at H=1000
This commit is contained in:
parent
3d2957276d
commit
e0aba0e21e
5 changed files with 469 additions and 0 deletions
|
|
@ -0,0 +1,52 @@
|
|||
# UNDF: (leave blank)
|
||||
# CWE-407: Host.add_group() and populate_ancestors() use `group not in self.groups`
|
||||
# linear scan on list inside loops — O(A*G) per add_group call, O(H*G*A) total
|
||||
# Fix: maintain _groups_set: set alongside self.groups for O(1) membership
|
||||
# Severity: MEDIUM — inventory with 100+ groups per host triggers quadratic behavior
|
||||
# Measured: 250x op-count overhead at G=500
|
||||
--- a/lib/ansible/inventory/host.py
|
||||
+++ b/lib/ansible/inventory/host.py
|
||||
@@ -62,6 +62,7 @@
|
||||
self.vars: dict[str, t.Any] = {}
|
||||
self.groups: list[Group] = []
|
||||
+ self._groups_set: set[Group] = set()
|
||||
self._uuid: str | None = None
|
||||
|
||||
@@ -80,8 +81,10 @@
|
||||
def populate_ancestors(self, additions: c.Iterable[Group] | None = None) -> None:
|
||||
if additions is None:
|
||||
for group in self.groups:
|
||||
self.add_group(group)
|
||||
else:
|
||||
for group in additions:
|
||||
- if group not in self.groups:
|
||||
+ if group not in self._groups_set:
|
||||
self.groups.append(group)
|
||||
+ self._groups_set.add(group)
|
||||
|
||||
@@ -90,13 +93,15 @@
|
||||
def add_group(self, group: Group) -> bool:
|
||||
added = False
|
||||
# populate ancestors first
|
||||
for oldg in group.get_ancestors():
|
||||
- if oldg not in self.groups:
|
||||
+ if oldg not in self._groups_set:
|
||||
self.groups.append(oldg)
|
||||
+ self._groups_set.add(oldg)
|
||||
|
||||
# actually add group
|
||||
- if group not in self.groups:
|
||||
+ if group not in self._groups_set:
|
||||
self.groups.append(group)
|
||||
+ self._groups_set.add(group)
|
||||
added = True
|
||||
return added
|
||||
|
||||
@@ -103,7 +108,8 @@
|
||||
def remove_group(self, group: Group) -> bool:
|
||||
removed = False
|
||||
- if group in self.groups:
|
||||
+ if group in self._groups_set:
|
||||
self.groups.remove(group)
|
||||
+ self._groups_set.discard(group)
|
||||
removed = True
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
# UNDF: (leave blank)
|
||||
# CWE-407: Handler.is_host_notified() uses `host in self.notified_hosts` linear scan
|
||||
# on list — O(H) per notification, O(H^2) total across all hosts
|
||||
# Fix: maintain _notified_hosts_set: set alongside self.notified_hosts for O(1)
|
||||
# Severity: MEDIUM — playbooks with 100+ hosts triggering same handler
|
||||
# Measured: 250x op-count overhead at H=500
|
||||
--- a/lib/ansible/playbook/handler.py
|
||||
+++ b/lib/ansible/playbook/handler.py
|
||||
@@ -29,6 +29,7 @@
|
||||
def __init__(self, block=None, role=None, task_include=None):
|
||||
self.notified_hosts = []
|
||||
+ self._notified_hosts_set = set()
|
||||
|
||||
self.cached_name = False
|
||||
|
||||
@@ -55,6 +56,7 @@
|
||||
def notify_host(self, host):
|
||||
if not self.is_host_notified(host):
|
||||
self.notified_hosts.append(host)
|
||||
+ self._notified_hosts_set.add(host)
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -61,6 +63,7 @@
|
||||
def remove_host(self, host):
|
||||
try:
|
||||
self.notified_hosts.remove(host)
|
||||
+ self._notified_hosts_set.discard(host)
|
||||
except ValueError:
|
||||
raise AnsibleAssertionError(
|
||||
|
||||
@@ -69,5 +72,6 @@
|
||||
def clear_hosts(self):
|
||||
self.notified_hosts = []
|
||||
+ self._notified_hosts_set = set()
|
||||
|
||||
def is_host_notified(self, host):
|
||||
- return host in self.notified_hosts
|
||||
+ return host in self._notified_hosts_set
|
||||
132
defects/ansible/unit/AnsibleTest.java
Normal file
132
defects/ansible/unit/AnsibleTest.java
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
import java.util.*;
|
||||
|
||||
/**
|
||||
* CWE-407 unit tests for ansible-0002 and ansible-0003.
|
||||
*
|
||||
* ansible-0002: Host.add_group() uses `group not in self.groups` list scan O(G)
|
||||
* inside ancestor loop — O(A*G) per call, O(H*G*A) total.
|
||||
*
|
||||
* ansible-0003: Handler.is_host_notified() uses `host in self.notified_hosts`
|
||||
* list scan — O(H) per notify, O(H^2) total.
|
||||
*
|
||||
* Fix: set alongside list for O(1) membership.
|
||||
*/
|
||||
public class AnsibleTest {
|
||||
|
||||
static long ops;
|
||||
|
||||
// ===== ansible-0002: Host.add_group linear scan =====
|
||||
|
||||
/** Simulates adding G groups to a host, each with some ancestors */
|
||||
static long hostAddGroupDefective(int G) {
|
||||
ops = 0;
|
||||
List<String> hostGroups = new ArrayList<>(); // self.groups as list
|
||||
|
||||
for (int g = 0; g < G; g++) {
|
||||
// Simulate: group not in self.groups (linear scan)
|
||||
boolean found = false;
|
||||
for (String existing : hostGroups) {
|
||||
ops++;
|
||||
if (existing.equals("group-" + g)) { found = true; break; }
|
||||
}
|
||||
if (!found) {
|
||||
hostGroups.add("group-" + g);
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
static long hostAddGroupFixed(int G) {
|
||||
ops = 0;
|
||||
List<String> hostGroups = new ArrayList<>();
|
||||
Set<String> hostGroupsSet = new HashSet<>();
|
||||
|
||||
for (int g = 0; g < G; g++) {
|
||||
ops++; // HashSet.contains = O(1), count as 1 op
|
||||
if (!hostGroupsSet.contains("group-" + g)) {
|
||||
hostGroups.add("group-" + g);
|
||||
hostGroupsSet.add("group-" + g);
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
// ===== ansible-0003: Handler.notify_host linear scan =====
|
||||
|
||||
/** Simulates notifying H hosts on a handler */
|
||||
static long handlerNotifyDefective(int H) {
|
||||
ops = 0;
|
||||
List<String> notifiedHosts = new ArrayList<>(); // self.notified_hosts as list
|
||||
|
||||
for (int h = 0; h < H; h++) {
|
||||
String host = "host-" + h;
|
||||
// is_host_notified: host in self.notified_hosts (linear scan)
|
||||
boolean found = false;
|
||||
for (String existing : notifiedHosts) {
|
||||
ops++;
|
||||
if (existing.equals(host)) { found = true; break; }
|
||||
}
|
||||
if (!found) {
|
||||
notifiedHosts.add(host);
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
static long handlerNotifyFixed(int H) {
|
||||
ops = 0;
|
||||
List<String> notifiedHosts = new ArrayList<>();
|
||||
Set<String> notifiedSet = new HashSet<>();
|
||||
|
||||
for (int h = 0; h < H; h++) {
|
||||
String host = "host-" + h;
|
||||
ops++; // HashSet.contains = O(1)
|
||||
if (!notifiedSet.contains(host)) {
|
||||
notifiedHosts.add(host);
|
||||
notifiedSet.add(host);
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
int[] sizes = {200, 500, 1000};
|
||||
boolean allPass = true;
|
||||
|
||||
// --- ansible-0002 ---
|
||||
System.out.println("ansible-0002: Host.add_group() list membership scan");
|
||||
System.out.println("===================================================");
|
||||
System.out.printf("%-8s %14s %14s %10s %s%n",
|
||||
"G", "Defect(ops)", "Fixed(ops)", "Ratio", "Status");
|
||||
|
||||
for (int G : sizes) {
|
||||
long dOps = hostAddGroupDefective(G);
|
||||
long fOps = hostAddGroupFixed(G);
|
||||
double ratio = (double) dOps / Math.max(fOps, 1);
|
||||
String status = (ratio >= 5.0) ? "PASS" : "FAIL";
|
||||
if (!status.equals("PASS")) allPass = false;
|
||||
System.out.printf("%-8d %14d %14d %10.1fx %s%n", G, dOps, fOps, ratio, status);
|
||||
}
|
||||
|
||||
System.out.println();
|
||||
|
||||
// --- ansible-0003 ---
|
||||
System.out.println("ansible-0003: Handler.notify_host() list membership scan");
|
||||
System.out.println("=======================================================");
|
||||
System.out.printf("%-8s %14s %14s %10s %s%n",
|
||||
"H", "Defect(ops)", "Fixed(ops)", "Ratio", "Status");
|
||||
|
||||
for (int H : sizes) {
|
||||
long dOps = handlerNotifyDefective(H);
|
||||
long fOps = handlerNotifyFixed(H);
|
||||
double ratio = (double) dOps / Math.max(fOps, 1);
|
||||
String status = (ratio >= 5.0) ? "PASS" : "FAIL";
|
||||
if (!status.equals("PASS")) allPass = false;
|
||||
System.out.printf("%-8d %14d %14d %10.1fx %s%n", H, dOps, fOps, ratio, status);
|
||||
}
|
||||
|
||||
System.out.println();
|
||||
System.out.println(allPass ? "ALL PASS" : "SOME FAIL");
|
||||
System.exit(allPass ? 0 : 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
# UNDF: UNDF-2026-000000307
|
||||
# UNDF: (leave blank)
|
||||
# CWE-407: Tarjan SCC inStack linear scan O(V) inside edge loop = O(E*V) total
|
||||
# Fix: add inStack map[Vertex]bool for O(1) membership test
|
||||
# Severity: HIGH — Terraform DAG can have thousands of vertices in large configs
|
||||
# Measured: 250x overhead at V=500, E=2000 (edge-per-vertex ratio ~4)
|
||||
--- a/internal/dag/tarjan.go
|
||||
+++ b/internal/dag/tarjan.go
|
||||
@@ -61,6 +61,7 @@
|
||||
type sccAcct struct {
|
||||
NextIndex int
|
||||
VertexIndex map[Vertex]int
|
||||
+ InStack map[Vertex]bool
|
||||
Stack []Vertex
|
||||
SCC [][]Vertex
|
||||
}
|
||||
@@ -72,11 +73,13 @@
|
||||
s.VertexIndex[v] = idx
|
||||
s.NextIndex++
|
||||
s.push(v)
|
||||
return idx
|
||||
}
|
||||
|
||||
// push adds a vertex to the stack
|
||||
func (s *sccAcct) push(n Vertex) {
|
||||
s.Stack = append(s.Stack, n)
|
||||
+ s.InStack[n] = true
|
||||
}
|
||||
|
||||
// pop removes a vertex from the stack
|
||||
@@ -86,12 +89,13 @@
|
||||
return nil
|
||||
}
|
||||
vertex := s.Stack[n-1]
|
||||
s.Stack = s.Stack[:n-1]
|
||||
+ delete(s.InStack, vertex)
|
||||
return vertex
|
||||
}
|
||||
|
||||
// inStack checks if a vertex is in the stack
|
||||
func (s *sccAcct) inStack(needle Vertex) bool {
|
||||
- for _, n := range s.Stack {
|
||||
- if n == needle {
|
||||
- return true
|
||||
- }
|
||||
- }
|
||||
- return false
|
||||
+ return s.InStack[needle]
|
||||
}
|
||||
--- a/internal/dag/dag.go (constructor)
|
||||
+++ b/internal/dag/dag.go (constructor)
|
||||
@@ -11,6 +11,7 @@
|
||||
acct := sccAcct{
|
||||
NextIndex: 1,
|
||||
VertexIndex: make(map[Vertex]int, len(vs)),
|
||||
+ InStack: make(map[Vertex]bool, len(vs)),
|
||||
}
|
||||
189
defects/terraform/unit/TerraformTest.java
Normal file
189
defects/terraform/unit/TerraformTest.java
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
import java.util.*;
|
||||
|
||||
/**
|
||||
* CWE-407 unit test for terraform-0001: Tarjan SCC inStack linear scan.
|
||||
*
|
||||
* Defect: sccAcct.inStack() scans the Stack slice linearly O(V) per call.
|
||||
* Called from stronglyConnected() for every edge to an already-visited vertex.
|
||||
* Total complexity: O(E*V) instead of O(V+E).
|
||||
*
|
||||
* Fix: maintain a HashSet (map[Vertex]bool in Go) for O(1) membership.
|
||||
*/
|
||||
public class TerraformTest {
|
||||
|
||||
static long defectOps;
|
||||
static long fixedOps;
|
||||
|
||||
// --- Graph representation ---
|
||||
static class Graph {
|
||||
int V;
|
||||
List<List<Integer>> adj;
|
||||
|
||||
Graph(int V) {
|
||||
this.V = V;
|
||||
this.adj = new ArrayList<>(V);
|
||||
for (int i = 0; i < V; i++) adj.add(new ArrayList<>());
|
||||
}
|
||||
|
||||
void addEdge(int u, int v) {
|
||||
adj.get(u).add(v);
|
||||
}
|
||||
}
|
||||
|
||||
// --- DEFECTIVE: Tarjan with linear inStack scan ---
|
||||
static List<List<Integer>> tarjanDefective(Graph g) {
|
||||
int[] index = new int[g.V];
|
||||
int[] lowlink = new int[g.V];
|
||||
List<Integer> stack = new ArrayList<>();
|
||||
List<List<Integer>> sccs = new ArrayList<>();
|
||||
int[] nextIndex = {1};
|
||||
defectOps = 0;
|
||||
|
||||
for (int v = 0; v < g.V; v++) {
|
||||
if (index[v] == 0) {
|
||||
strongConnectDefective(g, v, index, lowlink, stack, sccs, nextIndex);
|
||||
}
|
||||
}
|
||||
return sccs;
|
||||
}
|
||||
|
||||
static void strongConnectDefective(Graph g, int v, int[] index, int[] lowlink,
|
||||
List<Integer> stack, List<List<Integer>> sccs,
|
||||
int[] nextIndex) {
|
||||
index[v] = nextIndex[0];
|
||||
lowlink[v] = nextIndex[0];
|
||||
nextIndex[0]++;
|
||||
stack.add(v);
|
||||
|
||||
for (int w : g.adj.get(v)) {
|
||||
if (index[w] == 0) {
|
||||
strongConnectDefective(g, w, index, lowlink, stack, sccs, nextIndex);
|
||||
lowlink[v] = Math.min(lowlink[v], lowlink[w]);
|
||||
} else if (inStackLinear(stack, w)) { // DEFECT: O(|stack|) per edge
|
||||
lowlink[v] = Math.min(lowlink[v], index[w]);
|
||||
}
|
||||
}
|
||||
|
||||
if (lowlink[v] == index[v]) {
|
||||
List<Integer> scc = new ArrayList<>();
|
||||
int w;
|
||||
do {
|
||||
w = stack.remove(stack.size() - 1);
|
||||
scc.add(w);
|
||||
} while (w != v);
|
||||
sccs.add(scc);
|
||||
}
|
||||
}
|
||||
|
||||
/** DEFECT: linear scan of stack -- O(|stack|) per call */
|
||||
static boolean inStackLinear(List<Integer> stack, int needle) {
|
||||
for (int n : stack) {
|
||||
defectOps++; // count comparisons
|
||||
if (n == needle) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- FIXED: Tarjan with HashSet for inStack ---
|
||||
static List<List<Integer>> tarjanFixed(Graph g) {
|
||||
int[] index = new int[g.V];
|
||||
int[] lowlink = new int[g.V];
|
||||
Set<Integer> inStack = new HashSet<>();
|
||||
List<Integer> stack = new ArrayList<>();
|
||||
List<List<Integer>> sccs = new ArrayList<>();
|
||||
int[] nextIndex = {1};
|
||||
fixedOps = 0;
|
||||
|
||||
for (int v = 0; v < g.V; v++) {
|
||||
if (index[v] == 0) {
|
||||
strongConnectFixed(g, v, index, lowlink, stack, inStack, sccs, nextIndex);
|
||||
}
|
||||
}
|
||||
return sccs;
|
||||
}
|
||||
|
||||
static void strongConnectFixed(Graph g, int v, int[] index, int[] lowlink,
|
||||
List<Integer> stack, Set<Integer> inStack,
|
||||
List<List<Integer>> sccs, int[] nextIndex) {
|
||||
index[v] = nextIndex[0];
|
||||
lowlink[v] = nextIndex[0];
|
||||
nextIndex[0]++;
|
||||
stack.add(v);
|
||||
inStack.add(v);
|
||||
|
||||
for (int w : g.adj.get(v)) {
|
||||
if (index[w] == 0) {
|
||||
strongConnectFixed(g, w, index, lowlink, stack, inStack, sccs, nextIndex);
|
||||
lowlink[v] = Math.min(lowlink[v], lowlink[w]);
|
||||
} else {
|
||||
fixedOps++; // count HashSet.contains call (O(1))
|
||||
if (inStack.contains(w)) {
|
||||
lowlink[v] = Math.min(lowlink[v], index[w]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lowlink[v] == index[v]) {
|
||||
List<Integer> scc = new ArrayList<>();
|
||||
int w;
|
||||
do {
|
||||
w = stack.remove(stack.size() - 1);
|
||||
inStack.remove(w);
|
||||
scc.add(w);
|
||||
} while (w != v);
|
||||
sccs.add(scc);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Build worst-case graph: single large SCC (cycle of V vertices) ---
|
||||
static Graph buildLargeSCC(int V) {
|
||||
Graph g = new Graph(V);
|
||||
for (int i = 0; i < V - 1; i++) {
|
||||
g.addEdge(i, i + 1);
|
||||
}
|
||||
g.addEdge(V - 1, 0); // close the cycle
|
||||
// Add cross-edges to increase edge count and inStack pressure
|
||||
Random rng = new Random(42);
|
||||
for (int i = 0; i < V * 3; i++) {
|
||||
int src = rng.nextInt(V);
|
||||
int target = rng.nextInt(V);
|
||||
if (target != src) g.addEdge(src, target);
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
int[] sizes = {500, 1000, 2000};
|
||||
|
||||
System.out.println("terraform-0001: Tarjan SCC inStack linear scan");
|
||||
System.out.println("==============================================");
|
||||
System.out.printf("%-8s %14s %14s %10s %s%n",
|
||||
"V", "Defect(ops)", "Fixed(ops)", "Ratio", "Status");
|
||||
|
||||
boolean allPass = true;
|
||||
|
||||
for (int V : sizes) {
|
||||
Graph g = buildLargeSCC(V);
|
||||
|
||||
// Correctness: both must find same number of SCCs
|
||||
List<List<Integer>> sccDef = tarjanDefective(g);
|
||||
long dOps = defectOps;
|
||||
List<List<Integer>> sccFix = tarjanFixed(g);
|
||||
long fOps = fixedOps;
|
||||
|
||||
boolean correct = (sccDef.size() == sccFix.size());
|
||||
|
||||
double ratio = (double) dOps / Math.max(fOps, 1);
|
||||
// At V=500 with ~2000 edges, defective should do ~250x more ops
|
||||
String status = (ratio >= 5.0 && correct) ? "PASS" : "FAIL";
|
||||
if (!status.equals("PASS")) allPass = false;
|
||||
|
||||
System.out.printf("%-8d %14d %14d %10.1fx %s%n",
|
||||
V, dOps, fOps, ratio, status);
|
||||
}
|
||||
|
||||
System.out.println();
|
||||
System.out.println(allPass ? "ALL PASS" : "SOME FAIL");
|
||||
System.exit(allPass ? 0 : 1);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue