fix(servo): bound live request frames

This commit is contained in:
2026-07-09 23:44:27 -04:00
parent 83badaea81
commit 58e9bd7d38
11 changed files with 244 additions and 17 deletions
+3 -1
View File
@@ -248,7 +248,9 @@ impl ServoLiveClient {
let reply = match self.ipc.exchange(request, timeout, operation) {
Ok(reply) => reply,
Err(error) => {
self.terminate();
if !error.sidecar_remains_available() {
self.terminate();
}
return Err(error);
}
};
+47 -4
View File
@@ -10,7 +10,7 @@ use super::{
ServoLiveError, ServoLiveFrame, ServoLivePermissionGrant,
wire::{
LiveRequest, LiveResponse, LiveSurfaceHandle, MAX_FRAME_BYTE_COUNT, MAX_FRAME_DIMENSION,
MAX_RESPONSE_HEADER_BYTES,
MAX_REQUEST_LINE_BYTES, MAX_RESPONSE_HEADER_BYTES,
},
};
@@ -82,7 +82,8 @@ fn run_io(
while let Ok(message) = requests.recv() {
let should_shutdown = matches!(message.request, LiveRequest::Shutdown);
let response = exchange_pipes(&mut stdin, &mut stdout, &message.request);
let should_stop = should_shutdown || response.is_err();
let should_stop = should_shutdown
|| response.as_ref().is_err_and(|error| !error.sidecar_remains_available());
let _ = message.response.send(response);
if should_stop {
break;
@@ -95,12 +96,22 @@ fn exchange_pipes(
stdout: &mut BufReader<ChildStdout>,
request: &LiveRequest,
) -> Result<IpcReply, ServoLiveError> {
serde_json::to_writer(&mut *stdin, request)?;
stdin.write_all(b"\n").map_err(ServoLiveError::Command)?;
let line = serialize_request_line(request)?;
stdin.write_all(&line).map_err(ServoLiveError::Command)?;
stdin.flush().map_err(ServoLiveError::Command)?;
read_reply(stdout)
}
fn serialize_request_line(request: &LiveRequest) -> Result<Vec<u8>, ServoLiveError> {
let mut line = serde_json::to_vec(request)?;
let bytes = line.len().saturating_add(1);
if bytes > MAX_REQUEST_LINE_BYTES {
return Err(ServoLiveError::RequestLineTooLarge { bytes, limit: MAX_REQUEST_LINE_BYTES });
}
line.push(b'\n');
Ok(line)
}
fn read_reply(stdout: &mut impl BufRead) -> Result<IpcReply, ServoLiveError> {
let mut line = String::new();
let bytes = Read::by_ref(stdout)
@@ -280,6 +291,38 @@ mod tests {
));
}
#[test]
fn request_line_preflight_accepts_the_limit_and_rejects_limit_plus_one()
-> Result<(), ServoLiveError> {
let empty = LiveRequest::Close { tab_id: String::new() };
let fixed_bytes = serde_json::to_vec(&empty)?.len();
let exact =
LiveRequest::Close { tab_id: "a".repeat(MAX_REQUEST_LINE_BYTES - fixed_bytes - 1) };
let exact_line = serialize_request_line(&exact)?;
assert_eq!(exact_line.len(), MAX_REQUEST_LINE_BYTES);
let overflow =
LiveRequest::Close { tab_id: "a".repeat(MAX_REQUEST_LINE_BYTES - fixed_bytes) };
let error = match serialize_request_line(&overflow) {
Err(error) => error,
Ok(_) => {
return Err(ServoLiveError::InvalidResponse {
message: "limit-plus-one request unexpectedly passed preflight",
});
}
};
assert!(matches!(
&error,
ServoLiveError::RequestLineTooLarge {
bytes,
limit: MAX_REQUEST_LINE_BYTES,
} if *bytes == MAX_REQUEST_LINE_BYTES + 1
));
assert!(error.sidecar_remains_available());
Ok(())
}
#[test]
fn reply_parses_permission_consumption_without_a_frame() -> Result<(), ServoLiveError> {
let profile_id = ely_domain::ProfileId::new();
@@ -317,6 +317,9 @@ pub(crate) enum ServoLiveError {
#[error("servo live sidecar failed: {message}")]
SidecarFailed { message: String },
#[error("servo live request requires {bytes} bytes; the line limit is {limit}")]
RequestLineTooLarge { bytes: usize, limit: usize },
#[error("servo live sidecar response header exceeded {limit} bytes")]
ResponseHeaderTooLarge { limit: usize },
@@ -375,6 +378,10 @@ pub(crate) enum ServoLiveError {
}
impl ServoLiveError {
pub(crate) fn sidecar_remains_available(&self) -> bool {
matches!(self, Self::RequestLineTooLarge { .. })
}
pub(crate) fn is_runtime_unavailable(&self) -> bool {
match self {
Self::SidecarExited
@@ -403,6 +410,7 @@ impl ServoLiveError {
Self::SidecarBinaryUnavailable { .. }
| Self::PipeUnavailable { .. }
| Self::SidecarFailed { .. }
| Self::RequestLineTooLarge { .. }
| Self::SidecarCommand(_) => false,
}
}
@@ -6,6 +6,7 @@ pub(super) const LIVE_PROTOCOL_VERSION: u32 = 3;
pub(super) const MAX_FRAME_DIMENSION: u32 = 16_384;
pub(super) const MAX_FRAME_BYTE_COUNT: usize = 256 * 1024 * 1024;
pub(super) const MAX_RESPONSE_HEADER_BYTES: usize = 256 * 1024;
pub(super) const MAX_REQUEST_LINE_BYTES: usize = 1024 * 1024;
#[derive(Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
@@ -110,7 +111,14 @@ fn default_device_pixel_ratio() -> f32 {
mod tests {
use serde_json::json;
use super::{LIVE_PROTOCOL_VERSION, LiveRequest, ServoLiveSitePermission};
use super::{
LIVE_PROTOCOL_VERSION, LiveRequest, MAX_REQUEST_LINE_BYTES, ServoLiveSitePermission,
};
#[test]
fn request_line_limit_matches_the_host_protocol() {
assert_eq!(MAX_REQUEST_LINE_BYTES, ely_servo_host::MAX_REQUEST_LINE_BYTES);
}
#[test]
fn handshake_request_serializes_protocol_version() -> Result<(), serde_json::Error> {