docs(host): prove unsafe blocks in the Windows + cross-platform files + gate them (unsafe-proof program 3/N)
Continues the unsafe-proof program across the Windows/cross-platform host files
(~75 blocks, 21 files), each with a SAFETY proof of the real invariant and a
per-file #![deny(clippy::undocumented_unsafe_blocks)] gate:
capture/windows: dxgi.rs, wgc_relay.rs, wgc.rs, desktop_watch.rs, composed_flip.rs
(windows-rs COM: interface validity, same-D3D11-device textures,
immediate-context single-thread, borrowed args outlive the call)
windows: service.rs (SCM/token/CreateProcessAsUserW/event handles — OwnedHandle
liveness, no double-close/signal race), win_display, wgc_helper, interactive
vdisplay/windows: manager.rs, pf_vdisplay.rs (SwDeviceCreate/IddCx/ioctl handle
liveness via the OnceLock VDM singleton + OwnedHandle)
encode/windows: ffmpeg_win.rs (full AVBufferRef refcount audit — balanced, NO leaks,
unlike the vaapi sibling), sw.rs
cross-platform: gamestream/audio.rs (libopus), gamestream/stream.rs (sendmmsg),
inject/windows/sendinput.rs, audio/windows/wasapi_mic.rs,
session_tuning.rs, vdisplay.rs
Two findings (handled separately):
- wgc_relay.rs `unsafe impl Sync for HelperRelay` is UNSOUND (its mpsc Receiver is
!Sync) though not live-exploited — marked SUSPECT inline; fix pending box check
(it touches the in-flight punktfunk1.rs).
- capture.rs / encode.rs (PARENT modules of the WIP idd_push.rs / nvenc.rs) do NOT
get the file deny yet — it would propagate the lint into the undocumented WIP
children. The deny lands there once those are documented (after the WIP commits).
Linux-visible parts verified green (cargo clippy -p punktfunk-host --all-targets
-- -D warnings). The cfg(windows) deny gates are box-verified next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,9 @@
|
||||
//! data packets are consumed immediately and missing parity only costs loss recovery — so
|
||||
//! the validated stereo path stays byte-identical (data packets only, exactly as before).
|
||||
|
||||
// Every `unsafe` block in this file carries a `// SAFETY:` proof; enforce it.
|
||||
#![deny(clippy::undocumented_unsafe_blocks)]
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "windows", test))]
|
||||
use crate::audio::SAMPLE_RATE;
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
@@ -409,7 +412,10 @@ struct MsEncoder {
|
||||
st: std::ptr::NonNull<audiopus_sys::OpusMSEncoder>,
|
||||
}
|
||||
|
||||
// The raw encoder state has no thread affinity; the session owns it on one thread at a time.
|
||||
// SAFETY: `MsEncoder` owns a unique `OpusMSEncoder` via `NonNull` (it is neither `Clone` nor
|
||||
// `Sync`, so the pointer is never aliased). libopus's multistream encoder state is a self-contained
|
||||
// heap allocation with no thread-local or thread-affine state, so moving ownership to another thread
|
||||
// is sound; every method takes `&mut self`, keeping access single-threaded at any instant.
|
||||
#[cfg(target_os = "linux")]
|
||||
unsafe impl Send for MsEncoder {}
|
||||
|
||||
@@ -418,6 +424,13 @@ impl MsEncoder {
|
||||
fn new(layout: &OpusLayout) -> Result<MsEncoder> {
|
||||
use std::os::raw::c_int;
|
||||
let mut err: c_int = 0;
|
||||
// SAFETY: every scalar arg is a valid libopus input (sample rate, channel/stream/coupled
|
||||
// counts, the RESTRICTED_LOWDELAY application constant). `layout.mapping.as_ptr()` addresses
|
||||
// a 'static slice of exactly `layout.channels` bytes (every `OpusLayout` constant upholds
|
||||
// that), which is the element count `opus_multistream_encoder_create` reads through it, and
|
||||
// `&mut err` is a live local the call writes its status into. libopus copies the mapping into
|
||||
// its own allocation, so the pointer need only be valid for the call; the returned pointer is
|
||||
// null/`OPUS_OK`-checked below before any use.
|
||||
let st = unsafe {
|
||||
audiopus_sys::opus_multistream_encoder_create(
|
||||
SAMPLE_RATE as i32,
|
||||
@@ -432,6 +445,11 @@ impl MsEncoder {
|
||||
let st = std::ptr::NonNull::new(st)
|
||||
.filter(|_| err == audiopus_sys::OPUS_OK)
|
||||
.ok_or_else(|| anyhow::anyhow!("opus_multistream_encoder_create failed ({err})"))?;
|
||||
// SAFETY: `st` is the non-null encoder `opus_multistream_encoder_create` just returned, owned
|
||||
// exclusively here. Each `opus_multistream_encoder_ctl` call passes a valid request constant
|
||||
// with the single by-value `c_int` argument that request's variadic ABI expects
|
||||
// (`OPUS_SET_BITRATE_REQUEST` → bitrate, `OPUS_SET_VBR_REQUEST` → 0). No pointer escapes the
|
||||
// call and the encoder outlives it.
|
||||
unsafe {
|
||||
audiopus_sys::opus_multistream_encoder_ctl(
|
||||
st.as_ptr(),
|
||||
@@ -453,6 +471,13 @@ impl MsEncoder {
|
||||
samples_per_channel: usize,
|
||||
out: &mut [u8],
|
||||
) -> Result<usize> {
|
||||
// SAFETY: `self.st` is the live encoder from `new`. libopus reads `samples_per_channel *
|
||||
// channels` f32s through `frame.as_ptr()`; every caller passes a `frame` of exactly that
|
||||
// length together with the matching `samples_per_channel` (`audio_body`'s `frame_len =
|
||||
// samples_per_channel * layout.channels`; the round-trip tests size identically), so the read
|
||||
// stays in bounds. `out.as_mut_ptr()` is written for at most `out.len()` bytes, which is
|
||||
// passed as the capacity bound. Both buffers are live locals outliving this synchronous call;
|
||||
// the return value is range-checked before being used as a length.
|
||||
let n = unsafe {
|
||||
audiopus_sys::opus_multistream_encode_float(
|
||||
self.st.as_ptr(),
|
||||
@@ -470,6 +495,9 @@ impl MsEncoder {
|
||||
#[cfg(target_os = "linux")]
|
||||
impl Drop for MsEncoder {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: `self.st` is the encoder `opus_multistream_encoder_create` returned; this
|
||||
// `MsEncoder` owns it uniquely and `drop` runs exactly once, so the destroy frees it once
|
||||
// with no subsequent use.
|
||||
unsafe { audiopus_sys::opus_multistream_encoder_destroy(self.st.as_ptr()) }
|
||||
}
|
||||
}
|
||||
@@ -761,6 +789,10 @@ mod tests {
|
||||
let client_mapping = client_swap(&digits[3..]);
|
||||
|
||||
let mut err = 0i32;
|
||||
// SAFETY: scalar args are valid libopus inputs. `client_mapping.as_ptr()` addresses a
|
||||
// `Vec<u8>` of exactly `ch` entries (derived from the advertised surround-params), which is
|
||||
// the element count the decoder reads through it, and `&mut err` is a live local the call
|
||||
// writes. The returned pointer is `OPUS_OK`/non-null-checked immediately below before use.
|
||||
let dec = unsafe {
|
||||
audiopus_sys::opus_multistream_decoder_create(
|
||||
SAMPLE_RATE as i32,
|
||||
@@ -789,6 +821,11 @@ mod tests {
|
||||
}
|
||||
let n = enc.encode_float(&frame, samples, &mut out).unwrap();
|
||||
assert!(n > 0);
|
||||
// SAFETY: `dec` is the non-null decoder asserted above. `out.as_ptr()` is read for
|
||||
// the `n` encoded bytes just produced by `encode_float`; `decoded.as_mut_ptr()` is
|
||||
// written for up to `samples * ch` f32s and `decoded` is exactly that long; `samples`
|
||||
// is the per-channel frame size. All buffers are live locals outliving the call; the
|
||||
// return is checked to equal `samples`.
|
||||
let got = unsafe {
|
||||
audiopus_sys::opus_multistream_decode_float(
|
||||
dec,
|
||||
@@ -817,6 +854,8 @@ mod tests {
|
||||
(energies: {energy:?})"
|
||||
);
|
||||
}
|
||||
// SAFETY: `dec` is the decoder `opus_multistream_decoder_create` returned; the test owns it
|
||||
// and destroys it exactly once here, after the final decode — no later use, no double free.
|
||||
unsafe { audiopus_sys::opus_multistream_decoder_destroy(dec) };
|
||||
}
|
||||
|
||||
@@ -853,6 +892,9 @@ mod tests {
|
||||
let digits: Vec<u8> = s.bytes().map(|b| b - b'0').collect();
|
||||
let client_mapping = client_swap(&digits[3..]);
|
||||
let mut err = 0i32;
|
||||
// SAFETY: scalar args are valid; `client_mapping.as_ptr()` addresses a 6-entry `Vec<u8>`
|
||||
// (matches the 6-channel layout the decoder reads through it), alive past the call, and
|
||||
// `&mut err` is a live local. The pointer is `OPUS_OK`-checked before use.
|
||||
let dec = unsafe {
|
||||
audiopus_sys::opus_multistream_decoder_create(
|
||||
48000,
|
||||
@@ -865,6 +907,10 @@ mod tests {
|
||||
};
|
||||
assert_eq!(err, audiopus_sys::OPUS_OK);
|
||||
let mut pcm = vec![0f32; 240 * 6];
|
||||
// SAFETY: `dec` is the non-null decoder from create. `out.as_ptr()` is read for the CBR
|
||||
// packet length passed in (`*sizes.first()`, a real encoded packet size in `out`);
|
||||
// `pcm.as_mut_ptr()` is written for up to `240 * 6` f32s and `pcm` is exactly that long;
|
||||
// `240` is the per-channel frame size. All buffers are live locals outliving the call.
|
||||
let got = unsafe {
|
||||
audiopus_sys::opus_multistream_decode_float(
|
||||
dec,
|
||||
@@ -875,6 +921,7 @@ mod tests {
|
||||
0,
|
||||
)
|
||||
};
|
||||
// SAFETY: `dec` is owned by the test; destroyed exactly once here after the final decode.
|
||||
unsafe { audiopus_sys::opus_multistream_decoder_destroy(dec) };
|
||||
assert_eq!(got, 240);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
//! either real portal desktop capture (`PUNKTFUNK_VIDEO_SOURCE=portal`, the portal PipeWire path) or
|
||||
//! a synthetic test pattern (default). Runs on its own native thread.
|
||||
|
||||
// Every `unsafe` block in this file carries a `// SAFETY:` proof; enforce it.
|
||||
#![deny(clippy::undocumented_unsafe_blocks)]
|
||||
|
||||
use super::video::{FrameType, VideoPacketizer};
|
||||
use super::VIDEO_PORT;
|
||||
use crate::capture::{self, Capturer, FastSyntheticCapturer};
|
||||
@@ -207,6 +210,10 @@ fn sendmmsg_all(sock: &UdpSocket, pkts: &[Vec<u8>]) -> std::io::Result<()> {
|
||||
let mut hdrs: Vec<libc::mmsghdr> = iovs
|
||||
.iter_mut()
|
||||
.map(|iov| {
|
||||
// SAFETY: `libc::mmsghdr` is a plain `#[repr(C)]` struct of integers and raw
|
||||
// pointers, for which an all-zero bit pattern is valid (null pointers / zero
|
||||
// lengths); the fields we rely on (`msg_iov`, `msg_iovlen`) are overwritten on the
|
||||
// next two lines before the struct is handed to the kernel.
|
||||
let mut h: libc::mmsghdr = unsafe { std::mem::zeroed() };
|
||||
h.msg_hdr.msg_iov = iov;
|
||||
h.msg_hdr.msg_iovlen = 1;
|
||||
@@ -215,6 +222,13 @@ fn sendmmsg_all(sock: &UdpSocket, pkts: &[Vec<u8>]) -> std::io::Result<()> {
|
||||
.collect();
|
||||
let mut off = 0usize;
|
||||
while off < hdrs.len() {
|
||||
// SAFETY: `fd` is `sock`'s live raw fd (`sock` outlives the call). `hdrs[off..]
|
||||
// .as_mut_ptr()` is a live slice of `(hdrs.len() - off)` `mmsghdr`s — exactly the count
|
||||
// passed — into which the kernel writes each `msg_len`. Each header's `msg_iov` points
|
||||
// into `iovs` (a local that outlives this call, with `msg_iovlen == 1` matching its one
|
||||
// entry) and each `iovec.iov_base` points into the `chunk` packet buffers (the caller's
|
||||
// `pkts`, alive for the call); the kernel only reads those payloads. Flags 0; the return
|
||||
// is error-/progress-checked before advancing `off`.
|
||||
let n = unsafe {
|
||||
libc::sendmmsg(fd, hdrs[off..].as_mut_ptr(), (hdrs.len() - off) as u32, 0)
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user