comms/voip/smtp: 30 CWE-407 defects + 9 CLEAN; 224 sites, 101 ecosystems

This commit is contained in:
russell@unturf.com 2026-03-27 14:37:02 -04:00
parent 4d3fcc8e73
commit b3842ab6b8
86 changed files with 6516 additions and 5 deletions

View file

@ -0,0 +1,103 @@
diff --git a/src/mod/applications/mod_conference/mod_conference.c b/src/mod/applications/mod_conference/mod_conference.c
index 1234567..abcdef0 100644
--- a/src/mod/applications/mod_conference/mod_conference.c
+++ b/src/mod/applications/mod_conference/mod_conference.c
@@ -612,6 +612,58 @@ static void conference_loop_output(conference_obj_t *conference)
/* Create write frame once per member who is not deaf for each sample in the main frame */
for (omember = conference->members; omember; omember = omember->next) {
switch_size_t ok = 1;
+
+/*
+ * CWE-407 fix: Pre-compute relationship exclusion matrix outside the per-sample loop.
+ *
+ * Original code scanned imember->relationships (linked list, O(R)) and omember->relationships (O(R))
+ * for EVERY sample of EVERY (omember, imember) pair — O(S * M * M * R) per mix cycle.
+ *
+ * Fix: before the per-sample loop for this omember, build a bitmask of imembers whose audio
+ * should be excluded for this omember. One O(M*R) pass replaces O(S*M*R) per omember.
+ *
+ * Implementation uses switch_core stack allocation (conference->member_count bounded).
+ * relationship_total guard is preserved — skip if no relationships exist.
+ */
+
+ /* --- BEGIN relationship pre-computation --- */
+ /* exclude_audio[imember->id % MAX_MEMBERS] set → omember should not hear imember */
+#define CONF_MAX_MEMBERS 512
+ uint8_t exclude_audio[CONF_MAX_MEMBERS];
+ memset(exclude_audio, 0, sizeof(exclude_audio));
+
+ if (conference->relationship_total) {
+ conference_member_t *im2;
+ for (im2 = conference->members; im2; im2 = im2->next) {
+ if (im2 == omember) continue;
+ if (!conference_utils_member_test_flag(im2, MFLAG_HAS_AUDIO)) continue;
+
+ conference_relationship_t *rel;
+ switch_size_t found = 0;
+ /* Check im2 → omember: can im2 speak to omember? */
+ for (rel = im2->relationships; rel; rel = rel->next) {
+ if ((rel->id == omember->id || rel->id == 0) && !switch_test_flag(rel, RFLAG_CAN_SPEAK)) {
+ exclude_audio[im2->id % CONF_MAX_MEMBERS] = 1;
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ /* Check omember → im2: can omember hear im2? */
+ for (rel = omember->relationships; rel; rel = rel->next) {
+ if ((rel->id == im2->id || rel->id == 0) && !switch_test_flag(rel, RFLAG_CAN_HEAR)) {
+ exclude_audio[im2->id % CONF_MAX_MEMBERS] = 1;
+ break;
+ }
+ }
+ }
+ }
+ }
+ /* --- END relationship pre-computation --- */
if (!conference_utils_member_test_flag(omember, MFLAG_RUNNING) ||
(!conference_utils_member_test_flag(omember, MFLAG_NOCHANNEL) && !switch_channel_test_flag(omember->channel, CF_AUDIO))) {
@@ -641,24 +693,15 @@ static void conference_loop_output(conference_obj_t *conference)
z -= (int32_t) bptr[x];
}
- /* when there are relationships, we have to do more work by scouring all the members to see if there are any
- reasons why we should not be hearing a particular member, and if not, delete their samples as well.
- */
- if (conference->relationship_total) {
- for (imember = conference->members; imember; imember = imember->next) {
- if (imember != omember && conference_utils_member_test_flag(imember, MFLAG_HAS_AUDIO)) {
- conference_relationship_t *rel;
- switch_size_t found = 0;
- int16_t *rptr = (int16_t *) imember->frame;
- for (rel = imember->relationships; rel; rel = rel->next) {
- if ((rel->id == omember->id || rel->id == 0) && !switch_test_flag(rel, RFLAG_CAN_SPEAK)) {
- z -= (int32_t) rptr[x];
- found = 1;
- break;
- }
- }
- if (!found) {
- for (rel = omember->relationships; rel; rel = rel->next) {
- if ((rel->id == imember->id || rel->id == 0) && !switch_test_flag(rel, RFLAG_CAN_HEAR)) {
- z -= (int32_t) rptr[x];
- break;
- }
- }
- }
- }
- }
- }
+ /* CWE-407 fix: use pre-computed exclude matrix — O(1) lookup per (omember, imember) per sample */
+ if (conference->relationship_total) {
+ for (imember = conference->members; imember; imember = imember->next) {
+ if (imember != omember && conference_utils_member_test_flag(imember, MFLAG_HAS_AUDIO)) {
+ if (exclude_audio[imember->id % CONF_MAX_MEMBERS]) {
+ int16_t *rptr = (int16_t *) imember->frame;
+ z -= (int32_t) rptr[x];
+ }
+ }
+ }
+ }
/* Now we can convert to 16 bit. */

View file

@ -0,0 +1,280 @@
package unit;
import java.util.*;
/**
* CWE-407 benchmark for freeswitch defects:
* freeswitch-0001: mod_conference.c relationship list scan inside per-sample audio mixing
* O(S × M × M × R) relationship linked-list scan per sample per member pair
*
* Simulates the audio mixing loop in C with Java equivalents:
* - S = samples per frame (160 at 8kHz/20ms)
* - M = conference members
* - R = relationships per member (singly-linked list)
*/
public class FreeSWITCHTest {
static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) {
slow.run(); fast.run();
long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime() - t0) / 1_000_000;
long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime() - t1) / 1_000_000;
double r = fOps > 0 ? (double) sOps / fOps : 0;
System.out.printf(" %-60s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n",
label, sMs, sOps, fMs, fOps, r);
}
// Simulated conference relationship (singly-linked list like C struct)
static class Rel {
int id; // member ID this relationship targets (0 = all)
boolean canSpeak;
boolean canHear;
Rel next;
Rel(int id, boolean speak, boolean hear) { this.id = id; canSpeak = speak; canHear = hear; }
}
static class Member {
int id;
short[] frame;
Rel relationships; // singly-linked list head
Member(int id, int samples) {
this.id = id;
frame = new short[samples];
Arrays.fill(frame, (short) 100);
}
void addRelationship(int targetId, boolean canSpeak, boolean canHear) {
Rel r = new Rel(targetId, canSpeak, canHear);
r.next = relationships;
relationships = r;
}
}
//
// freeswitch-0001: relationship scan per sample
//
/**
* Slow: O(S × M × M × R) relationship linked-list scanned for every sample.
* Matches the original mod_conference.c code structure.
*/
static long slowConferenceMix(List<Member> members, int samples, boolean hasRelationships) {
long ops = 0;
int[] mainFrame = new int[samples];
// Build main frame: sum all members' audio
for (Member m : members) {
for (int x = 0; x < samples; x++) {
mainFrame[x] += m.frame[x];
ops++;
}
}
// Per output member: subtract self, subtract excluded members
for (Member omember : members) {
int[] writeFrame = new int[samples];
for (int x = 0; x < samples; x++) {
ops++;
int z = mainFrame[x] - omember.frame[x];
if (hasRelationships) {
// Inner member loop
for (Member imember : members) {
if (imember == omember) continue;
boolean found = false;
// Scan imember->relationships linked list O(R)
for (Rel rel = imember.relationships; rel != null; rel = rel.next) {
ops++;
if ((rel.id == omember.id || rel.id == 0) && !rel.canSpeak) {
z -= imember.frame[x];
found = true;
break;
}
}
if (!found) {
// Scan omember->relationships O(R)
for (Rel rel = omember.relationships; rel != null; rel = rel.next) {
ops++;
if ((rel.id == imember.id || rel.id == 0) && !rel.canHear) {
z -= imember.frame[x];
break;
}
}
}
}
}
writeFrame[x] = z;
}
}
return ops;
}
/**
* Fast: O(M² × R + S × M²) pre-compute exclusion matrix outside sample loop.
* Relationship scan moved out of the per-sample hot path.
*/
static long fastConferenceMix(List<Member> members, int samples, boolean hasRelationships) {
long ops = 0;
int[] mainFrame = new int[samples];
// Build main frame
for (Member m : members) {
for (int x = 0; x < samples; x++) {
mainFrame[x] += m.frame[x];
ops++;
}
}
// Per output member: pre-compute exclusion bitmask, then apply per sample
for (Member omember : members) {
// Pre-compute: which imembers are excluded for this omember?
// O(M × R) done ONCE per omember, not per sample
boolean[] excludeAudio = new boolean[members.size()];
if (hasRelationships) {
for (int ii = 0; ii < members.size(); ii++) {
Member imember = members.get(ii);
if (imember == omember) continue;
boolean found = false;
for (Rel rel = imember.relationships; rel != null; rel = rel.next) {
ops++;
if ((rel.id == omember.id || rel.id == 0) && !rel.canSpeak) {
excludeAudio[ii] = true;
found = true;
break;
}
}
if (!found) {
for (Rel rel = omember.relationships; rel != null; rel = rel.next) {
ops++;
if ((rel.id == imember.id || rel.id == 0) && !rel.canHear) {
excludeAudio[ii] = true;
break;
}
}
}
}
}
// Per-sample loop: O(S × M) with O(1) exclusion lookup
int[] writeFrame = new int[samples];
for (int x = 0; x < samples; x++) {
ops++;
int z = mainFrame[x] - omember.frame[x];
if (hasRelationships) {
for (int ii = 0; ii < members.size(); ii++) {
ops++;
if (excludeAudio[ii]) {
z -= members.get(ii).frame[x];
}
}
}
writeFrame[x] = z;
}
}
return ops;
}
//
public static void main(String[] args) {
int M = 20; // conference members
int S = 160; // samples per frame (8kHz, 20ms)
int R = 4; // relationships per member
// Build members
List<Member> members = new ArrayList<>();
for (int i = 0; i < M; i++) {
members.add(new Member(i, S));
}
// Add relationships: each member excludes 1-2 others (simulate mute/subconference)
Random rng = new Random(42);
for (int i = 0; i < M; i++) {
for (int r = 0; r < R / 2; r++) {
int target = rng.nextInt(M);
if (target != i) {
members.get(i).addRelationship(target, false, true); // can't speak to target
}
}
}
System.out.println("=== freeswitch CWE-407 benchmark ===");
System.out.printf(" M=%d members, S=%d samples/frame, R=%d relationships/member%n%n", M, S, R);
long sOps = slowConferenceMix(members, S, true);
long fOps = fastConferenceMix(members, S, true);
bench("freeswitch-0001 conference mix [M=" + M + ",S=" + S + ",R=" + R + "] with rels",
() -> slowConferenceMix(members, S, true),
() -> fastConferenceMix(members, S, true),
sOps, fOps);
// Also benchmark without relationships (baseline)
long sOpsNoRel = slowConferenceMix(members, S, false);
long fOpsNoRel = fastConferenceMix(members, S, false);
bench("freeswitch-0001 conference mix [M=" + M + ",S=" + S + "] no rels (baseline)",
() -> slowConferenceMix(members, S, false),
() -> fastConferenceMix(members, S, false),
sOpsNoRel, fOpsNoRel);
// Higher M to show quadratic growth
int M2 = 40;
List<Member> members2 = new ArrayList<>();
for (int i = 0; i < M2; i++) {
Member m2 = new Member(i, S);
for (int r = 0; r < R / 2; r++) {
int target = rng.nextInt(M2);
if (target != i) m2.addRelationship(target, false, true);
}
members2.add(m2);
}
long sOps2 = slowConferenceMix(members2, S, true);
long fOps2 = fastConferenceMix(members2, S, true);
bench("freeswitch-0001 conference mix [M=" + M2 + ",S=" + S + ",R=" + R + "] with rels",
() -> slowConferenceMix(members2, S, true),
() -> fastConferenceMix(members2, S, true),
sOps2, fOps2);
System.out.println();
// Assertions
int pass = 0, total = 0;
// slow ops should scale as O(S*M*M*R), fast as O(S*M + M*M*R)
// slow / fast > R (relationship scan eliminated from inner loop)
total++;
long expectedSlowOps = (long) S * M * M; // at minimum (without R)
if (sOps > fOps * 2 && sOps >= expectedSlowOps) {
System.out.printf(" freeswitch-0001 M=%d: PASS (slow=%,d >= S*M²=%,d, fast=%,d, ratio=%.1fx)%n",
M, sOps, expectedSlowOps, fOps, (double)sOps/fOps);
pass++;
} else {
System.out.printf(" freeswitch-0001 M=%d: FAIL (slow=%,d fast=%,d)%n", M, sOps, fOps);
}
// At M2=40 slow ops should be roughly 4× M=20 (quadratic growth)
total++;
long expectedSlowOps2 = (long) S * M2 * M2;
if (sOps2 > fOps2 * 2 && sOps2 >= expectedSlowOps2) {
System.out.printf(" freeswitch-0001 M=%d: PASS (slow=%,d >= S*M²=%,d, fast=%,d, ratio=%.1fx)%n",
M2, sOps2, expectedSlowOps2, fOps2, (double)sOps2/fOps2);
pass++;
} else {
System.out.printf(" freeswitch-0001 M=%d: FAIL (slow=%,d fast=%,d)%n", M2, sOps2, fOps2);
}
// Quadratic growth check: sOps2 / sOps should be roughly (M2/M)² = 4
total++;
double growthRatio = (double) sOps2 / sOps;
double expectedGrowth = (double)(M2 * M2) / (M * M);
if (growthRatio >= expectedGrowth * 0.5) {
System.out.printf(" freeswitch-0001 quadratic growth: PASS (ratio=%.1fx, expected~%.1fx)%n",
growthRatio, expectedGrowth);
pass++;
} else {
System.out.printf(" freeswitch-0001 quadratic growth: FAIL (ratio=%.1fx, expected~%.1fx)%n",
growthRatio, expectedGrowth);
}
System.out.println();
System.out.println(pass + "/" + total + (pass == total ? " PASS" : " FAIL"));
if (pass != total) System.exit(1);
}
}