Skip to content

fix: 5 Fable deep-hunt findings — integer_ssim AVX2 16-bit overflow, bpc validation, set_fex UAF, vmafx-server DoS cap, CUDA prev_ref - #866

Merged
lusoris merged 5 commits into
masterfrom
chore/bundle-fable-5-findings
Jun 12, 2026
Merged

lusoris merged 5 commits into
masterfrom
chore/bundle-fable-5-findings

Conversation

@lusoris

@lusoris lusoris commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Five Fable deep-hunt findings fixed in one bundle. Commit 3 (set_fex UAF) is already on master (PR #859); the remaining four are cherry-picked from their fix branches.

Fixes

# Severity ID Description
1 High (silent wrong SSIM on 16-bit content) T-INTEGER-SSIM-AVX2-16BIT-OVERFLOW integer_ssim_avx2.c: reorder w*(s*s) to (w*s)*s; for pixels ≥ 46341 the old order set bit 31, corrupting SSIM accumulators. Netflix golden gate uses 8/10-bit and never detected this.
2 High (silent acceptance of depth-mismatched frames) T-BPC-VALIDATION-AND-OR libvmaf.c validate_pic_params: && → ||; with && the bpc guard always short-circuited false on frame 0, silently accepting 8bpc/10bpc mismatched pairs.
3 Informational (already fixed) set_fex ordering Present on master in commit 3f6ec11ea / PR #859; no further changes.
4 Medium (DoS via unbounded subprocess fork) T-VMAFX-SERVER-CONCURRENCY-CAP cmd/vmafx-server/: ScoreLimiter semaphore shared across HTTP and gRPC; default cap = runtime.NumCPU(); excess callers get 429 / ResourceExhausted; --max-concurrent-scores flag.
5 Medium (latent UAF + swallowed error) T-CUDA-PREV-REF-UAF-DIST-TRANSLATE libvmaf.c CUDA path: Phase 2 PREV_REF now uses vmaf_picture_ref; dist translate error is no longer (void)-discarded.

Most dangerous: fix #1 (integer_ssim AVX2 16-bit) — produces silently wrong SSIM scores on any 16-bit content (BT.2020, HDR), undetectable by the Netflix golden gate.

Deliverables checklist

  • Research digest: no digest needed: all four fixes are deterministic bug corrections with clear root-cause analyses; no competing approaches exist.
  • Decision matrix: no alternatives: only-one-way fix for each (operator correction, operand reorder, reference counting, error propagation).
  • AGENTS.md invariant note: no rebase-sensitive invariants: changes confined to integer_ssim_avx2.c, libvmaf.c, cmd/vmafx-server/ (5 files), and new test files.
  • Reproducer / smoke-test: meson test -C build-fable --suite=fast 88/88 pass; go test ./cmd/vmafx-server/... ./pkg/libvmaf/... pass; test_integer_ssim_simd 5/5; test_validate_pic_params_bpc 5/5.
  • changelog.d fragment: changelog.d/fixed/fable5-bundle-integer-ssim-bpc-server-cuda.md (4 entries).
  • docs/rebase-notes.md: entry chore/bundle-fable-5-findings added.
  • state.md: 4 rows added to Recently closed (T-INTEGER-SSIM-AVX2-16BIT-OVERFLOW, T-BPC-VALIDATION-AND-OR, T-VMAFX-SERVER-CONCURRENCY-CAP, T-CUDA-PREV-REF-UAF-DIST-TRANSLATE, all 2026-06-12).
  • Netflix golden assertions: not modified.
  • ffmpeg-patches: no public header or API surface changed; patches not affected.

lusoris and others added 5 commits June 12, 2026 20:40
The 16-bit accumulation loop in integer_ssim_accumulate_row_16_avx2()
computed squared moments as _mm256_mul_epi32(wv, _mm256_mul_epi32(sv, sv)).
For true 16-bit content (pixel p >= 46341), s*s >= 2^31, setting bit 31
of the low 32-bit lane. _mm256_mul_epi32 reads the low 32 bits as a
signed quantity, so sv_sq appears negative and the outer multiply with wv
produces a corrupted (wrong-sign) 64-bit product for x2, xy, and y2.

The 8-bit and 10-bit paths were unaffected because max s*s = 65535^2/1023^2
stays below 2^31 only for bpc <= 15.  Netflix golden tests use 8/10-bit
content, so the gate never caught it.

Fix: reorder to (w*s)*s.  w*s <= 256*65535 = 16,776,960 < 2^24 — bit 31
always clear — so both _mm256_mul_epi32 operands are safely non-negative.
Same reorder applied to w*d and the xy cross-term.

Adds a regression test (test_integer_ssim_avx2_16bpc_bright) feeding
alternating 65535/65534 pixels — the exact values that trigger the overflow
— and asserting bit-exact equality between AVX2 and scalar.

There is no AVX-512 twin for integer_ssim (only integer_ssim_avx2.c
exists in core/src/feature/x86/), so no further file requires the fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…_params

The bpc consistency check in validate_pic_params() used && where all
sibling checks (w, h, pix_fmt) use ||:

  WRONG: if ((ref->bpc != dist->bpc) && (ref->bpc != vmaf->pic_params.bpc))
  FIXED: if ((ref->bpc != dist->bpc) || (ref->bpc != vmaf->pic_params.bpc))

On frame 0, pic_params.bpc is assigned from ref->bpc immediately before
the guard, making the second term always false.  The && short-circuits to
false for any first-frame submission, so a ref=8bpc / dist=10bpc pair is
silently accepted.  The dist buffer is then read at the wrong
stride/element size, producing garbage output or an OOB access.

Fix: change && to || so any bpc mismatch (ref≠dist or ref≠pic_params) is
rejected with -EINVAL, consistent with all other dimension/format checks.

Inherited from upstream Netflix/vmaf but a genuine logic bug.  Reported
as Fable finding libvmaf-bpc-validation-and-or.

Adds regression test test_validate_pic_params_bpc (fast suite) that
covers: matched bpc accepted, 8bpc/10bpc mismatch rejected on frame 0,
reversed mismatch rejected, consistent 10bpc across frames accepted, and
bpc change mid-stream rejected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nt DoS

Without a cap, N concurrent unauthenticated POST /v1/score or gRPC Score
calls fork N vmaf subprocesses simultaneously, exhausting CPU/RAM/PIDs.

Add ScoreLimiter (golang.org/x/sync/semaphore.Weighted) shared across both
the HTTP and gRPC entry points so the cap is enforced system-wide.  Excess
callers receive HTTP 429 (TooManyRequests) or gRPC codes.ResourceExhausted
immediately when their context is already cancelled; waiting callers are
unblocked in FIFO order as slots free up.  A new --max-concurrent-scores
flag (default: runtime.NumCPU()) controls the cap size.

Existing constructors (newGRPCServer / newHTTPServer) are preserved unchanged
for test compatibility; production startup uses the new WithLimiter variants.

Fixes Fable finding vmafx-server-concurrency-cap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two bugs in the CUDA read-pictures path in core/src/libvmaf.c:

1. read_pictures_extractor_loop_cuda (Phase 2 submit loop): prev_ref was
   assigned via a bare struct copy (`fex_ctx->fex->prev_ref = vmaf->prev_ref`)
   instead of vmaf_picture_ref().  Without a counted reference, the picture
   pool can reuse the buffer after read_pictures_update_prev_ref decrements
   vmaf->prev_ref on the next frame, while the CUDA extractor is still
   reading from it.  Latent today (no CUDA extractor carries
   VMAF_FEATURE_EXTRACTOR_PREV_REF), but becomes a live UAF+leak the moment
   one is added.  Fix: vmaf_picture_ref in Phase 2; unref+zero on error;
   unref+zero after collect in Phase 1 (ADR-0778 Fix-B, mirroring the sync
   path in read_pictures_dispatch_one).

2. read_pictures_cuda_translate: the dist-side translate_picture call was
   wrapped in (void), swallowing any error.  If translate fails, dist_device
   may be partially populated and subsequently passed to CUDA extractors.
   Fix: capture the return value and propagate as the sync path does for ref.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r Fable-5 bundle

Four recently-closed rows added to docs/state.md:
  T-INTEGER-SSIM-AVX2-16BIT-OVERFLOW-2026-06-12
  T-BPC-VALIDATION-AND-OR-2026-06-12
  T-VMAFX-SERVER-CONCURRENCY-CAP-2026-06-12
  T-CUDA-PREV-REF-UAF-DIST-TRANSLATE-2026-06-12

changelog.d/fixed/fable5-bundle-integer-ssim-bpc-server-cuda.md added.
docs/rebase-notes.md entry added for chore/bundle-fable-5-findings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@lusoris
lusoris merged commit e91044a into master Jun 12, 2026
56 of 65 checks passed
@lusoris
lusoris deleted the chore/bundle-fable-5-findings branch June 12, 2026 18:44
@lusoris lusoris added this to the 1.0.0 — First release milestone Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant