Security phase 1: sealed remote control + service-folder lockdown + counter nonces
From the 2026-07-26 security audit, the two must-fix findings plus the crypto tidy: 1. Remote-control commands are now SEALED with the profile's audio key (AES-256-GCM, ControlSealing in Core). Previously a 2-byte plaintext payload gated only by a forgeable source-IP check - anyone who learned an allowed peer's address could drive the receiving machine's SYSTEM volume/mute, and muting a blind user's machine mutes their screen reader. Now only a password-holder can issue a command; a ControlReceiveGuard also bounds clock skew (10 min) and remembers nonces so a captured packet can't be replayed to re-toggle mute. Legacy plaintext control from pre-5.6 peers is dropped at the receiver (never acted on) - release notes must say both ends need 5.6 for remote volume. 2. Cross-user LPE closed: the SYSTEM service trusts app-source.txt to decide what to copy+run on self-update, and ProgramData lets any user who pre-created the service folder own it (CREATOR OWNER inheritance) and repoint that file. Elevated install now records the installing user's SID, takes ownership for Administrators and resets the ACL to exactly SYSTEM + Administrators + installing user (takeown + icacls /inheritance:r). Re-asserted on every SYSTEM self-update so existing installs pick it up; as SYSTEM with no recorded SID it defers rather than lock the user out of their no-admin workflow. 3. Audio-path GCM nonces are now counter-based per lane (random 4-byte prefix + 64-bit counter, fresh sequence with every key rebuild) - unique by arithmetic, removing the random-nonce birthday bound on a long-lived key. Wire format unchanged; the receiver reads the nonce from the packet as before. New gate steps: sealed-control auth/replay/stale/wrong-key/plaintext matrix + nonce discipline; service-folder lockdown args + SID recording garbage-proofing. Gate 63/63. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -46,6 +46,7 @@ internal sealed class SenderLane
|
||||
// cipherScratch holds the per-frame ciphertext (plaintext + 28 bytes overhead); 4096 covers
|
||||
// the largest single frame (Opus 20 ms or PCM 5 ms) with room to spare.
|
||||
private AesGcm? cryptoGcm;
|
||||
private RemSoundCrypto.NonceSequence? cryptoNonces;
|
||||
private byte[]? cryptoKeyCached;
|
||||
private readonly byte[] cipherScratch = new byte[4096];
|
||||
|
||||
@@ -317,7 +318,7 @@ internal sealed class SenderLane
|
||||
// Encrypt the whole PCM frame, then split the ciphertext across as many parts as the
|
||||
// Ethernet payload budget needs (the +28-byte crypto overhead can push a 5 ms frame over
|
||||
// a single datagram). The receiver reassembles the parts and then decrypts.
|
||||
var ctLen = RemSoundCrypto.EncryptInto(cryptoGcm, int24, cipherScratch);
|
||||
var ctLen = RemSoundCrypto.EncryptInto(cryptoGcm, cryptoNonces!, int24, cipherScratch);
|
||||
var maxPart = RemPacket.MaxAudioPayloadBytes;
|
||||
var totalParts = (byte)((ctLen + maxPart - 1) / maxPart);
|
||||
pcmFrameId++;
|
||||
@@ -353,7 +354,7 @@ internal sealed class SenderLane
|
||||
}
|
||||
EnsureCrypto();
|
||||
if (cryptoGcm is null) return; // no password yet → never send audio in the clear
|
||||
var ctLen = RemSoundCrypto.EncryptInto(cryptoGcm, opusPlainScratch.AsSpan(0, encLen), cipherScratch);
|
||||
var ctLen = RemSoundCrypto.EncryptInto(cryptoGcm, cryptoNonces!, opusPlainScratch.AsSpan(0, encLen), cipherScratch);
|
||||
Interlocked.Increment(ref audioFramesSent);
|
||||
SendAudio(cipherScratch.AsSpan(0, ctLen));
|
||||
}
|
||||
@@ -403,6 +404,9 @@ internal sealed class SenderLane
|
||||
if (ReferenceEquals(key, cryptoKeyCached)) return;
|
||||
cryptoGcm?.Dispose();
|
||||
cryptoGcm = key is null ? null : RemSoundCrypto.CreateGcm(key);
|
||||
// Fresh nonce sequence with the fresh cipher: new random prefix, counter from zero —
|
||||
// a rebuilt key never continues an old counter, and an old key never sees a reused one.
|
||||
cryptoNonces = key is null ? null : new RemSoundCrypto.NonceSequence();
|
||||
cryptoKeyCached = key;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user