4.9 KiB
CFEngine — CWE-407 Disclosure Brief
2026-03-27 · Patch available — awaiting upstream merge
Finding
Three O(n²) defects in CFEngine's policy evaluation built-ins: getindices(), unique(), and maparray(). All three share the same root cause in rlist.c. All patched. Patches ready for upstream review. unique() in particular is a first-class CFEngine policy built-in used on hostname lists of N=10,000+ in fleet management policies.
The Defects
cfengine-0001 (PATCHED — MEDIUM): libpromises/evalfunction.c:3656
// In getindices() built-in evaluation:
for (KeyBinding *k = /*..*/; k != NULL; k = k->next) {
RlistAppendScalarIdemp(&result, k->key); // O(K) per key
}
// RlistAppendScalarIdemp calls RlistKeyIn() — O(K) linked-list walk per append
// Total: O(K²) for K keys
RlistAppendScalarIdemp() calls RlistKeyIn() — an O(K) linked-list walk through the accumulated result — before every append. O(K²) total for K keys in getindices().
cfengine-0002 (PATCHED — HIGH): libpromises/evalfunction.c:5783
// In unique() built-in — used on fleet hostname/filepath lists:
for (const Rlist *rp = list; rp != NULL; rp = rp->next) {
RlistAppendScalarIdemp(&result, RlistScalarValue(rp)); // O(R) per item
}
// RlistAppendScalarIdemp → RlistKeyIn() → O(R) linked-list scan per item
// Total: O(R²) for R-item list
unique() is a first-class CFEngine policy function. Fleet-management policies commonly call it on hostname lists (N=10,000+ in large enterprises). O(N²) cost with N=10,000 means 100,000,000 string comparisons per unique() call.
cfengine-0003 (PATCHED — MEDIUM): libpromises/evalfunction.c:4407
// In maparray() built-in:
RlistAppendScalarIdemp(&mapped, expanded_val); // O(R) per mapped value
Same RlistAppendScalarIdemp() pattern. O(R²) over R mapped array values.
All three share the same root in rlist.c:542: RlistAppendScalarIdemp() uses RlistKeyIn() — an O(N) linked-list walk — as its dedup primitive.
Complexity Proof
All three defects produce O(N²) from the same pattern: O(N) RlistKeyIn() called N times.
cfengine-0002 (unique): For N items:
- Each
RlistAppendScalarIdemp(): O(N)RlistKeyIn()walk - Total: O(N²)
At N=10,000 (fleet hostname list): defective=50,000,000 comparisons, fixed=10,000. 5,000× reduction for typical fleet size.
At N=80 (unit test): 39× op reduction confirmed.
cfengine-0001 (getindices): At K=60: 15× op reduction confirmed.
Impact
CFEngine is one of the oldest and most widely-deployed configuration management systems, particularly in enterprise Linux environments and regulated industries (financial, government, healthcare). It predates Puppet and Ansible and is still running in many large-scale data center deployments.
cfengine-0002 is HIGH severity: unique() is a documented, recommended CFEngine built-in for deduplicating lists of hostnames, file paths, or configuration values. Enterprise fleet policies commonly apply unique() to host group lists with thousands of entries. At N=10,000, the quadratic cost is catastrophic — what should be a millisecond operation takes seconds or minutes.
cfengine-0001 and cfengine-0003 affect getindices() and maparray() — also commonly used in data transformation policies.
The Fix
Replace RlistAppendScalarIdemp() / RlistKeyIn() with a StringSet (hash set) dedup:
// Before (in unique() implementation):
for (const Rlist *rp = list; rp != NULL; rp = rp->next) {
RlistAppendScalarIdemp(&result, RlistScalarValue(rp)); // O(R) via RlistKeyIn
}
// After
// CWE-407 fix: StringSet for O(1) membership instead of O(R) RlistKeyIn scan.
StringSet *seen = StringSetNew();
for (const Rlist *rp = list; rp != NULL; rp = rp->next) {
const char *val = RlistScalarValue(rp);
if (!StringSetContains(seen, val)) {
StringSetAdd(seen, xstrdup(val));
RlistAppendScalar(&result, val);
}
}
StringSetDestroy(seen);
StringSet is already available in CFEngine's own utility library — this is a use of an existing correct data structure, not a new dependency.
Patch
Fix available: defects/cfengine/patch/cfengine-0001-0003-stringset-dedup.patch
Three-location patch in libpromises/evalfunction.c. All three changes use the existing StringSet API.
Unit test: cfengine-0002 39× speedup at N=80. cfengine-0001 15× speedup at K=60.
What We Ask
A patch is ready for review.
- Confirm receipt and assign a GitHub issue reference (cfengine/core) or community forum reference.
- Assess severity — cfengine-0002 (
unique()) is HIGH; at fleet scale (N=10,000+) it is catastrophically slow. - Coordinate a disclosure date — we are targeting 90 days from first contact.
- We will credit the CFEngine team in the public disclosure. Preferred acknowledgment format welcome.
Contact: see cover email. This brief is confidential until coordinated disclosure.