comms/voip/smtp: 30 CWE-407 defects + 9 CLEAN; 224 sites, 101 ecosystems
This commit is contained in:
parent
4d3fcc8e73
commit
b3842ab6b8
86 changed files with 6516 additions and 5 deletions
|
|
@ -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. */
|
||||
280
defects/freeswitch/unit/FreeSWITCHTest.java
Normal file
280
defects/freeswitch/unit/FreeSWITCHTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue