fix(gui): cap ipc_client frame reads at the service's 1 MiB frame cap
A torn stream could yield an arbitrary u32 length prefix, turning a re-dial into a multi-GiB allocation attempt. Mirror the service reader: len > MAX_FRAME is InvalidData, so the worker treats it as a dead connection and re-dials instead of OOMing.
This commit is contained in:
@@ -119,15 +119,29 @@ fn write_frame(stream: &mut UnixStream, body: &[u8]) -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Read one `[u32 LE len][body]` frame from the stream.
|
/// Read one `[u32 LE len][body]` frame from the stream.
|
||||||
fn read_frame(stream: &mut UnixStream) -> io::Result<Vec<u8>> {
|
///
|
||||||
|
/// Mirrors the service's reader: a length prefix above [`MAX_FRAME`] is a
|
||||||
|
/// protocol violation (`InvalidData`) rather than an allocation, so a torn
|
||||||
|
/// or hostile frame kills the connection (worker re-dials) instead of the
|
||||||
|
/// process.
|
||||||
|
fn read_frame(stream: &mut impl Read) -> io::Result<Vec<u8>> {
|
||||||
let mut len_buf = [0u8; 4];
|
let mut len_buf = [0u8; 4];
|
||||||
stream.read_exact(&mut len_buf)?;
|
stream.read_exact(&mut len_buf)?;
|
||||||
let len = u32::from_le_bytes(len_buf) as usize;
|
let len = u32::from_le_bytes(len_buf) as usize;
|
||||||
|
if len > MAX_FRAME {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
format!("frame {len} bytes exceeds {MAX_FRAME}"),
|
||||||
|
));
|
||||||
|
}
|
||||||
let mut buf = vec![0u8; len];
|
let mut buf = vec![0u8; len];
|
||||||
stream.read_exact(&mut buf)?;
|
stream.read_exact(&mut buf)?;
|
||||||
Ok(buf)
|
Ok(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 1 MiB cap on a single JSON frame, matching the service's own reader.
|
||||||
|
const MAX_FRAME: usize = 1 << 20;
|
||||||
|
|
||||||
/// The worker loop: dial, serve requests in order, redial forever.
|
/// The worker loop: dial, serve requests in order, redial forever.
|
||||||
fn worker(
|
fn worker(
|
||||||
socket: PathBuf,
|
socket: PathBuf,
|
||||||
@@ -196,3 +210,27 @@ fn worker(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_frame_accepts_a_valid_frame() {
|
||||||
|
let body = b"{\"ok\":true}";
|
||||||
|
let mut frame = Vec::new();
|
||||||
|
frame.extend_from_slice(&(body.len() as u32).to_le_bytes());
|
||||||
|
frame.extend_from_slice(body);
|
||||||
|
assert_eq!(read_frame(&mut &frame[..]).unwrap(), body);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_frame_rejects_an_oversized_len_prefix() {
|
||||||
|
// A torn stream could yield any u32; the cap keeps it from becoming
|
||||||
|
// an allocation.
|
||||||
|
let mut frame = ((MAX_FRAME as u32) + 1).to_le_bytes().to_vec();
|
||||||
|
frame.extend_from_slice(&[0u8; 8]);
|
||||||
|
let err = read_frame(&mut &frame[..]).unwrap_err();
|
||||||
|
assert_eq!(err.kind(), io::ErrorKind::InvalidData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user