Skip to content

fix(core): clear *pool on init failure to prevent UAF in vmaf_gpu_picture_pool_init - #317

Closed
lusoris wants to merge 1 commit into
masterfrom
fix/gpu-picture-pool-uaf-on-init-failure
Closed

lusoris wants to merge 1 commit into
masterfrom
fix/gpu-picture-pool-uaf-on-init-failure

Conversation

@lusoris

@lusoris lusoris commented May 30, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fixes a high-severity use-after-free in vmaf_gpu_picture_pool_init() (core/src/gpu_picture_pool.c) where the combined assignment VmafGpuPicturePool *const p = *pool = malloc(...) publishes the pool pointer to the caller's *pool before any later failure path runs. On goto free_p the function frees p while *pool still holds the dangling pointer; the CUDA call site (libvmaf.c:326) stores that handle in the long-lived VmafContext.cuda.ring_buffer, and vmaf_close() then calls vmaf_gpu_picture_pool_close() on the freed object — UAF + potential double-free.
  • Fix is mechanical: set *pool = NULL after the !p malloc check and after free(p) at the free_p label. The fail: label is unchanged.
  • Adds a CPU-only test_gpu_picture_pool_uaf regression in suite=fast that exercises the goto free_p arm by passing a pic_cnt large enough to make the second malloc() return NULL, then asserts *pool == NULL.
  • Documents the invariant in core/src/AGENTS.md (chore(deps): Update rocm/rocm-terminal Docker tag to v6.4 #10) so future out-parameter init functions inherit the clear-on-failure contract by convention.

Test plan

  • meson setup build-cpu core -Denable_cuda=false -Denable_sycl=false -Denable_hip=false
  • ninja -C build-cpu — green
  • meson test -C build-cpu test_gpu_picture_pool_uaf --print-errorlogs — pass
  • meson test -C build-cpu --suite=fast — 50/50 pass
  • Regression verification: with the *pool = NULL after free(p) reverted, the new test fails on the "pool handle must be NULL after init failure (UAF regression)" assertion. With the fix in place it passes in <1 ms.
  • clang-tidy -p build-cpu core/src/gpu_picture_pool.c core/test/test_gpu_picture_pool_uaf.c — clean
  • clang-format --dry-run --Werror on touched C files — clean
  • CI required-checks aggregator (will fire on ready-for-review).

ADR-0108 deep-dive deliverables

Research digest: [x] no research digest needed: small targeted bug fix per CLAUDE.md §12 r11 ("trivial deliverable" sentinel). Root cause + fix + the surrounding ring-buffer call site are explained inline in the commit body and pinned as core/src/AGENTS.md invariant #10.

Decision matrix: [x] no alternatives: only-one-way fix. Clearing *pool = NULL at the failure labels is the minimal correctness-preserving change. Hoisting the *pool = ... assignment to the success tail was considered but rejected — it changes the emission shape of the const-init pattern reviewers grep for, and would still need a separate NULL-init for the early failure case.

AGENTS.md invariant note: core/src/AGENTS.md invariant #10 added — "Out-parameter init functions must clear the caller's handle on every failure path." Names vmaf_gpu_picture_pool_init as the exemplar and points to the regression test.

Reproducer / smoke-test command:

meson setup build-cpu core -Denable_cuda=false -Denable_sycl=false -Denable_hip=false
ninja -C build-cpu
meson test -C build-cpu test_gpu_picture_pool_uaf --print-errorlogs

Without the fix the test fails on the "pool handle must be NULL after init failure (UAF regression)" assertion. With the fix it passes in microseconds.

CHANGELOG fragment: changelog.d/fixed/gpu-picture-pool-uaf-init-failure.md (per ADR-0221).

Rebase note: docs/rebase-notes.md — gpu-picture-pool-uaf-on-init-failure (2026-05-30) entry added. The file core/src/gpu_picture_pool.c is fork-added by ADR-0239 (promotion of upstream's cuda/ring_buffer.c into a backend-agnostic helper); upstream Netflix/vmaf still ships the original cuda/ring_buffer.c, so any upstream parallel fix will land in a different TU and won't textually conflict. docs/state.md updated under "Recently closed" with the T-GPU-PICTURE-POOL-UAF-INIT-FAILURE-2026-05-30 row per CLAUDE.md §12 r13.

🤖 Generated with Claude Code

…ture_pool_init

The combined assignment

    VmafGpuPicturePool *const p = *pool = malloc(sizeof(*p));

in vmaf_gpu_picture_pool_init() publishes the pool pointer to the
caller's `*pool` argument *before* any later failure path runs. On
`goto free_p` (pic-array malloc failure or pthread_mutex_init failure)
the function then frees `p` while `*pool` still holds the dangling
pointer. The CUDA call site (`libvmaf.c:326`,
`return vmaf_gpu_picture_pool_init(&vmaf->cuda.ring_buffer, ...)`)
stores that handle directly in the long-lived
`VmafContext.cuda.ring_buffer`, and the natural `vmaf_close()`
teardown calls `vmaf_gpu_picture_pool_close(vmaf->cuda.ring_buffer)`
on the freed object — use-after-free, with double-free as a follow-on
once `close()` reaches its trailing `free(pool)`.

Fix: clear `*pool = NULL` at every failure label

  * after the `if (!p)` malloc check (where the assignment already
    stored NULL but we set it explicitly so the contract is grep-able);
  * after `free(p)` at the `free_p` label, before falling through to
    the shared `fail:` return.

The `fail:` label itself is unchanged. The contract this pins is
"caller may inspect `*pool` only on success; a non-zero return
guarantees `*pool == NULL`".

Adds a CPU-only regression test (`test_gpu_picture_pool_uaf`,
`suite=fast`) that triggers the `goto free_p` arm via an oversized
`pic_cnt` so the second `malloc()` returns NULL, then asserts that
the caller's handle is NULL on return. Verified locally that with
the fix reverted the test fails with the expected
"pool handle must be NULL after init failure (UAF regression)"
assertion, and with the fix in place all 50 fast-suite tests pass.

Documents the pattern in `core/src/AGENTS.md` as invariant #10 so
future out-parameter init functions in this directory inherit the
clear-on-failure contract by convention.

**Research digest**: [x] no research digest needed: small targeted
  bug fix per CLAUDE.md §12 r11 ("trivial deliverable" sentinel).
  Root cause + fix are inline in the commit body and the AGENTS.md
  invariant note.
**Decision matrix**: [x] no alternatives: only-one-way fix.
  Clearing `*pool = NULL` at the failure labels is the minimal
  correctness-preserving change. Hoisting the assignment to the
  success tail was considered but rejected because it changes the
  emission shape of the const-init pattern that several reviewers
  rely on for grep-ability.
**AGENTS.md invariant note**: `core/src/AGENTS.md` #10 added —
  "Out-parameter init functions must clear the caller's handle on
  every failure path" with `vmaf_gpu_picture_pool_init` as the
  exemplar and a pointer to the regression test.
**Reproducer / smoke-test command**:
  `meson setup build-cpu core -Denable_cuda=false -Denable_sycl=false
  -Denable_hip=false && ninja -C build-cpu &&
  meson test -C build-cpu test_gpu_picture_pool_uaf`.
  Without the fix the test fails on the
  "pool handle must be NULL after init failure (UAF regression)"
  assertion; with the fix it passes in <1 ms.
**CHANGELOG fragment**:
  `changelog.d/fixed/gpu-picture-pool-uaf-init-failure.md`.
**Rebase note**:
  `docs/rebase-notes.md` — `gpu-picture-pool-uaf-on-init-failure`
  entry added. The file is fork-added by ADR-0239; upstream still
  ships the equivalent code as `cuda/ring_buffer.c` so any upstream
  parallel fix won't textually conflict.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@lusoris
lusoris marked this pull request as ready for review May 31, 2026 13:22
@lusoris
lusoris marked this pull request as draft May 31, 2026 13:54
@lusoris
lusoris marked this pull request as ready for review May 31, 2026 13:59
@lusoris

lusoris commented May 31, 2026

Copy link
Copy Markdown
Contributor Author

Closing as part of marathon cleanup 2026-05-31 (150 PRs merged today). Content likely superseded by sibling merges. Reopen if specific finding still needs work; bigger PRs preferred going forward per session feedback.

@lusoris lusoris closed this May 31, 2026
@lusoris
lusoris deleted the fix/gpu-picture-pool-uaf-on-init-failure branch May 31, 2026 14:08
@lusoris
lusoris restored the fix/gpu-picture-pool-uaf-on-init-failure branch May 31, 2026 18:41
@lusoris lusoris reopened this May 31, 2026
@lusoris
lusoris marked this pull request as draft May 31, 2026 18:49
@lusoris

lusoris commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #515 — bundled into one PR per bigger-PRs guidance. All three diffs preserved verbatim.

@lusoris lusoris closed this Jun 1, 2026
lusoris added a commit that referenced this pull request Jun 2, 2026
…ool UAF/leak + #187 feature-dict double-free)

- GPU pool UAF: clear *pool on every init() failure path so callers can't
  double-free a dangling pointer in vmaf_gpu_picture_pool_close() (ADR-0778).
- picture_pool prealloc leak: two-pass approach — allocate all pictures first,
  then strip priv/ref; error unwind uses vmaf_picture_unref on intact pictures
  instead of manual aligned_free on already-detached data (ADR-0778 Fix-E).
- feature-dict double-free: move feature-dictionary ownership into
  feature_collector; extractors no longer free dicts they don't own (ADR-0806).

Rebased onto master (aa17751) — resolved add/add conflicts in
.github/workflows/ by keeping the core/ path refs from master.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
lusoris added a commit that referenced this pull request Jun 2, 2026
…ool UAF/leak + #187 feature-dict double-free) (#515)

- GPU pool UAF: clear *pool on every init() failure path so callers can't
  double-free a dangling pointer in vmaf_gpu_picture_pool_close() (ADR-0778).
- picture_pool prealloc leak: two-pass approach — allocate all pictures first,
  then strip priv/ref; error unwind uses vmaf_picture_unref on intact pictures
  instead of manual aligned_free on already-detached data (ADR-0778 Fix-E).
- feature-dict double-free: move feature-dictionary ownership into
  feature_collector; extractors no longer free dicts they don't own (ADR-0806).

Rebased onto master (aa17751) — resolved add/add conflicts in
.github/workflows/ by keeping the core/ path refs from master.

Co-authored-by: Lusoris <lusoris@pm.me>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@lusoris
lusoris deleted the fix/gpu-picture-pool-uaf-on-init-failure branch June 4, 2026 08:11
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