578/240 — rustc-0004/vertx-0001/asterisk-0003/bitcoin-0001/actix-web-0003/hazelcast-0001+0002/rabbitmq-0005/nginx-0003/haproxy-0003/envoy-0003/istio-0003

This commit is contained in:
russell@unturf.com 2026-03-27 21:25:37 -04:00
parent dde5ec97fb
commit e4ee168b1e
50 changed files with 4775 additions and 5 deletions

View file

@ -0,0 +1,117 @@
# asterisk-0003: CDR Variable Merge O(B×V) Quadratic Membership Scan
## Classification
- **CWE**: CWE-407 (Inefficient Algorithmic Complexity)
- **Severity**: MEDIUM
- **Component**: Call Detail Records (CDR)
- **Location**: `main/cdr.c`, function `cdr_object_create_public_records()`, ~line 1458
## Description
When publishing CDR records at call teardown, Asterisk merges party_b channel
variables into the CDR varshead. For each party_b variable, it performs a full
linear scan of `varshead` (already containing party_a variables) to check for
duplicates before inserting.
This is an O(B × V) operation where:
- B = number of party_b variables
- V = number of variables already in varshead (party_a variables)
In a dialplan with many channel variables (common in IVR-heavy or CRM-integrated
deployments), every call teardown pays this quadratic cost.
## Defective Code
```c
/* main/cdr.c ~line 1458 — cdr_object_create_public_records() */
AST_LIST_TRAVERSE(&it_cdr->party_b.variables, it_var, entries) {
int found = 0;
struct ast_var_t *newvariable;
AST_LIST_TRAVERSE(&cdr_copy->varshead, it_copy_var, entries) { /* O(V) per it_var */
if (!strcasecmp(ast_var_name(it_var), ast_var_name(it_copy_var))) {
found = 1;
break;
}
}
if (!found && (newvariable = ast_var_assign(ast_var_name(it_var), ast_var_value(it_var)))) {
AST_LIST_INSERT_TAIL(&cdr_copy->varshead, newvariable, entries);
}
}
```
## Fix
Build a `case-insensitive hash set` of already-present variable names before the
merge loop, then do O(1) membership checks:
```c
/* Build a hash set of existing variable names (lowercased) */
struct ao2_container *existing_names = ao2_container_alloc_hash(
AO2_ALLOC_OPT_LOCK_NOLOCK, 0, 31, str_hash_fn, NULL, str_cmp_fn);
AST_LIST_TRAVERSE(&cdr_copy->varshead, it_copy_var, entries) {
char *lower = ast_strdupa(ast_var_name(it_copy_var));
ast_str_to_lower(lower);
ao2_link(existing_names, lower);
}
AST_LIST_TRAVERSE(&it_cdr->party_b.variables, it_var, entries) {
char *lower = ast_strdupa(ast_var_name(it_var));
ast_str_to_lower(lower);
if (!ao2_find(existing_names, lower, OBJ_SEARCH_KEY | OBJ_NOLOCK)) {
struct ast_var_t *newvariable = ast_var_assign(
ast_var_name(it_var), ast_var_value(it_var));
if (newvariable) {
AST_LIST_INSERT_TAIL(&cdr_copy->varshead, newvariable, entries);
ao2_link(existing_names, lower);
}
}
}
ao2_ref(existing_names, -1);
```
Alternatively, since CDR variable counts are moderate (typically < 100), a
simpler approach uses `ast_hashtab` which is already available in Asterisk:
```c
struct ast_hashtab *seen = ast_hashtab_create(31, ast_hashtab_compare_strings_nocase,
ast_hashtab_resize_java, ast_hashtab_newsize_java,
ast_hashtab_hash_string_nocase, 0);
AST_LIST_TRAVERSE(&cdr_copy->varshead, it_copy_var, entries) {
ast_hashtab_insert_safe(seen, (void *)ast_var_name(it_copy_var));
}
AST_LIST_TRAVERSE(&it_cdr->party_b.variables, it_var, entries) {
if (!ast_hashtab_lookup(seen, ast_var_name(it_var))) {
struct ast_var_t *newvariable = ast_var_assign(
ast_var_name(it_var), ast_var_value(it_var));
if (newvariable) {
AST_LIST_INSERT_TAIL(&cdr_copy->varshead, newvariable, entries);
ast_hashtab_insert_safe(seen, ast_var_name(it_var));
}
}
}
ast_hashtab_destroy(seen, NULL);
```
## Complexity
| Metric | Before | After |
|--------|--------|-------|
| Variable merge | O(B × V) | O(B + V) |
| Per-lookup | O(V) linear scan | O(1) hash lookup |
## Speedup Estimate
At V=50 party_a vars and B=50 party_b vars:
- Before: 50 × 50 = 2,500 strcmp operations
- After: 50 + 50 = 100 hash operations
- Ratio: ~25x
At V=200, B=200 (large IVR/CRM deployment):
- Before: 200 × 200 = 40,000 operations
- After: 400 operations
- Ratio: ~100x
The defect scales quadratically with the number of channel variables, which
grows with dialplan complexity and integration depth.

View file

@ -0,0 +1,180 @@
package unit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* CWE-407 unit test: asterisk-0003
*
* Models cdr_object_create_public_records() variable merge.
* SLOW: nested list traversal with strcasecmp O(B × V)
* FAST: hash set membership check O(B + V)
*/
public class CdrVarMergeAlgorithm {
static long slowOps = 0;
static long fastOps = 0;
/** Simulated variable: name -> value pair */
static class Var {
String name;
String value;
Var(String name, String value) { this.name = name; this.value = value; }
}
/**
* SLOW path: nested linear scan to deduplicate variables.
* Models the defective AST_LIST_TRAVERSE inside AST_LIST_TRAVERSE.
*
* @param partyAVars already in varshead
* @param partyBVars party_b variables to merge in
* @return merged list
*/
static List<Var> mergeVarsSlow(List<Var> partyAVars, List<Var> partyBVars) {
List<Var> varshead = new ArrayList<>(partyAVars);
for (Var bVar : partyBVars) { // outer: B iterations
boolean found = false;
for (Var existing : varshead) { // inner: V iterations O(B×V)
slowOps++;
if (bVar.name.equalsIgnoreCase(existing.name)) {
found = true;
break;
}
}
if (!found) {
varshead.add(new Var(bVar.name, bVar.value));
}
}
return varshead;
}
/**
* FAST path: hash set membership check O(B + V).
* Fix: build a HashMap of existing names first, then check in O(1).
*
* @param partyAVars already in varshead
* @param partyBVars party_b variables to merge in
* @return merged list
*/
static List<Var> mergeVarsFast(List<Var> partyAVars, List<Var> partyBVars) {
List<Var> varshead = new ArrayList<>(partyAVars);
HashMap<String, Boolean> existingNames = new HashMap<>();
for (Var v : partyAVars) { // O(V) build
fastOps++;
existingNames.put(v.name.toLowerCase(), Boolean.TRUE);
}
for (Var bVar : partyBVars) { // outer: B iterations
fastOps++; // O(1) lookup
if (!existingNames.containsKey(bVar.name.toLowerCase())) {
varshead.add(new Var(bVar.name, bVar.value));
existingNames.put(bVar.name.toLowerCase(), Boolean.TRUE);
}
}
return varshead;
}
static boolean runTest(int numVarsA, int numVarsB, int overlap) {
// Build party_a vars: var_a_0 ... var_a_(numVarsA-1)
List<Var> partyA = new ArrayList<>();
for (int i = 0; i < numVarsA; i++) {
partyA.add(new Var("var_a_" + i, "val_a_" + i));
}
// Build party_b vars: first 'overlap' vars share names with party_a
// rest are unique to party_b
List<Var> partyB = new ArrayList<>();
for (int i = 0; i < overlap; i++) {
partyB.add(new Var("var_a_" + i, "val_b_" + i)); // duplicate
}
for (int i = 0; i < numVarsB - overlap; i++) {
partyB.add(new Var("var_b_" + i, "val_b_" + i)); // unique
}
long slowBefore = slowOps;
long fastBefore = fastOps;
List<Var> slowResult = mergeVarsSlow(partyA, partyB);
List<Var> fastResult = mergeVarsFast(partyA, partyB);
long slowCount = slowOps - slowBefore;
long fastCount = fastOps - fastBefore;
// Both should produce same merged count: numVarsA + (numVarsB - overlap) unique vars
int expectedSize = numVarsA + (numVarsB - overlap);
if (slowResult.size() != expectedSize) {
System.out.println("FAIL: slow result size " + slowResult.size() + " expected " + expectedSize);
return false;
}
if (fastResult.size() != expectedSize) {
System.out.println("FAIL: fast result size " + fastResult.size() + " expected " + expectedSize);
return false;
}
// Slow ops should be at least numVarsB (inner loop on each), approx numVarsB * numVarsA
// Fast ops should be at most numVarsA + numVarsB
double ratio = (double) slowCount / (double) fastCount;
System.out.printf(" N=%d B=%d overlap=%d | slowOps=%d fastOps=%d ratio=%.1fx%n",
numVarsA, numVarsB, overlap, slowCount, fastCount, ratio);
if (ratio < 5.0) {
System.out.printf("FAIL: ratio %.1fx < 5x threshold%n", ratio);
return false;
}
return true;
}
public static void main(String[] args) {
int pass = 0;
int fail = 0;
System.out.println("=== asterisk-0003: CDR Variable Merge O(B×V) ===");
System.out.println();
// Test cases: (numVarsA, numVarsB, overlap)
int[][] tests = {
{20, 20, 5},
{50, 50, 10},
{100, 100, 20},
{200, 200, 50},
{500, 500, 100},
};
for (int[] t : tests) {
slowOps = 0;
fastOps = 0;
boolean ok = runTest(t[0], t[1], t[2]);
if (ok) { pass++; } else { fail++; }
}
System.out.println();
// Verify quadratic growth in slow path
System.out.println("Quadratic growth verification (slow path):");
for (int n : new int[]{10, 50, 100, 200}) {
slowOps = 0;
fastOps = 0;
mergeVarsSlow(buildVarList("a", n), buildVarList("b", n));
System.out.printf(" N=%d slow_ops=%d (expected ~%d quadratic)%n",
n, slowOps, n * n);
}
System.out.println();
System.out.printf("%d/%d PASS%n", pass, pass + fail);
if (fail > 0) {
System.exit(1);
}
}
static List<Var> buildVarList(String prefix, int n) {
List<Var> list = new ArrayList<>();
for (int i = 0; i < n; i++) {
list.add(new Var(prefix + "_var_" + i, "val_" + i));
}
return list;
}
}