diff --git a/fuzz/src/fuzz_target.rs b/fuzz/src/fuzz_target.rs index 00994987..55d620c9 100644 --- a/fuzz/src/fuzz_target.rs +++ b/fuzz/src/fuzz_target.rs @@ -436,21 +436,31 @@ impl FuzzTargetRunner { mut stream: UnixStream, ) -> Result<(), FuzzTargetError> { // First message must be the PeerInfo handshake - self.handle_handshake(&mut stream).await?; + if let Err(e) = self.handle_handshake(&mut stream).await { + if Self::is_session_disconnect_error(&e) { + tracing::info!("Fuzzer disconnected (handshake)"); + return Ok(()); + } + return Err(e); + } // Handle incoming messages loop { match StreamUtils::read_message(&mut stream).await { - Ok(message_kind) => self.process_message(&mut stream, message_kind).await?, - Err(e) => { - if let FuzzTargetError::IoError(io_error) = e { - // Normal disconnection (EOF) - if io_error.kind() == std::io::ErrorKind::UnexpectedEof { - tracing::info!("Fuzzer session disconnected gracefully"); + Ok(message_kind) => { + if let Err(e) = self.process_message(&mut stream, message_kind).await { + if Self::is_session_disconnect_error(&e) { + tracing::info!("Fuzzer session disconnected (read_message)"); return Ok(()); } + return Err(e); + } + } + Err(e) => { + if Self::is_session_disconnect_error(&e) { + tracing::info!("Fuzzer session disconnected gracefully"); + return Ok(()); } else { - // Other errors return Err(e); } } @@ -458,6 +468,21 @@ impl FuzzTargetRunner { } } + fn is_session_disconnect_error(error: &FuzzTargetError) -> bool { + if let FuzzTargetError::IoError(io_error) = error { + return matches!( + io_error.kind(), + std::io::ErrorKind::UnexpectedEof + | std::io::ErrorKind::BrokenPipe + | std::io::ErrorKind::ConnectionReset + | std::io::ErrorKind::ConnectionAborted + | std::io::ErrorKind::NotConnected + | std::io::ErrorKind::WriteZero + ); + } + false + } + async fn handle_handshake(&mut self, stream: &mut UnixStream) -> Result<(), FuzzTargetError> { let message_kind = StreamUtils::read_message(stream).await?; diff --git a/fuzz/src/tests.rs b/fuzz/src/tests.rs index 48138422..831a1be4 100644 --- a/fuzz/src/tests.rs +++ b/fuzz/src/tests.rs @@ -658,4 +658,70 @@ mod fuzz_target_tests { cleanup_socket(&socket_path); Ok(()) } + + #[cfg(feature = "tiny")] + #[tokio::test] + async fn test_fuzz_disconnect_during_session() -> Result<(), FuzzTargetError> { + setup_tracing(); + let _temp_dir_sock = tempdir().unwrap(); + let socket_path = _temp_dir_sock + .path() + .join("fuzz_socket") + .to_str() + .unwrap() + .to_string(); + + let _server_jh = run_fuzz_target(socket_path.clone())?; + tokio::time::sleep(Duration::from_millis(100)).await; + + // Session #1: send GetState and disconnect before reading response. + { + let mut client = UnixStream::connect(socket_path.clone()).await?; + let _ = MockFuzzer::handshake(&mut client).await?; + + let test_case_1 = load_test_case(1); + let test_case_2 = load_test_case(2); + let test_case_3 = load_test_case(3); + + let _ = MockFuzzer::initialize( + &mut client, + Initialize { + header: test_case_1.block.header.clone().into(), + state: test_case_1.post_state.clone().into(), + ancestry: Ancestry::default(), + }, + ) + .await?; + + let _ = MockFuzzer::import_block(&mut client, ImportBlock(test_case_2.block.into())) + .await?; + let _ = MockFuzzer::import_block( + &mut client, + ImportBlock(test_case_3.block.clone().into()), + ) + .await?; + + let last_header_hash = BlockHeader::from(test_case_3.block.header).hash()?; + StreamUtils::send_message( + &mut client, + FuzzMessageKind::GetState(GetState(last_header_hash)), + ) + .await?; + + // Drop the socket immediately to simulate fuzzer-side early disconnect. + drop(client); + } + + tokio::time::sleep(Duration::from_millis(200)).await; + + // Session #2: server should still accept new connections. + { + let mut client = UnixStream::connect(socket_path.clone()).await?; + let peer_info = MockFuzzer::handshake(&mut client).await?; + assert_eq!(peer_info, create_test_peer_info("TestFastRoll")); + } + + cleanup_socket(&socket_path); + Ok(()) + } } diff --git a/integration/jam-conformance b/integration/jam-conformance index d4ead6e7..44ff0a7a 160000 --- a/integration/jam-conformance +++ b/integration/jam-conformance @@ -1 +1 @@ -Subproject commit d4ead6e7d50bba8519593e1ccfe89fa69ea9ce4a +Subproject commit 44ff0a7ae655179f40661132a2c81589a7e28ec7