From f4f438f688369dcb94360a78b6d2855438552f87 Mon Sep 17 00:00:00 2001 From: M1noa Date: Thu, 16 Jul 2026 17:34:50 -0500 Subject: [PATCH] fix(LCEL-04): bind relay to 127.0.0.1 + cap read_line at 8 KiB two changes: 1. run_relay_proxy bound TcpListener to 0.0.0.0:61000. any LAN host could connect to this port and their bytes were piped to the upstream relay - no authentication of the local peer. changed to 127.0.0.1:61000 since the game process connects on loopback. 2. read_line read byte-by-byte into a Vec with no size cap. a malicious peer (relay or LAN) sending an unbounded line without \n could OOM the launcher. capped at 8 KiB (relay control lines are tiny - HOST/JOIN tokens + small id). --- src-tauri/src/networking/relay.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src-tauri/src/networking/relay.rs b/src-tauri/src/networking/relay.rs index f4773c5..878010f 100644 --- a/src-tauri/src/networking/relay.rs +++ b/src-tauri/src/networking/relay.rs @@ -6,6 +6,10 @@ use tokio::time::sleep; use tokio_util::sync::CancellationToken; use crate::state::ProxyGuard; const PROXY_ADDR: &str = "proxy.mclegacyedition.xyz:2052"; //neo: yeah bro im hardcoding it +// security: cap read_line at 8 KiB. relay control lines are tiny (HOST/JOIN +// tokens + small id). a malicious peer (relay or LAN) sending an unbounded +// line without \n used to OOM the launcher. (LCEL-04) +const MAX_RELAY_LINE_BYTES: usize = 8 * 1024; async fn read_line(stream: &mut TcpStream) -> Result { let mut buf = Vec::new(); let mut byte = [0u8; 1]; @@ -13,6 +17,9 @@ async fn read_line(stream: &mut TcpStream) -> Result { stream.read_exact(&mut byte).await.map_err(|e| e.to_string())?; if byte[0] == b'\n' { break; } if byte[0] != b'\r' { buf.push(byte[0]); } + if buf.len() > MAX_RELAY_LINE_BYTES { + return Err(format!("relay line exceeded {} bytes", MAX_RELAY_LINE_BYTES)); + } } String::from_utf8(buf).map_err(|e| e.to_string()) } @@ -111,7 +118,11 @@ async fn run_relay_proxy( .map_err(|e| format!("Proxy connect failed: {}", e))?; write_line(&mut stream, &format!("JOIN {} 0 {}", auth_token, target_session)).await?; - let listener = TcpListener::bind("0.0.0.0:61000") + // security: bind to loopback only. the game process connects on + // 127.0.0.1:61000; binding 0.0.0.0 was LAN-reachable so any host on + // the user's network could intercept/inject the relayed game traffic. + // (LCEL-04) + let listener = TcpListener::bind("127.0.0.1:61000") .await .map_err(|e| format!("Bind failed: {}", e))?; let local_port = listener.local_addr().map_err(|e| e.to_string())?.port();