undefect. CWE-407 — 92 sites, 42 ecosystems
B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections. Squash of 94 local commits onto remote master.
This commit is contained in:
parent
0a580b313d
commit
db29a08762
1311 changed files with 371202 additions and 1188 deletions
46
defects/cfengine/patch/cfe-0001-getindices-stringset.patch
Normal file
46
defects/cfengine/patch/cfe-0001-getindices-stringset.patch
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
diff --git a/libpromises/evalfunction.c b/libpromises/evalfunction.c
|
||||
index a1b2c3d..e4f5a6b 100644
|
||||
--- a/libpromises/evalfunction.c
|
||||
+++ b/libpromises/evalfunction.c
|
||||
@@ -3649,15 +3649,24 @@ static FnCallResult FnCallGetIndicesClassic(EvalContext *ctx, ARG_UNUSED const P
|
||||
}
|
||||
}
|
||||
|
||||
- Rlist *keys = NULL;
|
||||
+ /* CWE-407 fix: replace Rlist accumulator with StringSet for O(1) dedup.
|
||||
+ * The defect: RlistAppendScalarIdemp() calls RlistKeyIn() — an O(K)
|
||||
+ * linked-list walk — on every insertion, giving O(K²) total cost when
|
||||
+ * the variable table has K matching indices.
|
||||
+ * Fix: collect unique indices into a StringSet (hash table, O(1) insert
|
||||
+ * and membership), then convert to Rlist once at return. O(K) total. */
|
||||
+ StringSet *keys_set = StringSetNew();
|
||||
|
||||
VariableTableIterator *iter = EvalContextVariableTableFromRefIteratorNew(ctx, ref);
|
||||
const Variable *itervar;
|
||||
while ((itervar = VariableTableIteratorNext(iter)) != NULL)
|
||||
{
|
||||
const VarRef *itervar_ref = VariableGetRef(itervar);
|
||||
if (itervar_ref->num_indices > ref->num_indices)
|
||||
{
|
||||
- RlistAppendScalarIdemp(&keys, itervar_ref->indices[ref->num_indices]);
|
||||
+ /* O(1) hash insert; StringSet silently ignores duplicates. */
|
||||
+ StringSetAdd(keys_set, xstrdup(itervar_ref->indices[ref->num_indices]));
|
||||
}
|
||||
}
|
||||
|
||||
VariableTableIteratorDestroy(iter);
|
||||
VarRefDestroy(ref);
|
||||
|
||||
- return (FnCallResult) { FNCALL_SUCCESS, { keys, RVAL_TYPE_LIST } };
|
||||
+ /* Convert StringSet → Rlist for the caller. */
|
||||
+ Rlist *keys = NULL;
|
||||
+ StringSetIterator set_iter = StringSetIteratorInit(keys_set);
|
||||
+ const char *key;
|
||||
+ while ((key = StringSetIteratorNext(&set_iter)) != NULL)
|
||||
+ {
|
||||
+ RlistAppendScalar(&keys, key);
|
||||
+ }
|
||||
+ StringSetDestroy(keys_set);
|
||||
+
|
||||
+ return (FnCallResult) { FNCALL_SUCCESS, { keys, RVAL_TYPE_LIST } };
|
||||
}
|
||||
79
defects/cfengine/patch/cfe-0002-unique-stringset.patch
Normal file
79
defects/cfengine/patch/cfe-0002-unique-stringset.patch
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
diff --git a/libpromises/evalfunction.c b/libpromises/evalfunction.c
|
||||
index a1b2c3d..f7c8d9e 100644
|
||||
--- a/libpromises/evalfunction.c
|
||||
+++ b/libpromises/evalfunction.c
|
||||
@@ -5768,7 +5768,10 @@ static FnCallResult FnCallSetop(EvalContext *ctx,
|
||||
StringSet *set_b = StringSetNew();
|
||||
if (!unique_mode)
|
||||
{
|
||||
JsonIterator iter = JsonIteratorInit(json_b);
|
||||
const JsonElement *e;
|
||||
while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
|
||||
{
|
||||
StringSetAdd(set_b, xstrdup(JsonPrimitiveGetAsString(e)));
|
||||
}
|
||||
}
|
||||
|
||||
+ /* CWE-407 fix for unique() mode: build a StringSet from the input first
|
||||
+ * so membership checks are O(1), then emit one Rlist per unique value.
|
||||
+ * The defect: when unique_mode is true, set_b is always empty, so
|
||||
+ * RlistAppendScalarIdemp() falls back to walking the growing returnlist
|
||||
+ * on every call — O(N) per element, O(N²) total for N input values.
|
||||
+ * Fix: use a dedicated StringSet to track seen values. O(N) total. */
|
||||
+ StringSet *seen = unique_mode ? StringSetNew() : NULL;
|
||||
+
|
||||
Rlist *returnlist = NULL;
|
||||
|
||||
JsonIterator iter = JsonIteratorInit(json);
|
||||
const JsonElement *e;
|
||||
while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
|
||||
{
|
||||
const char *value = JsonPrimitiveGetAsString(e);
|
||||
|
||||
// Yes, this is an XOR. But it's more legible this way.
|
||||
if (!unique_mode && difference_mode && StringSetContains(set_b, value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!unique_mode && !difference_mode && !StringSetContains(set_b, value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
- RlistAppendScalarIdemp(&returnlist, value);
|
||||
+ if (unique_mode)
|
||||
+ {
|
||||
+ /* O(1) hash lookup replaces the O(N) Rlist walk. */
|
||||
+ if (!StringSetContains(seen, value))
|
||||
+ {
|
||||
+ StringSetAdd(seen, xstrdup(value));
|
||||
+ RlistAppendScalar(&returnlist, value);
|
||||
+ }
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ /* intersection / difference: set_b already deduplicates by
|
||||
+ * construction; a given value can appear multiple times in
|
||||
+ * json_a but set_b membership already filters correctly.
|
||||
+ * Use Idemp here to preserve the previous dedup behaviour for
|
||||
+ * the non-unique paths (they are not the hot path). */
|
||||
+ RlistAppendScalarIdemp(&returnlist, value);
|
||||
+ }
|
||||
}
|
||||
|
||||
JsonDestroyMaybe(json, allocated);
|
||||
if (json_b != NULL)
|
||||
{
|
||||
JsonDestroyMaybe(json_b, allocated_b);
|
||||
}
|
||||
|
||||
+ if (seen != NULL)
|
||||
+ {
|
||||
+ StringSetDestroy(seen);
|
||||
+ }
|
||||
+
|
||||
StringSetDestroy(set_b);
|
||||
|
||||
return (FnCallResult) { FNCALL_SUCCESS, (Rval) { returnlist, RVAL_TYPE_LIST } };
|
||||
}
|
||||
62
defects/cfengine/patch/cfe-0003-maparray-nodededup.patch
Normal file
62
defects/cfengine/patch/cfe-0003-maparray-nodededup.patch
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
diff --git a/libpromises/evalfunction.c b/libpromises/evalfunction.c
|
||||
index a1b2c3d..c2e1f7b 100644
|
||||
--- a/libpromises/evalfunction.c
|
||||
+++ b/libpromises/evalfunction.c
|
||||
@@ -4221,6 +4221,12 @@ static FnCallResult FnCallMapData(EvalContext *ctx, ARG_UNUSED const Policy *pol
|
||||
bool mapdatamode = (strcmp(fp->name, "mapdata") == 0);
|
||||
Rlist *returnlist = NULL;
|
||||
|
||||
+ /* CWE-407 fix: track already-appended values in a StringSet so the
|
||||
+ * nested-container branch can dedup in O(1) per element instead of the
|
||||
+ * O(R) linked-list walk performed by RlistAppendScalarIdemp.
|
||||
+ * With N total sub-elements, that was O(N²); with StringSet it is O(N).
|
||||
+ * Initialised here; destroyed at every exit path below. */
|
||||
+ StringSet *seen = StringSetNew();
|
||||
+
|
||||
// This is a delayed evaluation function, so we have to resolve arguments ourselves
|
||||
// We resolve them once now, to get the second or third argument with the iteration data
|
||||
Rlist *expargs = NewExpArgs(ctx, policy, fp, NULL);
|
||||
@@ -4335,6 +4341,7 @@ static FnCallResult FnCallMapData(EvalContext *ctx, ARG_UNUSED const Policy *pol
|
||||
if (strstr(BufferData(expbuf), "$(this.k)") || strstr(BufferData(expbuf), "${this.k}") ||
|
||||
strstr(BufferData(expbuf), "$(this.v)") || strstr(BufferData(expbuf), "${this.v}"))
|
||||
{
|
||||
+ StringSetDestroy(seen);
|
||||
RlistDestroy(returnlist);
|
||||
EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "k");
|
||||
EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "v");
|
||||
@@ -4385,6 +4392,7 @@ static FnCallResult FnCallMapData(EvalContext *ctx, ARG_UNUSED const Policy *pol
|
||||
if (strstr(BufferData(expbuf), "$(this.k)") || strstr(BufferData(expbuf), "${this.k}") ||
|
||||
(havekey && (strstr(BufferData(expbuf), "$(this.k[1])") || strstr(BufferData(expbuf), "${this.k[1]}"))) ||
|
||||
strstr(BufferData(expbuf), "$(this.v)") || strstr(BufferData(expbuf), "${this.v}"))
|
||||
{
|
||||
+ StringSetDestroy(seen);
|
||||
RlistDestroy(returnlist);
|
||||
EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "k");
|
||||
if (havekey)
|
||||
@@ -4404,9 +4413,18 @@ static FnCallResult FnCallMapData(EvalContext *ctx, ARG_UNUSED const Policy *pol
|
||||
if (canonifymode)
|
||||
{
|
||||
BufferCanonify(expbuf);
|
||||
}
|
||||
|
||||
- RlistAppendScalarIdemp(&returnlist, BufferData(expbuf));
|
||||
+ /* CWE-407 fix: O(1) hash membership test replaces O(R) list
|
||||
+ * walk. RlistAppendScalarIdemp called RlistKeyIn() which
|
||||
+ * scanned the full returnlist on every iteration. */
|
||||
+ const char *expanded = BufferData(expbuf);
|
||||
+ if (!StringSetContains(seen, expanded))
|
||||
+ {
|
||||
+ StringSetAdd(seen, xstrdup(expanded));
|
||||
+ RlistAppendScalar(&returnlist, expanded);
|
||||
+ }
|
||||
if (havekey)
|
||||
{
|
||||
EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "k[1]");
|
||||
@@ -4421,6 +4439,8 @@ static FnCallResult FnCallMapData(EvalContext *ctx, ARG_UNUSED const Policy *pol
|
||||
}
|
||||
|
||||
BufferDestroy(expbuf);
|
||||
+ StringSetDestroy(seen);
|
||||
+
|
||||
JsonDestroyMaybe(container, allocated);
|
||||
RlistDestroy(expargs);
|
||||
Loading…
Add table
Add a link
Reference in a new issue