From 9e252cfb8d6af2e1dd5c91d61c1357456411b84d Mon Sep 17 00:00:00 2001 From: Kandelo Agent Date: Mon, 15 Jun 2026 05:57:12 +0000 Subject: [PATCH] fix: raise SIGPIPE for bridged TCP EPIPE (cherry picked from commit aede00ba912f3dff9b8fdc31aa467bc40da2f5c5) --- crates/kernel/src/syscalls.rs | 98 +++++++++++++++++++++++++++++++---- 1 file changed, 87 insertions(+), 11 deletions(-) diff --git a/crates/kernel/src/syscalls.rs b/crates/kernel/src/syscalls.rs index 0a727d567..9103ae096 100644 --- a/crates/kernel/src/syscalls.rs +++ b/crates/kernel/src/syscalls.rs @@ -2680,9 +2680,19 @@ pub fn sys_write( return Err(Errno::EAGAIN); } } - // External path: delegate to host + // External path: delegate to host. A stream write that + // fails with EPIPE must also generate SIGPIPE; the host + // only reports the errno, so mirror the POSIX side effect + // in the kernel just like the in-kernel pipe-backed TCP + // path above. let net_handle = sock.host_net_handle.ok_or(Errno::ENOTCONN)?; - host.host_net_send(net_handle, buf, 0) + match host.host_net_send(net_handle, buf, 0) { + Err(Errno::EPIPE) => { + proc.signals.raise(wasm_posix_shared::signal::SIGPIPE); + Err(Errno::EPIPE) + } + other => other, + } } SocketDomain::Unix => { if sock.send_buf_idx.is_none() @@ -6677,10 +6687,26 @@ pub fn sys_send( SocketDomain::Inet | SocketDomain::Inet6 => { // Loopback path: use pipe buffers if available if sock.send_buf_idx.is_some() { - return sys_write(proc, host, fd, buf); + let nosignal = flags & MSG_NOSIGNAL != 0; + let sigpipe_was_pending = + proc.signals.is_pending(wasm_posix_shared::signal::SIGPIPE); + let result = sys_write(proc, host, fd, buf); + // MSG_NOSIGNAL: suppress SIGPIPE raised by write. + if nosignal && !sigpipe_was_pending { + proc.signals.clear(wasm_posix_shared::signal::SIGPIPE); + } + return result; } let net_handle = sock.host_net_handle.ok_or(Errno::ENOTCONN)?; - host.host_net_send(net_handle, buf, flags) + match host.host_net_send(net_handle, buf, flags) { + Err(Errno::EPIPE) => { + if flags & MSG_NOSIGNAL == 0 { + proc.signals.raise(wasm_posix_shared::signal::SIGPIPE); + } + Err(Errno::EPIPE) + } + other => other, + } } SocketDomain::Unix => { // DGRAM bit-bucket (syslog pattern): data is discarded @@ -11048,6 +11074,11 @@ mod tests { /// Override for `gl_submit`'s return value (0 = success, negative /// = errno). Defaults to 0. gl_submit_rc: i32, + net_connect_result: Result<(), Errno>, + net_connect_status_result: Result<(), Errno>, + net_send_result: Result, + net_connect_calls: Vec<(i32, Vec, u16)>, + net_listen_calls: Vec<(i32, u16, [u8; 4])>, } impl MockHostIO { @@ -11071,6 +11102,11 @@ mod tests { gl_unbind_calls: Vec::new(), gbm_bo_bind_rc: 0, gl_submit_rc: 0, + net_connect_result: Err(Errno::ECONNREFUSED), + net_connect_status_result: Err(Errno::ECONNREFUSED), + net_send_result: Err(Errno::ENOTCONN), + net_connect_calls: Vec::new(), + net_listen_calls: Vec::new(), } } @@ -11427,14 +11463,15 @@ mod tests { } fn host_net_connect( &mut self, - _handle: i32, - _addr: &[u8], - _port: u16, + handle: i32, + addr: &[u8], + port: u16, ) -> Result<(), Errno> { - Err(Errno::ECONNREFUSED) + self.net_connect_calls.push((handle, addr.to_vec(), port)); + self.net_connect_result } fn host_net_connect_status(&mut self, _handle: i32) -> Result<(), Errno> { - Err(Errno::ECONNREFUSED) + self.net_connect_status_result } fn host_net_send( &mut self, @@ -11442,7 +11479,7 @@ mod tests { _data: &[u8], _flags: u32, ) -> Result { - Err(Errno::ENOTCONN) + self.net_send_result } fn host_net_recv( &mut self, @@ -11456,7 +11493,8 @@ mod tests { fn host_net_close(&mut self, _handle: i32) -> Result<(), Errno> { Ok(()) } - fn host_net_listen(&mut self, _fd: i32, _port: u16, _addr: &[u8; 4]) -> Result<(), Errno> { + fn host_net_listen(&mut self, fd: i32, port: u16, addr: &[u8; 4]) -> Result<(), Errno> { + self.net_listen_calls.push((fd, port, *addr)); Ok(()) } fn host_getaddrinfo(&mut self, _name: &[u8], _result: &mut [u8]) -> Result { @@ -13412,6 +13450,25 @@ mod tests { assert!(proc.signals.is_pending(wasm_posix_shared::signal::SIGPIPE)); } + #[test] + fn test_write_external_tcp_epipe_raises_sigpipe() { + let mut proc = Process::new(1); + let mut host = MockHostIO::new(); + use wasm_posix_shared::socket::*; + + host.net_connect_result = Ok(()); + host.net_connect_status_result = Ok(()); + host.net_send_result = Err(Errno::EPIPE); + + let fd = sys_socket(&mut proc, &mut host, AF_INET, SOCK_STREAM, 0).unwrap(); + let addr = [2, 0, 0, 80, 93, 184, 216, 34, 0, 0, 0, 0, 0, 0, 0, 0]; + sys_connect(&mut proc, &mut host, fd, &addr).unwrap(); + + let result = sys_write(&mut proc, &mut host, fd, b"test"); + assert_eq!(result, Err(Errno::EPIPE)); + assert!(proc.signals.is_pending(wasm_posix_shared::signal::SIGPIPE)); + } + #[test] fn test_socketpair_close_both_frees_pipes() { let mut proc = Process::new(1); @@ -13620,6 +13677,25 @@ mod tests { assert!(!proc.signals.is_pending(wasm_posix_shared::signal::SIGPIPE)); } + #[test] + fn test_send_external_tcp_msg_nosignal_suppresses_sigpipe() { + let mut proc = Process::new(1); + let mut host = MockHostIO::new(); + use wasm_posix_shared::socket::*; + + host.net_connect_result = Ok(()); + host.net_connect_status_result = Ok(()); + host.net_send_result = Err(Errno::EPIPE); + + let fd = sys_socket(&mut proc, &mut host, AF_INET, SOCK_STREAM, 0).unwrap(); + let addr = [2, 0, 0, 80, 93, 184, 216, 34, 0, 0, 0, 0, 0, 0, 0, 0]; + sys_connect(&mut proc, &mut host, fd, &addr).unwrap(); + + let result = sys_send(&mut proc, &mut host, fd, b"test", MSG_NOSIGNAL); + assert_eq!(result, Err(Errno::EPIPE)); + assert!(!proc.signals.is_pending(wasm_posix_shared::signal::SIGPIPE)); + } + #[test] fn test_send_not_connected() { let mut proc = Process::new(1);