# 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` ```c // 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` ```c // 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` ```c // 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: ```c // 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. 1. Confirm receipt and assign a GitHub issue reference (cfengine/core) or community forum reference. 2. Assess severity — cfengine-0002 (`unique()`) is HIGH; at fleet scale (N=10,000+) it is catastrophically slow. 3. Coordinate a disclosure date — we are targeting 90 days from first contact. 4. 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.