@@ -1,5 +1,5 @@ |
| 1 | use std::fs; | 1 | use std::fs; |
| 2 | -use std::io::{self, BufRead, BufReader, Write}; | 2 | +use std::io::{self, BufRead, BufReader, Read, Write}; |
| 3 | use std::os::unix::net::{UnixListener, UnixStream}; | 3 | use std::os::unix::net::{UnixListener, UnixStream}; |
| 4 | use std::thread; | 4 | use std::thread; |
| 5 | use std::time::{Duration, Instant}; | 5 | use std::time::{Duration, Instant}; |
@@ -19,6 +19,8 @@ use crate::runtime::RuntimePaths; |
| 19 | use crate::validate::{validate_request_id, validate_request_identity}; | 19 | use crate::validate::{validate_request_id, validate_request_identity}; |
| 20 | use crate::window::parse_optional_parent_window; | 20 | use crate::window::parse_optional_parent_window; |
| 21 | | 21 | |
| | 22 | +const MAX_CONTROL_LINE_BYTES: usize = 4096; |
| | 23 | + |
| 22 | pub fn run() -> io::Result<()> { | 24 | pub fn run() -> io::Result<()> { |
| 23 | let config = Config::from_env(); | 25 | let config = Config::from_env(); |
| 24 | let paths = RuntimePaths::from_env(); | 26 | let paths = RuntimePaths::from_env(); |
@@ -110,7 +112,21 @@ struct DaemonState { |
| 110 | fn handle_connection(stream: UnixStream, state: &mut DaemonState) -> io::Result<()> { | 112 | fn handle_connection(stream: UnixStream, state: &mut DaemonState) -> io::Result<()> { |
| 111 | let mut reader = BufReader::new(stream); | 113 | let mut reader = BufReader::new(stream); |
| 112 | let mut line = String::new(); | 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 | let response = match ControlRequest::parse_line(&line) { | 131 | let response = match ControlRequest::parse_line(&line) { |
| 116 | Some(ControlRequest::Status) => ControlResponse::Status(StatusResponse { | 132 | Some(ControlRequest::Status) => ControlResponse::Status(StatusResponse { |
@@ -329,7 +345,9 @@ fn persist_registry_state(path: &std::path::Path, registry: &RequestRegistry) { |
| 329 | | 345 | |
| 330 | #[cfg(test)] | 346 | #[cfg(test)] |
| 331 | mod tests { | 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 | use garwarp_ipc::{ControlResponse, HealthStatus}; | 351 | use garwarp_ipc::{ControlResponse, HealthStatus}; |
| 334 | use std::fs; | 352 | use std::fs; |
| 335 | use std::io::{BufRead, BufReader, Write}; | 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 | #[test] | 550 | #[test] |
| 503 | fn duplicate_request_fields_map_to_invalid_request() { | 551 | fn duplicate_request_fields_map_to_invalid_request() { |
| 504 | let (mut client, server) = UnixStream::pair().expect("pair should be created"); | 552 | let (mut client, server) = UnixStream::pair().expect("pair should be created"); |