@@ -1,5 +1,5 @@ |
| 1 | 1 | use std::fs; |
| 2 | | -use std::io::{self, BufRead, BufReader, Write}; |
| 2 | +use std::io::{self, BufRead, BufReader, Read, Write}; |
| 3 | 3 | use std::os::unix::net::{UnixListener, UnixStream}; |
| 4 | 4 | use std::thread; |
| 5 | 5 | use std::time::{Duration, Instant}; |
@@ -19,6 +19,8 @@ use crate::runtime::RuntimePaths; |
| 19 | 19 | use crate::validate::{validate_request_id, validate_request_identity}; |
| 20 | 20 | use crate::window::parse_optional_parent_window; |
| 21 | 21 | |
| 22 | +const MAX_CONTROL_LINE_BYTES: usize = 4096; |
| 23 | + |
| 22 | 24 | pub fn run() -> io::Result<()> { |
| 23 | 25 | let config = Config::from_env(); |
| 24 | 26 | let paths = RuntimePaths::from_env(); |
@@ -110,7 +112,21 @@ struct DaemonState { |
| 110 | 112 | fn handle_connection(stream: UnixStream, state: &mut DaemonState) -> io::Result<()> { |
| 111 | 113 | let mut reader = BufReader::new(stream); |
| 112 | 114 | let mut line = String::new(); |
| 113 | | - reader.read_line(&mut line)?; |
| 115 | + let bytes_read = { |
| 116 | + let mut limited = reader.by_ref().take((MAX_CONTROL_LINE_BYTES + 1) as u64); |
| 117 | + limited.read_line(&mut line)? |
| 118 | + }; |
| 119 | + |
| 120 | + if bytes_read > MAX_CONTROL_LINE_BYTES { |
| 121 | + let mapping = map_portal_error(&PortalError::InvalidRequestPayload); |
| 122 | + return write_response( |
| 123 | + reader.into_inner(), |
| 124 | + ControlResponse::Error { |
| 125 | + code: mapping.code as u32, |
| 126 | + reason: mapping.reason.to_string(), |
| 127 | + }, |
| 128 | + ); |
| 129 | + } |
| 114 | 130 | |
| 115 | 131 | let response = match ControlRequest::parse_line(&line) { |
| 116 | 132 | Some(ControlRequest::Status) => ControlResponse::Status(StatusResponse { |
@@ -329,7 +345,9 @@ fn persist_registry_state(path: &std::path::Path, registry: &RequestRegistry) { |
| 329 | 345 | |
| 330 | 346 | #[cfg(test)] |
| 331 | 347 | mod tests { |
| 332 | | - use super::{DaemonState, handle_connection, load_registry_with_recovery}; |
| 348 | + use super::{ |
| 349 | + DaemonState, MAX_CONTROL_LINE_BYTES, handle_connection, load_registry_with_recovery, |
| 350 | + }; |
| 333 | 351 | use garwarp_ipc::{ControlResponse, HealthStatus}; |
| 334 | 352 | use std::fs; |
| 335 | 353 | use std::io::{BufRead, BufReader, Write}; |
@@ -499,6 +517,36 @@ mod tests { |
| 499 | 517 | ); |
| 500 | 518 | } |
| 501 | 519 | |
| 520 | + #[test] |
| 521 | + fn oversized_request_maps_to_invalid_request() { |
| 522 | + let (mut client, server) = UnixStream::pair().expect("pair should be created"); |
| 523 | + let oversized = format!("{}\n", "x".repeat(MAX_CONTROL_LINE_BYTES + 1)); |
| 524 | + client |
| 525 | + .write_all(oversized.as_bytes()) |
| 526 | + .expect("oversized request should be written"); |
| 527 | + |
| 528 | + let mut state = DaemonState { |
| 529 | + health: HealthStatus::Healthy, |
| 530 | + requests: RequestRegistry::new(Duration::from_secs(5)), |
| 531 | + running: true, |
| 532 | + }; |
| 533 | + handle_connection(server, &mut state).expect("request should be handled"); |
| 534 | + |
| 535 | + let mut response_line = String::new(); |
| 536 | + let mut reader = BufReader::new(client); |
| 537 | + reader |
| 538 | + .read_line(&mut response_line) |
| 539 | + .expect("response should be readable"); |
| 540 | + let response = ControlResponse::parse_line(&response_line).expect("response should parse"); |
| 541 | + assert_eq!( |
| 542 | + response, |
| 543 | + ControlResponse::Error { |
| 544 | + code: 2, |
| 545 | + reason: "invalid_request".to_string(), |
| 546 | + } |
| 547 | + ); |
| 548 | + } |
| 549 | + |
| 502 | 550 | #[test] |
| 503 | 551 | fn duplicate_request_fields_map_to_invalid_request() { |
| 504 | 552 | let (mut client, server) = UnixStream::pair().expect("pair should be created"); |