Pumpkin/pumpkin-protocol/fuzz/fuzz_targets/decoder_java.rs
Alexander Medvedev bc205d2683 feat: pumpkin-protocol: add fuzzing
actually found some issues
2026-02-22 15:24:36 +01:00

148 lines
5.5 KiB
Rust

#![no_main]
use libfuzzer_sys::fuzz_target;
use pumpkin_protocol::{
ServerPacket,
java::{
packet_decoder::TCPNetworkDecoder,
server::{
config::{
SClientInformationConfig, SConfigCookieResponse, SConfigResourcePack, SKnownPacks,
SPluginMessage,
},
handshake::SHandShake,
login::{SEncryptionResponse, SLoginCookieResponse, SLoginPluginResponse, SLoginStart},
play::{
SChangeGameMode, SChatCommand, SChatMessage, SChunkBatch, SClickSlot,
SClientCommand, SClientInformationPlay, SCloseContainer, SCommandSuggestion,
SConfirmTeleport, SCookieResponse, SCustomPayload, SInteract, SKeepAlive,
SMoveVehicle, SPaddleBoat, SPickItemFromBlock, SPlayPingRequest, SPlayerAbilities,
SPlayerAction, SPlayerCommand, SPlayerInput, SPlayerLoaded, SPlayerPosition,
SPlayerPositionRotation, SPlayerRotation, SPlayerSession, SSetCommandBlock,
SSetCreativeSlot, SSetHeldItem, SSetPlayerGround, SSwingArm, SUpdateSign, SUseItem,
SUseItemOn,
},
status::SStatusPingRequest,
},
},
};
use std::io::Cursor;
use tokio::runtime::Runtime;
// ---------------------------------------------------------------------------
// Helper: run every known ServerPacket::read against the same payload.
// We don't care about the result — only that nothing panics.
// ---------------------------------------------------------------------------
fn fuzz_all_deserializers(payload: &[u8]) {
// Handshake
let _ = SHandShake::read(payload);
// Status
//let _ = SStatusRequest::read(payload);
let _ = SStatusPingRequest::read(payload);
// Login
let _ = SLoginStart::read(payload);
let _ = SEncryptionResponse::read(payload);
let _ = SLoginPluginResponse::read(payload);
//let _ = SLoginAcknowledged::read(payload);
let _ = SLoginCookieResponse::read(payload);
// Config
let _ = SClientInformationConfig::read(payload);
let _ = SPluginMessage::read(payload);
//let _ = SAcknowledgeFinishConfig::read(payload);
let _ = SKnownPacks::read(payload);
let _ = SConfigCookieResponse::read(payload);
let _ = SConfigResourcePack::read(payload);
// Play
let _ = SConfirmTeleport::read(payload);
let _ = SChangeGameMode::read(payload);
let _ = SChatCommand::read(payload);
let _ = SChatMessage::read(payload);
let _ = SClientInformationPlay::read(payload);
let _ = SClientCommand::read(payload);
let _ = SPlayerInput::read(payload);
let _ = SMoveVehicle::read(payload);
let _ = SPaddleBoat::read(payload);
let _ = SInteract::read(payload);
let _ = SKeepAlive::read(payload);
let _ = SPlayerPosition::read(payload);
let _ = SPlayerPositionRotation::read(payload);
let _ = SPlayerRotation::read(payload);
let _ = SSetPlayerGround::read(payload);
let _ = SPickItemFromBlock::read(payload);
let _ = SPlayerAbilities::read(payload);
let _ = SPlayerAction::read(payload);
let _ = SSetCommandBlock::read(payload);
let _ = SPlayerCommand::read(payload);
let _ = SPlayerLoaded::read(payload);
let _ = SPlayPingRequest::read(payload);
let _ = SClickSlot::read(payload);
let _ = SSetHeldItem::read(payload);
let _ = SSetCreativeSlot::read(payload);
let _ = SSwingArm::read(payload);
let _ = SUpdateSign::read(payload);
let _ = SUseItemOn::read(payload);
let _ = SUseItem::read(payload);
let _ = SCommandSuggestion::read(payload);
let _ = SCookieResponse::read(payload);
let _ = SCloseContainer::read(payload);
let _ = SChunkBatch::read(payload);
let _ = SPlayerSession::read(payload);
let _ = SCustomPayload::read(payload);
}
// ---------------------------------------------------------------------------
// Fuzz target
// ---------------------------------------------------------------------------
fuzz_target!(|data: &[u8]| {
if data.len() < 18 {
return;
}
// Byte layout:
// [0] = decoder mode (0=plain, 1=compressed, 2=encrypted, 3=both)
// [1] = split point % rest.len() — how much goes to the decoder vs
// the deserializers, letting the fuzzer explore both paths
// independently with different byte patterns
// [2..18] = 16-byte AES key candidate
// [18..] = raw bytes fed to both fuzzing paths
let mode = data[0] % 4;
let key = &data[2..18];
let rest = &data[18..];
let split = if rest.is_empty() {
0
} else {
(data[1] as usize) % rest.len()
};
let (decoder_bytes, deser_bytes) = rest.split_at(split);
// --- Path 1: decoder (framing / encryption / compression) --------------
let rt = Runtime::new().unwrap();
rt.block_on(async {
let mut decoder = TCPNetworkDecoder::new(Cursor::new(decoder_bytes));
match mode {
1 => {
decoder.set_compression(256);
}
2 => {
let mut aes_key = [0u8; 16];
aes_key.copy_from_slice(key);
decoder.set_encryption(&aes_key);
}
3 => {
decoder.set_compression(256);
let mut aes_key = [0u8; 16];
aes_key.copy_from_slice(key);
decoder.set_encryption(&aes_key);
}
_ => {}
}
let _ = decoder.get_raw_packet().await;
});
fuzz_all_deserializers(deser_bytes);
});