docs(unsafe): audit all 49 unsafe impl — one proof was wrong, four were missing
windows / build (aarch64-pc-windows-msvc) (push) Canceled after 0s
windows / build (x86_64-pc-windows-msvc) (push) Canceled after 0s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Canceled after 0s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Canceled after 0s
windows-host / package (push) Canceled after 0s
windows-host / winget-source (push) Canceled after 0s
windows-drivers / probe-and-proto (push) Canceled after 0s
windows-drivers / driver-build (push) Canceled after 0s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 0s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 0s
flatpak / build-publish (push) Canceled after 0s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Canceled after 0s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 0s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / build-push-arm64cross (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
decky / build-publish (push) Canceled after 0s
deb / build-publish (push) Canceled after 0s
deb / build-publish-host (push) Canceled after 0s
deb / build-publish-client-arm64 (push) Canceled after 0s
ci / rust (push) Canceled after 0s
ci / rust-arm64 (push) Canceled after 0s
ci / web (push) Canceled after 0s
ci / docs-site (push) Canceled after 0s
ci / bench (push) Canceled after 0s
arch / build-publish (push) Canceled after 0s
apple / swift (push) Canceled after 0s
apple / screenshots (push) Canceled after 0s
android / android (push) Canceled after 0s
windows / build (aarch64-pc-windows-msvc) (push) Canceled after 0s
windows / build (x86_64-pc-windows-msvc) (push) Canceled after 0s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Canceled after 0s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Canceled after 0s
windows-host / package (push) Canceled after 0s
windows-host / winget-source (push) Canceled after 0s
windows-drivers / probe-and-proto (push) Canceled after 0s
windows-drivers / driver-build (push) Canceled after 0s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 0s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 0s
flatpak / build-publish (push) Canceled after 0s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Canceled after 0s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 0s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / build-push-arm64cross (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
decky / build-publish (push) Canceled after 0s
deb / build-publish (push) Canceled after 0s
deb / build-publish-host (push) Canceled after 0s
deb / build-publish-client-arm64 (push) Canceled after 0s
ci / rust (push) Canceled after 0s
ci / rust-arm64 (push) Canceled after 0s
ci / web (push) Canceled after 0s
ci / docs-site (push) Canceled after 0s
ci / bench (push) Canceled after 0s
arch / build-publish (push) Canceled after 0s
apple / swift (push) Canceled after 0s
apple / screenshots (push) Canceled after 0s
android / android (push) Canceled after 0s
`unsafe impl Send`/`Sync` is the highest-risk unsafe category here and the one this program had never looked at: a wrong one is cross-thread UB that is invisible at every call site, with no `unsafe` block to catch a reviewer's eye. 49 of them (41 Send, 8 Sync). Two results. **`MappedView`'s `Sync` proof was factually wrong.** It read "only exposes accessors that are safe under concurrent use" — they are not. `read_u8`/`write_u8`/`read_u16` are plain unaligned accesses through `&self`, and `&MappedView` really is shared across threads: `ChannelState::data()` hands out `&'static MappedView`, and pf-xusb, pf-mouse and pf-gamepad all dispatch `WdfIoQueueDispatchParallel` with `NumberOfPresentedRequests = u32::MAX`. The struct's own doc had the right story — consistency is the channel protocol's job — but the `unsafe impl` stated a different, stronger claim, which is the one a reviewer checking that line would rely on. The impl is still sound, for a reason worth writing down: these bytes are mapped into ANOTHER PROCESS that writes them concurrently, so Rust-level exclusivity over them is unachievable no matter what this type does. Sync fields go through the atomic accessors; the plain ones cover only protocol-fenced bytes. The proof now says that, and states the rule it implies for accessors added later — plain path only for bytes the protocol already fences. **Four `Send` impls carried a one-line aside instead of a proof** — the pf-client-core decoders and `DrmFrameGuard`. All four are sound, and each now says why, including the two facts that make them work and were nowhere stated: libav permits a codec context to be used from a thread other than its creator provided use is serialised (`&mut self` is that serialisation), and D3D11's immediate context is thread-AGNOSTIC rather than thread-safe — it wants serialised use, not one fixed thread. Each also records that it is deliberately not `Sync`, which is the invariant a future `impl Sync` would silently break. Every one of the 49 now carries reasoning. Verified: Linux .21 fmt + both CI clippy steps rc=0; Windows .47 `-p pf-client-core` clippy `-D warnings` rc=0 (it compiles the `video_d3d11` proof the Linux run cannot see). The pf-umdf-util edit is comment-only — confirmed by diff, since that crate needs the WDK, which .47 does not have.
This commit is contained in:
@@ -222,7 +222,11 @@ pub struct DmabufPlane {
|
||||
/// Owns the mapped DRM-PRIME `AVFrame` (which in turn references the VAAPI surface).
|
||||
/// Dropping it releases the surface back to the decoder pool and closes the fds.
|
||||
pub struct DrmFrameGuard(pub(crate) *mut ffmpeg::ffi::AVFrame);
|
||||
// An AVFrame is plain refcounted data; freeing it from the GTK main thread is fine.
|
||||
// SAFETY: the guard owns one `AVFrame` and frees it exactly once in `Drop`. libav's buffer
|
||||
// refcounts are atomic and its hwframe pool is internally locked, so releasing the frame — and with
|
||||
// it the VAAPI surface, back to the decoder's pool — from a different thread than the one that
|
||||
// mapped it is sound. That is the whole point here: the guard is handed to GTK and dropped on the
|
||||
// main thread while the pump thread keeps decoding. Moved, never shared; deliberately NOT `Sync`.
|
||||
unsafe impl Send for DrmFrameGuard {}
|
||||
|
||||
impl Drop for DrmFrameGuard {
|
||||
|
||||
@@ -492,8 +492,12 @@ pub(crate) struct D3d11vaDecoder {
|
||||
hdr10_out: bool,
|
||||
}
|
||||
|
||||
// Single-owner pointers + COM interfaces, only touched from the session pump thread (the
|
||||
// decode loop); the presenter reaches the shared textures exclusively via their NT handles.
|
||||
// SAFETY: the libav pointers are this decoder's own allocations (freed once in `Drop`) and the COM
|
||||
// interfaces it holds are reference-counted with interlocked counts, so moving the whole struct to
|
||||
// another thread and releasing it there is sound. D3D11's immediate context is not thread-SAFE but
|
||||
// it is thread-AGNOSTIC: it requires serialised use, which `&mut self` on every method gives, not
|
||||
// use from one fixed thread. The presenter never touches these objects — it reaches the shared
|
||||
// textures through their NT handles on its own device. Moved, never shared; deliberately NOT `Sync`.
|
||||
unsafe impl Send for D3d11vaDecoder {}
|
||||
|
||||
impl D3d11vaDecoder {
|
||||
|
||||
@@ -45,7 +45,13 @@ pub(crate) struct VaapiDecoder {
|
||||
frame: *mut ffmpeg::ffi::AVFrame,
|
||||
}
|
||||
|
||||
// Single-owner pointers, only touched from the session pump thread.
|
||||
// SAFETY: the three raw pointers (`ctx`, `packet`, `frame`) are allocations this decoder makes in
|
||||
// its constructor and frees exactly once in `Drop`; nothing else holds them, and `hw_device` is an
|
||||
// owning `AvBuffer` whose refcount is atomic. `Send` only permits MOVING that ownership to another
|
||||
// thread, which libav supports — a codec context may be used from a thread other than the one that
|
||||
// created it, provided use is serialised, and `&mut self` on every method is that serialisation.
|
||||
// Deliberately NOT `Sync`: two threads holding `&VaapiDecoder` could call into libav concurrently
|
||||
// on one context, which libav does not allow.
|
||||
#[cfg(target_os = "linux")]
|
||||
unsafe impl Send for VaapiDecoder {}
|
||||
|
||||
|
||||
@@ -39,7 +39,12 @@ pub(crate) struct VulkanDecoder {
|
||||
_ctx_storage: Box<VkCtxStorage>,
|
||||
}
|
||||
|
||||
// Single-owner pointers, only touched from the session pump thread.
|
||||
// SAFETY: `ctx`/`packet`/`frame` are allocations this decoder owns from its constructor to `Drop`,
|
||||
// `hw_device` is an owning `AvBuffer` (atomic refcount), and `_ctx_storage` is a `Box` that merely
|
||||
// has to outlive them. `Send` only moves that ownership between threads, which libav permits for a
|
||||
// codec context used serially — `&mut self` on every method provides that. The presenter reaches
|
||||
// decoded images through the `AVFrame` guard's own references and the shared `QueueLock`, not
|
||||
// through this struct. Deliberately NOT `Sync`.
|
||||
unsafe impl Send for VulkanDecoder {}
|
||||
|
||||
struct VkCtxStorage {
|
||||
|
||||
Reference in New Issue
Block a user