vlc+obs-studio: CWE-407 scan — 2 defects (vlc-0001 randomizer_Remove O(N×C), obs-studio-0001 push_audio_tree da_find O(N²)/frame)

This commit is contained in:
russell@unturf.com 2026-03-30 11:33:01 -04:00
parent 860b0ee52e
commit 15a65b2227
4 changed files with 271 additions and 0 deletions

View file

@ -0,0 +1,38 @@
# UNDF: (leave blank)
# CWE-407: Algorithmic Complexity — push_audio_tree da_find O(N²) per frame
#
# push_audio_tree() and push_audio_tree2() use da_find(audio->render_order,
# &source, 0) which is a linear scan O(N) of the render_order darray.
# These callbacks are invoked via obs_source_enum_active_tree() for every
# source in the audio scene graph. With N sources, building the render
# order is O(N²) — and this runs every audio frame (~47 Hz at 48 kHz).
#
# Fix: maintain a pointer hash set alongside render_order for O(1)
# membership checks, or use a 'visited' flag on obs_source_t cleared
# at the start of each audio tick.
#
# Severity: HIGH (hot audio path, runs ~47 times/second, quadratic in
# active source count — noticeable with complex scenes)
# Measured: 250× op-count ratio at N=500 sources
#
--- a/libobs/obs-audio.c
+++ b/libobs/obs-audio.c
@@ -30,7 +30,8 @@ static void push_audio_tree(obs_source_t *parent, obs_source_t *source, void *p)
{
struct obs_core_audio *audio = p;
- if (da_find(audio->render_order, &source, 0) == DARRAY_INVALID) {
+ /* TODO: replace da_find O(N) linear scan with hash set for O(1) lookup.
+ * Current code is O(N²) per audio frame when N sources are active. */
+ if (da_find(audio->render_order, &source, 0) == DARRAY_INVALID) {
obs_source_t *s = obs_source_get_ref(source);
if (s) {
da_push_back(audio->render_order, &s);
@@ -59,7 +60,8 @@ static void push_audio_tree2(obs_source_t *parent, obs_source_t *source, void *p
struct obs_core_audio *audio = p;
- size_t idx = da_find(audio->render_order, &source, 0);
+ /* TODO: replace da_find O(N) with hash set for O(1) lookup */
+ size_t idx = da_find(audio->render_order, &source, 0);
if (idx == DARRAY_INVALID) {

View file

@ -0,0 +1,96 @@
/**
* CWE-407 simulation: OBS Studio push_audio_tree da_find O(N²) per audio frame.
*
* Simulates the defect in libobs/obs-audio.c where push_audio_tree()
* and push_audio_tree2() use da_find(audio->render_order, &source, 0)
* — a linear scan — to check if a source is already in the render order.
* Called once per source during audio tree traversal, this is O(N²) per
* audio frame (~47 Hz). With complex scenes (many sources), this causes
* measurable audio processing overhead.
*
* Fix: use a HashSet alongside the render_order list for O(1) membership.
*/
import java.util.*;
public class ObsStudioTest {
/* --- Defective path: linear scan per source (da_find) --- */
static int buildRenderOrderDefective(Object[] sources) {
int ops = 0;
List<Object> renderOrder = new ArrayList<>();
for (Object source : sources) {
// da_find: linear scan of render_order
boolean found = false;
for (int i = 0; i < renderOrder.size(); i++) {
ops++;
if (renderOrder.get(i) == source) {
found = true;
break;
}
}
if (!found) {
renderOrder.add(source);
}
}
return ops;
}
/* --- Fixed path: hash set for O(1) membership --- */
static int buildRenderOrderFixed(Object[] sources) {
int ops = 0;
List<Object> renderOrder = new ArrayList<>();
Set<Object> seen = new HashSet<>();
for (Object source : sources) {
ops++; // HashSet.contains is O(1)
if (seen.add(source)) {
renderOrder.add(source);
}
}
return ops;
}
public static void main(String[] args) {
int[] sizes = {100, 500, 1000};
boolean allPass = true;
System.out.println("=== OBS Studio push_audio_tree CWE-407 Test ===");
System.out.printf("%-8s %-12s %-12s %-8s %-6s%n",
"N", "Defective", "Fixed", "Ratio", "Pass");
for (int N : sizes) {
// Simulate N unique sources being added to render order
Object[] sources = new Object[N];
for (int i = 0; i < N; i++) sources[i] = new Object();
int opsDefective = buildRenderOrderDefective(sources);
int opsFixed = buildRenderOrderFixed(sources);
double ratio = (double) opsDefective / opsFixed;
boolean pass = ratio > 2.0;
if (!pass) allPass = false;
System.out.printf("%-8d %-12d %-12d %-8.1fx %-6s%n",
N, opsDefective, opsFixed, ratio, pass ? "PASS" : "FAIL");
}
// Simulate repeated frames (the defect runs every audio frame)
System.out.println("\n--- Per-second overhead at 47 frames/sec ---");
int N = 200; // realistic complex scene
Object[] sources = new Object[N];
for (int i = 0; i < N; i++) sources[i] = new Object();
int perFrameDefective = buildRenderOrderDefective(sources);
int perFrameFixed = buildRenderOrderFixed(sources);
System.out.printf("N=%d: defective=%d ops/frame × 47 = %d ops/sec%n",
N, perFrameDefective, perFrameDefective * 47);
System.out.printf("N=%d: fixed=%d ops/frame × 47 = %d ops/sec%n",
N, perFrameFixed, perFrameFixed * 47);
System.out.println();
if (allPass) {
System.out.println("ALL PASS — defective path is quadratic, fixed path is linear");
} else {
System.out.println("FAIL — ratio not demonstrated");
System.exit(1);
}
}
}