feat: HDR Step-0 colour-metadata transport + security-audit hardening
ci / rust (push) Failing after 45s
apple / swift (push) Successful in 57s
ci / web (push) Successful in 39s
ci / docs-site (push) Successful in 38s
windows-host / package (push) Successful in 3m26s
android / android (push) Successful in 3m40s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m24s
deb / build-publish (push) Successful in 2m10s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m22s
decky / build-publish (push) Successful in 25s
ci / bench (push) Successful in 4m44s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 16s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m4s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m7s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 3m5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 2m45s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 30s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m37s
flatpak / build-publish (push) Successful in 4m17s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m30s
docker / deploy-docs (push) Successful in 23s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 7m53s

Two strands, entangled in punktfunk1.rs, committed together (one builds-green tree).

HDR pipeline Step 0 — glass-to-glass colour-metadata transport (docs/hdr-pipeline-plan.md):
- Protocol/ABI: ColorInfo on the Welcome + a 0xCE HdrMeta datagram carry the source colour
  space + HDR10 static mastering metadata (quic.rs, abi.rs connect_ex5 fixing caps=0).
- New platform-independent, unit-tested HDR static-metadata helpers (hdr.rs): chromaticities
  (1/50000), mastering luminance (0.0001 cd/m2), MaxCLL/MaxFALL in HDR10/ST.2086 units.
- Capture/encode hooks (capture.rs, encode.rs set_hdr_meta) + Linux client / probe plumbing.

Security-audit hardening — top 3 from docs/security-review.md, each adversarially verified:
- #1 [HIGH] Secret file permissions. The host key.pem/cert.pem and both trust stores are now
  written owner-only: 0600 + dir 0700 on Unix (mirrors mgmt_token), best-effort
  SYSTEM/Administrators/OWNER-only icacls DACL on Windows (%ProgramData% is Users-readable).
  Closes a local key-disclosure -> host-impersonation gap. New gamestream::{create_private_dir,
  write_secret_file} + a 0600 regression test.
- #2 [HIGH] Native SPAKE2 PIN is single-use. The PIN is consumed the moment the host sends its
  key-confirmation (which lets the client test its one guess), before reading the proof, so any
  completed attempt -- right OR wrong -- disarms the window. A wrong PIN isn't observable
  host-side (the client aborts before sending its proof), so consuming on first attempt is what
  delivers the documented "one online guess" instead of an unbounded brute-force of the static
  4-digit PIN. Test verifies single-use.
- #3 [MEDIUM] RTSP packetSize is bounded ([64,2048] in stream_config) and VideoPacketizer::new
  uses saturating .max(1), killing a PRE-AUTH div-by-zero/underflow panic of the video thread.
  Tests for {0,15,16,17} + out-of-range rejection.

fmt + clippy -D warnings clean; full workspace test suite green (93 host tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-21 09:07:59 +00:00
parent 22a9ce4229
commit 3526517eb1
26 changed files with 1916 additions and 77 deletions
+18 -1
View File
@@ -55,7 +55,12 @@ impl VideoPacketizer {
pub fn new(packet_size: usize, fec_percentage: u8, min_fec: u8) -> Self {
VideoPacketizer {
packet_size,
payload_per_shard: packet_size + 16 - SHARD_HEADER,
// Defense in depth: `pps` is a divisor in `packetize` (`% pps`, `div_ceil(pps)`), so it
// must never be 0. `blocksize = packet_size + 16`; a tiny attacker-supplied packet_size
// (≤ SHARD_HEADER-16 = 16) would otherwise underflow (panic) or yield pps==0 (div-by-zero).
// `stream_config` already rejects out-of-range packetSize; this saturating `.max(1)` makes
// a degenerate value structurally unable to panic, without affecting any valid size.
payload_per_shard: (packet_size + 16).saturating_sub(SHARD_HEADER).max(1),
fec_percentage: fec_percentage as usize,
min_fec: min_fec as usize,
frame_index: 0,
@@ -252,6 +257,18 @@ mod tests {
}
}
#[test]
fn degenerate_packet_size_does_not_panic() {
// A pre-auth attacker drives packetSize via the RTSP ANNOUNCE. `stream_config` rejects
// out-of-range values, but the packetizer must ALSO never panic (div-by-zero on `% pps` /
// `div_ceil(pps)`, or usize underflow) for ANY input — pps is clamped to >= 1.
for ps in [0usize, 15, 16, 17, 32] {
let mut pk = VideoPacketizer::new(ps, 20, 2);
assert!(pk.payload_per_shard >= 1, "pps must never be 0 (ps={ps})");
let _ = pk.packetize(&[0xCDu8; 200], FrameType::Idr, 0); // must not panic
}
}
#[test]
fn multi_block_split() {
let mut pk = VideoPacketizer::new(1392, 0, 0); // data-only