Repository navigation
fix(core): sanitizer-pass cleanup — CAMBI option type-mismatch + ADM signed-shift UB - #352
Conversation
Pull request was converted to draft
|
Watch-loop skipped: rebase onto current |
9cfdc8a to
6270309
Compare
Pull request was closed
6270309 to
bc0f002
Compare
…signed-shift UB (ADR-0869)
Local ASan+UBSan audit on master tip surfaced two real UB findings
that the per-PR CI sanitizer combination did not catch.
1) core/src/feature/cambi.c: the options table declared `window_size`
and `max_log_contrast` as VMAF_OPT_TYPE_INT, but the underlying
CambiState fields were uint16_t. set_option_int's `*(int *)data =
value` write produced a UBSan misaligned-store on max_log_contrast
(2-byte-aligned slot) AND silently overwrote src_window_size with
the upper bytes of the int when setting window_size. Mirror read
site in vmaf_feature_name_from_options repeated the load-side
misalignment per frame for every --feature cambi invocation.
Fix: add shadow int slots window_size_opt / max_log_contrast_opt;
point the options at them; copy into the existing uint16_t runtime
fields in init(). Option-parser bounds (15..127, 0..5) guarantee
the narrowing is lossless. Inner-loop SIMD/scalar signatures
unchanged. Bit-exact with prior behaviour.
2) core/src/feature/x86/adm_avx{2,512}.c: DWT2 filter packing used
`(uint32_t)(filter[k] << 16)` — cast on the shift's RESULT rather
than its operand, so the inner shift still ran on a signed int. UB
whenever filter[k] was negative (every HBD ADM frame; UBSan logs
`left shift of negative value -4240`).
Fix: move the cast inside — `((uint32_t)filter[k] << 16)`. Shifting
an unsigned operand is fully defined; bit-exact with the original
wrap-on-overflow signed shift on every two's-complement target.
Verification:
- ASan+UBSan unit-test suite (49 fast + 12 dnn + 2 slow = 63 tests OK)
- vmaf CLI under sanitizers on 4:2:0 8-bit, 4:2:2 10-bit, 4:2:0 12-bit
+ full feature set (psnr, ssim, ms_ssim, ciede, cambi, psnr_hvs, vif,
adm, motion) + model load — all silent under sanitizers
- Cambi feature-name derivation produces cambi_mlc_3_ws_63 with tuned
options (proves both shadow-slot write and read are correct)
- AVX2/AVX-512 ADM bit-identical with master on Netflix golden 10/12-bit
Restored from orphan commit 6270309 (original PR #352 head was force-
pushed away when the watch-loop mis-handled a rebase conflict; the
underlying fix is unchanged).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Recovered and reopened. The original branch was force-pushed to master tip ( This force-push restores the actual fix on top of current
Kept as DRAFT — train-watcher will promote when ready. |
eb4ed79 to
20b0d9e
Compare
…CI) (#868) * fix(codeql): resolve HIGH-severity security-cpp-high alerts (23 sites) - cpp/integer-multiplication-cast-to-long (11): pre-cast one operand to size_t / double / ptrdiff_t before int*int multiplications in cambi.c, float_vif.c (log message), iqa/convolve.c (img_offset), moment.c, psnr.c, and vif_tools.c (four memcpy size expressions). Add stddef.h to convolve.c for ptrdiff_t. - cpp/incomplete-parity-check (3): change `% 2 == 1` to `% 2 != 0` in vif_tools.c (assert), svm.cpp (powi loop), pdjson.c (JSON object key/value alternation). The == 1 form is wrong for negative operands; != 0 is always correct. - cpp/wrong-type-format-argument (2): fix float_vif.c error log that printed size_t fields scaled_w/scaled_h with %d; change to %zu. - cpp/world-writable-file-creation (1): in vmaf.cpp replace bare fopen("wb") with open(O_WRONLY|O_CREAT|O_TRUNC, 0644)+fdopen() on POSIX so the created file is never world-writable independent of the caller's umask. Add <fcntl.h>. - cpp/path-injection (4): in test_output.c resolve the mkstemp-created path through realpath() immediately after creation, breaking the taint chain from getenv("TMPDIR") to the vmaf_write_output call site. - cpp/toctou-race-condition (2): skipped — both sites are in test cleanup (RMDIR after stat assertion). The stat result drives a test assertion, not a security-sensitive access decision; no atomic replacement of open() is applicable to rmdir. Reported as skipped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(codeql): resolve security-python-and-ci CodeQL alerts Fixes 18 open CodeQL alerts across the Python and CI categories: - yaml.github-actions.security.run-shell-injection (#661): move github.event_name, github.base_ref, and github.event.before from inline ${{...}} interpolation to env: vars in the SYCL clang-tidy detect step of lint-and-format.yml. - python.lang.security.use-defused-xml-parse (#216, #217): replace xml.etree.ElementTree with defusedxml.ElementTree in feature_extractor.py and quality_runner.py; add defusedxml>=0.7.1 to python/pyproject.toml and python/requirements.txt. - py/undefined-export (#352, #353, #354, #616): restructure aiutils/__init__.py to do a conditional eager import of the parquet helpers so the names are defined when pyarrow is present, and only include them in __all__ when the import succeeded. - py/stack-trace-exposure (#178, #179, #585): log exception detail server-side and return a generic message to the HTTP client in http_transport.py _handle_score (invalid JSON, bad params, scorer error branches). - python.lang.security.audit.dangerous-subprocess-use-tainted-env-args (#227, #372): add shlex.quote() around user-supplied path arguments passed into shell strings in extract_ugc_features.py and test_bbb_e2e_v5_bug_cluster.py. - py/file-not-closed (#677, #678): replace bare open() calls with context managers in test_coverage_round3.py. - py/redundant-comparison (#427, #431): remove redundant assert not (x != y) lines that duplicate the preceding assert x == y. - py/equals-hash-mismatch (#182): convert RdPoint to frozen=True dataclass so __eq__ and __hash__ are generated consistently. - py/inheritance/signature-mismatch (#197): add result_dict=None default to EnsembleVmafQualityRunner._populate_result_dict so the signature is compatible with the base class. - py/multiple-definition (#201): drop redundant assignment to feature_found in feature_extractor.py wildcard discovery path. - py/str-format/surplus-named-argument (#204): remove unused dataset= kwarg from the format() call in routine.py. Skipped: python.lang.security.audit.insecure-file-permissions (#373) — the Unix socket at 0o660 is intentional (Go sidecar node must write to it and runs as the same UNIX group); tightening to 0o644 would break the IPC channel. Skipped: py/path-injection (#180, #181) — _validate_path() already resolves the path and checks it against an allowlist before any file operation; the data flow is secure and the CodeQL dataflow trace is a false positive on this allowlisted pattern. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Lusoris <lusoris@pm.me> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Summary
Local
-Db_sanitize=address,undefinedaudit on master tipbbcaa8d127surfaced two real undefined-behaviour findings that the per-PR CI
sanitizer combination is not currently catching. Both are bit-exact
fixes — no numerical-correctness delta.
CAMBI option-parser misaligned
uint16_twrite. Options tabledeclared
window_sizeandmax_log_contrastasVMAF_OPT_TYPE_INTbut the underlying
CambiStatefields wereuint16_t. Theparser's
*(int *)data = value(a) silently clobbered adjacentsrc_window_sizewhen writingwindow_sizeand (b) hit aUBSan-flagged misaligned 4-byte store on
max_log_contrast(2-byte-aligned slot). Mirror read-side misalignment in
vmaf_feature_name_from_options(core/src/feature/feature_name.c:104)per frame.
AVX2 / AVX-512 ADM DWT2 filter packing — left-shift of negative
signed int.
(uint32_t)(filter[k] << 16)casts the result, notthe operand — the inner shift runs on a signed
int. UB on everyHBD ADM frame; UBSan reports
left shift of negative value -4240.Type
fix— bug fixsimd— backend-specific (AVX2 / AVX-512 ADM)Checklist
pre-commit run --filesis green locally on every touched file(clang-format, secrets, ADR-0332, ADR-0105, ADR-0386 checks all
pass; semgrep clean on the C diff).
meson test -C build-asan— 63/63 OK with ASan+ UBSan + leak detection enabled.
(cast move inside parentheses);
/cross-backend-diffstyleworst-ULP check unchanged.
test_integer_adm_simdstillpasses — scalar / AVX2 / AVX-512 ULP 0.
twins both fixed in this PR (only x86 twins existed for ADM
DWT2 filter packing).
files; the new docs/changelog files are markdown).
Bug-status hygiene (ADR-0165)
docs/state.mdupdated — new rowT-SANITIZER-PASS-CLEANUP-2026-05-30in## Recently closed.Netflix golden-data gate (ADR-0024)
assertAlmostEqual(...)golden assertions modified. Bothfixes are bit-exact with prior behaviour on every input.
Cross-backend numerical results
Performance
Not a perf change. The CAMBI fix adds two assignments and 8 bytes per
extractor instance (one per pipeline). The ADM fix is one character
per call site and produces identical machine code under any optimising
compiler with two's-complement integer representation.
Deep-dive deliverables (ADR-0108)
docs/research/sanitizer-pass-2026-05-30.md.docs/adr/0869-sanitizer-pass-cleanup.md§Alternatives considered (5 rows: option-type-add, struct-type-flip, shadow-int-bridge chosen, NOLINT-and-ignore, branch-on-sign).AGENTS.mdinvariant note — no rebase-sensitive invariants: this PR fixes a 4-character cast move + a struct shadow slot; not invariant-class changes.changelog.d/fixed/sanitizer-pass-cleanup.md.docs/rebase-notes.mdundersanitizer-pass-cleanup (2026-05-30, ADR-0869).Reproducer
Findings detail
core/src/feature/cambi.c(CambiState)VMAF_OPT_TYPE_INTparser writes intouint16_tslot at 2-byte-aligned offset; clobbers adjacent field onwindow_size--feature cambicore/src/feature/x86/adm_avx{2,512}.c(4 sites each)left shift of negative valueintis UBKnown follow-ups
CambiState::enc_width,enc_height,enc_bitdepth,src_width,src_heightareunsignedbut targeted byVMAF_OPT_TYPE_INT.Layout-compatible on every shipped ABI; UBSan does not flag. A future
stricter option-type schema would discharge the class.
AdmState::adm_adm3_apply_hmisintbut targeted byVMAF_OPT_TYPE_BOOL. Safe by accident(
memset(priv, 0, priv_size)+ 1-byte bool write). Same class.Both noted in ADR-0869 §Consequences as follow-up audit candidates.