feat(ffi): hand-mirrored C structs are now layout-checked at compile time
The sharpest memory-safety risk left in this codebase is not an `unsafe` block — it is a hand-written `#[repr(C)]` mirror of an external C struct. Get a field offset wrong and nothing fails to compile and nothing reliably crashes: the library reads a pointer, a length or a pitch out of the wrong bytes. Eleven such structs across five files had NO check at all. Guarded here, each next to the struct it protects: * `AVCUDADeviceContext`, and `AVD3D11VADeviceContext`/`AVD3D11VAFramesContext`. `ffmpeg-sys-next` binds none of them, so these mirrors are the only definitions — and we WRITE through them (`cuda_ctx`, `device`, `bind_flags`). ⚠ The D3D11VA pair is duplicated VERBATIM in two crates (pf-encode's `ffmpeg_win.rs`, pf-client-core's `video_d3d11.rs`) because neither can depend on the other; they must agree with libav and with each other, and now a drift in either is a build error. * The six cuda.h structs. Three were already asserted — but only in `#[cfg(test)]`, so the check ran when someone ran the tests and never in a release build. They are `const` now. The other three, including `CUDA_MEMCPY2D` which is filled on EVERY zero-copy frame, had nothing. * `MsghdrX`, Darwin's `msghdr_x`, which `libc` does not expose. Its layout is not reviewable by eye: the 32-bit fields force padding before each following pointer, so `msg_iov` sits at 16 and not 12. `sendmsg_x`/`recvmsg_x` take the pointer and length from it. * `IPolicyConfigVtbl` — the sharpest of the set. It mirrors an UNDOCUMENTED COM interface, and `set_default_endpoint` is called by SLOT INDEX through a ten-entry `_reserved` gap that carries no names to anchor a review. A field added or resized above it does not break the build; it calls a different function pointer through a mismatched signature. Every assertion is `const _: () = assert!(..)`, so it holds on every build including release and cannot be skipped. The compiler verified the numbers — the sizes and offsets asserted here are the ones the target actually produces, on each platform that compiles the struct. Verified: Linux .21 fmt + both CI clippy steps rc=0 (CUDA + libav CUDA mirrors); Windows .47 full CI clippy set rc=0 + pf-capture tests (D3D11VA pair, COM vtable); macOS `cargo check -p punktfunk-core` (MsghdrX — the only platform that compiles it).
This commit is contained in:
@@ -58,6 +58,19 @@ struct AVCUDADeviceContext {
|
||||
internal: *mut std::ffi::c_void, // filled by ctx_init
|
||||
}
|
||||
|
||||
// A hand-written mirror of libav's `AVCUDADeviceContext` (hwcontext_cuda.h) — `ffmpeg-sys-next`
|
||||
// does not bind it, so this is the only definition, and `CudaHw::new` WRITES `cuda_ctx` through it.
|
||||
// A wrong offset here would not fail to compile or crash; it would scribble over libav's internal
|
||||
// pointer. Nothing checked that until this block: the realistic failure is someone adding or
|
||||
// reordering a field here, which these assertions turn into a build error.
|
||||
const _: () = {
|
||||
use std::mem::{offset_of, size_of};
|
||||
assert!(size_of::<AVCUDADeviceContext>() == 3 * size_of::<*mut std::ffi::c_void>());
|
||||
assert!(offset_of!(AVCUDADeviceContext, cuda_ctx) == 0);
|
||||
assert!(offset_of!(AVCUDADeviceContext, stream) == size_of::<*mut std::ffi::c_void>());
|
||||
assert!(offset_of!(AVCUDADeviceContext, internal) == 2 * size_of::<*mut std::ffi::c_void>());
|
||||
};
|
||||
|
||||
/// CUDA hardware-frame contexts that wrap our shared `CUcontext`, so `hevc_nvenc` reads the
|
||||
/// imported device buffer directly. Owns two `AVBufferRef`s, unref'd on drop.
|
||||
struct CudaHw {
|
||||
|
||||
@@ -92,6 +92,32 @@ struct AVD3D11VAFramesContext {
|
||||
texture_infos: *mut c_void, // AVD3D11FrameDescriptor* (FFmpeg-owned; we never touch it)
|
||||
}
|
||||
|
||||
// Hand-written mirrors of libav's `AVD3D11VADeviceContext` / `AVD3D11VAFramesContext`
|
||||
// (hwcontext_d3d11va.h) — `ffmpeg-sys-next` binds neither, and we WRITE `device` / `bind_flags`
|
||||
// through them, so a wrong offset is silent corruption of libav's context rather than a compile
|
||||
// error. ⚠ These two structs are duplicated in the other crate that talks to the same libav
|
||||
// contexts (pf-encode's `ffmpeg_win.rs` and pf-client-core's `video_d3d11.rs`); they must agree
|
||||
// with libav AND with each other, and these assertions are what makes a drift in either a build
|
||||
// failure instead of a runtime mystery.
|
||||
const _: () = {
|
||||
use std::mem::{offset_of, size_of};
|
||||
type P = *mut c_void;
|
||||
assert!(size_of::<AVD3D11VADeviceContext>() == 7 * size_of::<P>());
|
||||
assert!(offset_of!(AVD3D11VADeviceContext, device) == 0);
|
||||
assert!(offset_of!(AVD3D11VADeviceContext, device_context) == size_of::<P>());
|
||||
assert!(offset_of!(AVD3D11VADeviceContext, video_device) == 2 * size_of::<P>());
|
||||
assert!(offset_of!(AVD3D11VADeviceContext, video_context) == 3 * size_of::<P>());
|
||||
assert!(offset_of!(AVD3D11VADeviceContext, lock) == 4 * size_of::<P>());
|
||||
assert!(offset_of!(AVD3D11VADeviceContext, unlock) == 5 * size_of::<P>());
|
||||
assert!(offset_of!(AVD3D11VADeviceContext, lock_ctx) == 6 * size_of::<P>());
|
||||
// ptr, u32, u32, ptr — the two 32-bit flags pack into one pointer-sized slot with no padding.
|
||||
assert!(size_of::<AVD3D11VAFramesContext>() == 3 * size_of::<P>());
|
||||
assert!(offset_of!(AVD3D11VAFramesContext, texture) == 0);
|
||||
assert!(offset_of!(AVD3D11VAFramesContext, bind_flags) == size_of::<P>());
|
||||
assert!(offset_of!(AVD3D11VAFramesContext, misc_flags) == size_of::<P>() + 4);
|
||||
assert!(offset_of!(AVD3D11VAFramesContext, texture_infos) == 2 * size_of::<P>());
|
||||
};
|
||||
|
||||
/// AMD AMF vs Intel QSV — the two libavcodec vendor backends this module covers.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum WinVendor {
|
||||
|
||||
Reference in New Issue
Block a user