diff --git a/Cargo.lock b/Cargo.lock index 2ffa94d6c..79c2d81ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2735,7 +2735,7 @@ dependencies = [ "tracing-subscriber", "ureq", "uuid", - "wasmparser 0.245.1", + "wasmparser 0.246.1", "wasmtime", "wasmtime-wasi", ] @@ -4352,6 +4352,19 @@ dependencies = [ "serde", ] +[[package]] +name = "wasmparser" +version = "0.246.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d991c35d79bf8336dc1cd632ed4aacf0dc5fac4bc466c670625b037b972bb9c" +dependencies = [ + "bitflags", + "hashbrown 0.16.1", + "indexmap", + "semver", + "serde", +] + [[package]] name = "wasmprinter" version = "0.245.1" diff --git a/Cargo.toml b/Cargo.toml index d21245c11..62f5abcae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -181,7 +181,7 @@ ureq = "3.3.0" wasmtime = "43.0" wasmtime-wasi = "43.0" wit-bindgen = "0.54" -wasmparser = "0.245" +wasmparser = "0.246" postcard = { version = "1.1", features = ["alloc"] } tracing-serde-structured = "0.4" diff --git a/pumpkin-plugin-wit/v0.1.0/player.wit b/pumpkin-plugin-wit/v0.1.0/player.wit index eb19caf77..f0c35b3fd 100644 --- a/pumpkin-plugin-wit/v0.1.0/player.wit +++ b/pumpkin-plugin-wit/v0.1.0/player.wit @@ -4,6 +4,16 @@ interface player { use permission.{permission-level}; use text.{text-component}; + record player-abilities { + invulnerable: bool, + flying: bool, + allow-flying: bool, + creative: bool, + allow-modify-world: bool, + fly-speed: f32, + walk-speed: f32, + } + resource player { get-id: func () -> result; get-name: func () -> result; @@ -45,8 +55,23 @@ interface player { get-max-health: func () -> result; get-food-level: func () -> result; get-saturation: func () -> result; - get-experience-level: func () -> result; - get-experience-progress: func () -> result; - get-experience-points: func () -> result; + + get-experience-level: func() -> result; + get-experience-progress: func() -> result; + get-experience-points: func() -> result; + set-experience-level: func(level: s32) -> result<_, string>; + set-experience-progress: func(progress: f32) -> result<_, string>; + set-experience-points: func(points: s32) -> result<_, string>; + add-experience-levels: func(levels: s32) -> result<_, string>; + add-experience-points: func(points: s32) -> result<_, string>; + + is-sneaking: func() -> result; + is-sprinting: func() -> result; + is-on-ground: func() -> result; + is-flying: func() -> result; + set-flying: func(flying: bool) -> result<_, string>; + + get-abilities: func() -> result; + set-abilities: func(abilities: player-abilities) -> result<_, string>; } } diff --git a/pumpkin-plugin-wit/v0.1.0/server.wit b/pumpkin-plugin-wit/v0.1.0/server.wit index 729a82a2a..422531ceb 100644 --- a/pumpkin-plugin-wit/v0.1.0/server.wit +++ b/pumpkin-plugin-wit/v0.1.0/server.wit @@ -7,6 +7,16 @@ interface server { } resource server { + /// Returns the current difficulty level of the server get-difficulty: func() -> difficulty; + + /// Returns the total number of players connected across all worlds. + get-player-count: func() -> u32; + + /// Returns the rolling average Milliseconds Per Tick (MSPT) over the last 100 ticks + get-mspt: func() -> f64; + + /// Returns the effective Ticks Per Second (TPS) + get-tps: func() -> f64; } } diff --git a/pumpkin-plugin-wit/v0.1.0/world.wit b/pumpkin-plugin-wit/v0.1.0/world.wit index 2a5970bfc..aacd33632 100644 --- a/pumpkin-plugin-wit/v0.1.0/world.wit +++ b/pumpkin-plugin-wit/v0.1.0/world.wit @@ -1,5 +1,49 @@ interface %world { - resource %world { - get-id: func () -> string; + enum piston-behavior { + normal, + destroy, + block, + ignore, + push-only } -} + + flags block-flags { + notify-neighbors, + notify-listeners, + force-state, + skip-drops, + moved, + skip-redstone-wire-state-replacement, + skip-block-entity-replaced-callback, + skip-block-added-callback, + } + + record block-state { + id: u16, + luminance: u8, + opacity: u8, + hardness: f32, + is-air: bool, + is-liquid: bool, + is-solid: bool, + is-full-cube: bool, + piston-behavior: piston-behavior, + has-random-ticks: bool, + } + + record block-pos { + x: s32, + y: s32, + z: s32, + } + + resource %world { + get-id: func() -> string; + + get-block-state-id: func(pos: block-pos) -> u16; + + get-block-state: func(pos: block-pos) -> block-state; + + set-block-state: func(pos: block-pos, state: u16, update-flags: block-flags); + } +} \ No newline at end of file diff --git a/pumpkin-protocol/fuzz/Cargo.lock b/pumpkin-protocol/fuzz/Cargo.lock index 6e735087f..511911487 100644 --- a/pumpkin-protocol/fuzz/Cargo.lock +++ b/pumpkin-protocol/fuzz/Cargo.lock @@ -33,9 +33,9 @@ checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" [[package]] name = "async-compression" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d67d43201f4d20c78bcda740c142ca52482d81da80681533d33bf3f0596c8e2" +checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" dependencies = [ "compression-codecs", "compression-core", @@ -289,9 +289,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crossfire" -version = "3.1.3" +version = "3.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4150133c3f558d3190b52a4d36b571e29c18a24e98d24d12201bed0c038986f8" +checksum = "e0042e53977a94d5d10de04ce7eb6016aa212c08e83ea202f7a9f102ff104303" dependencies = [ "crossbeam-utils", "futures-core", @@ -1039,9 +1039,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pumpkin-codecs" +version = "0.1.0-dev+26.1" +dependencies = [ + "dashmap", + "either", + "serde_json", + "tracing", +] + [[package]] name = "pumpkin-config" -version = "0.1.0-dev+1.21.11" +version = "0.1.0-dev+26.1" dependencies = [ "pumpkin-util", "serde", @@ -1052,18 +1062,19 @@ dependencies = [ [[package]] name = "pumpkin-data" -version = "0.1.0-dev+1.21.11" +version = "0.1.0-dev+26.1" dependencies = [ "crc-fast", "phf", "pumpkin-nbt", "pumpkin-util", + "rand", "serde", ] [[package]] name = "pumpkin-macros" -version = "0.1.0-dev+1.21.11" +version = "0.1.0-dev+26.1" dependencies = [ "proc-macro-error2", "pumpkin-data", @@ -1073,18 +1084,20 @@ dependencies = [ [[package]] name = "pumpkin-nbt" -version = "0.1.0-dev+1.21.11" +version = "0.1.0-dev+26.1" dependencies = [ "bytes", "cesu8", "flate2", + "pumpkin-codecs", "serde", "thiserror", + "tracing", ] [[package]] name = "pumpkin-protocol" -version = "0.1.0-dev+1.21.11" +version = "0.1.0-dev+26.1" dependencies = [ "aes", "async-compression", @@ -1110,12 +1123,13 @@ version = "0.0.0" dependencies = [ "libfuzzer-sys", "pumpkin-protocol", + "pumpkin-util", "tokio", ] [[package]] name = "pumpkin-util" -version = "0.1.0-dev+1.21.11" +version = "0.1.0-dev+26.1" dependencies = [ "base64", "bytes", @@ -1139,7 +1153,7 @@ dependencies = [ [[package]] name = "pumpkin-world" -version = "0.1.0-dev+1.21.11" +version = "0.1.0-dev+26.1" dependencies = [ "bitflags", "bytes", @@ -1160,7 +1174,7 @@ dependencies = [ "rustc-hash", "ruzstd", "serde", - "sha2 0.11.0-rc.5", + "sha2 0.11.0", "slotmap", "thiserror", "tokio", @@ -1231,9 +1245,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustversion" @@ -1321,9 +1335,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] @@ -1341,12 +1355,12 @@ dependencies = [ [[package]] name = "sha2" -version = "0.11.0-rc.5" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f3b1e2dc8aad28310d8410bd4d7e180eca65fca176c52ab00d364475d0024" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", + "cpufeatures 0.3.0", "digest 0.11.0", ] @@ -1486,9 +1500,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", @@ -1528,9 +1542,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.0.3+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7614eaf19ad818347db24addfa201729cf2a9b6fdfd9eb0ab870fcacc606c0c" +checksum = "994b95d9e7bae62b34bab0e2a4510b801fa466066a6a8b2b57361fa1eba068ee" dependencies = [ "indexmap", "serde_core", @@ -1543,27 +1557,27 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.0.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.9+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +checksum = "39ca317ebc49f06bd748bfba29533eac9485569dc9bf80b849024b025e814fb9" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.6+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tracing" @@ -1632,9 +1646,9 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "uuid" -version = "1.21.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ "getrandom 0.4.1", "js-sys", @@ -1843,9 +1857,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.14" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" [[package]] name = "wit-bindgen" diff --git a/pumpkin-protocol/fuzz/Cargo.toml b/pumpkin-protocol/fuzz/Cargo.toml index 585172413..1ba419192 100644 --- a/pumpkin-protocol/fuzz/Cargo.toml +++ b/pumpkin-protocol/fuzz/Cargo.toml @@ -19,9 +19,19 @@ libfuzzer-sys = "0.4" [dependencies.pumpkin-protocol] path = ".." +[dependencies.pumpkin-util] +path = "../../pumpkin-util" + [[bin]] name = "decoder_java" path = "fuzz_targets/decoder_java.rs" test = false doc = false bench = false + +[[bin]] +name = "decoder_bedrock" +path = "fuzz_targets/decoder_bedrock.rs" +test = false +doc = false +bench = false diff --git a/pumpkin-protocol/fuzz/fuzz_targets/decoder_bedrock.rs b/pumpkin-protocol/fuzz/fuzz_targets/decoder_bedrock.rs new file mode 100644 index 000000000..1cdbb4b02 --- /dev/null +++ b/pumpkin-protocol/fuzz/fuzz_targets/decoder_bedrock.rs @@ -0,0 +1,97 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; +use pumpkin_protocol::bedrock::packet_decoder::UDPNetworkDecoder; +use pumpkin_protocol::bedrock::server::{ + client_cache_status::SClientCacheStatus, + command_request::SCommandRequest, + container_close::SContainerClose, + interaction::SInteraction, + loading_screen::SLoadingScreen, + login::SLogin, + player_auth_input::SPlayerAuthInput, + raknet::{ + connection::SConnectionRequest, + open_connection::{SOpenConnectionRequest1, SOpenConnectionRequest2}, + unconnected_ping::SUnconnectedPing, + }, + request_chunk_radius::SRequestChunkRadius, + request_network_settings::SRequestNetworkSettings, + text::SText, +}; +use pumpkin_protocol::serial::PacketRead; +use std::io::Cursor; + +// --------------------------------------------------------------------------- +// Helper: Run every Serverbound packet's read method. +// --------------------------------------------------------------------------- +fn fuzz_serverbound_packets(payload: &[u8]) { + let mut cursor = Cursor::new(payload); + + macro_rules! run_read { + ($($packet:ty),* $(,)?) => { + $( + cursor.set_position(0); + // Attempt to read as a standard Game Packet (with version) + let _ = <$packet>::read(&mut cursor); + )* + }; + } + + // Standard Bedrock Serverbound Packets + run_read!( + SClientCacheStatus, + SCommandRequest, + SContainerClose, + SInteraction, + SLoadingScreen, + SLogin, + SPlayerAuthInput, + SRequestChunkRadius, + SRequestNetworkSettings, + SText, + ); + + // RakNet Handshake Packets (Usually read without version) + cursor.set_position(0); + let _ = SConnectionRequest::read(&mut cursor); + cursor.set_position(0); + let _ = SOpenConnectionRequest1::read(&mut cursor); + cursor.set_position(0); + let _ = SOpenConnectionRequest2::read(&mut cursor); + cursor.set_position(0); + let _ = SUnconnectedPing::read(&mut cursor); +} + +// --------------------------------------------------------------------------- +// Fuzz Target +// --------------------------------------------------------------------------- +fuzz_target!(|data: &[u8]| { + if data.len() < 20 { + return; + } + + // Split data for decoder configuration vs raw payload + let threshold_raw = data[0]; + let key: [u8; 16] = data[1..17].try_into().unwrap(); + let stream_data = &data[17..]; + + let mut decoder = UDPNetworkDecoder::new(); + + // Setup Decoder + if threshold_raw > 0 { + // Assuming your CompressionThreshold is a wrapper around u32 + decoder.set_compression((threshold_raw as u32).try_into().unwrap()); + } + decoder.set_encryption(&key); + + // 1. Fuzz the Decoder (Framing/VarInts/Bitmasks) + let decoder_cursor = Cursor::new(stream_data.to_vec()); + if let Ok(raw_packet) = decoder.get_game_packet(decoder_cursor) { + // If framed correctly, fuzz the internal payload + fuzz_serverbound_packets(&raw_packet.payload); + } + + // 2. Shotgun Fuzz + // Pass raw data directly to all readers to catch panics in the parsers + fuzz_serverbound_packets(stream_data); +}); diff --git a/pumpkin-protocol/fuzz/fuzz_targets/decoder_java.rs b/pumpkin-protocol/fuzz/fuzz_targets/decoder_java.rs index f8bd5a28a..9711b321e 100644 --- a/pumpkin-protocol/fuzz/fuzz_targets/decoder_java.rs +++ b/pumpkin-protocol/fuzz/fuzz_targets/decoder_java.rs @@ -1,96 +1,102 @@ #![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 pumpkin_protocol::ServerPacket; +use pumpkin_protocol::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 pumpkin_util::version::MinecraftVersion; use std::io::Cursor; use tokio::runtime::Runtime; +const TARGET_VERSION: MinecraftVersion = MinecraftVersion::V_26_1; + // --------------------------------------------------------------------------- // Helper: run every known ServerPacket::read against the same payload. -// We don't care about the result — only that nothing panics. +// Uses a Cursor and the Version enum as required by the new signature. // --------------------------------------------------------------------------- fn fuzz_all_deserializers(payload: &[u8]) { - // Handshake - let _ = SHandShake::read(payload); + let mut cursor = Cursor::new(payload); - // Status - //let _ = SStatusRequest::read(payload); - let _ = SStatusPingRequest::read(payload); + macro_rules! run_read { + ($($packet:ty),* $(,)?) => { + $( + cursor.set_position(0); + let _ = <$packet>::read(&mut cursor, &TARGET_VERSION); + )* + }; + } - // 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); + run_read!( + // Handshake + SHandShake, + // Status + SStatusPingRequest, + // Login + SLoginStart, + SEncryptionResponse, + SLoginPluginResponse, + SLoginCookieResponse, + // Config + SClientInformationConfig, + SPluginMessage, + SKnownPacks, + SConfigCookieResponse, + SConfigResourcePack, + // Play + SConfirmTeleport, + SChangeGameMode, + SChatCommand, + SChatMessage, + SClientInformationPlay, + SClientCommand, + SPlayerInput, + SMoveVehicle, + SPaddleBoat, + SInteract, + SKeepAlive, + SPlayerPosition, + SPlayerPositionRotation, + SPlayerRotation, + SSetPlayerGround, + SPickItemFromBlock, + SPlayerAbilities, + SPlayerAction, + SSetCommandBlock, + SPlayerCommand, + SPlayerLoaded, + SPlayPingRequest, + SClickSlot, + SSetHeldItem, + SSetCreativeSlot, + SSwingArm, + SUpdateSign, + SUseItemOn, + SUseItem, + SCommandSuggestion, + SCookieResponse, + SCloseContainer, + SChunkBatch, + SPlayerSession, + SCustomPayload, + ); } // --------------------------------------------------------------------------- @@ -101,13 +107,6 @@ fuzz_target!(|data: &[u8]| { 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..]; @@ -143,5 +142,6 @@ fuzz_target!(|data: &[u8]| { let _ = decoder.get_raw_packet().await; }); + // --- Path 2: Individual Packet Deserializers --------------------------- fuzz_all_deserializers(deser_bytes); }); diff --git a/pumpkin-protocol/src/bedrock/packet_decoder.rs b/pumpkin-protocol/src/bedrock/packet_decoder.rs index 81124ce22..605b987c8 100644 --- a/pumpkin-protocol/src/bedrock/packet_decoder.rs +++ b/pumpkin-protocol/src/bedrock/packet_decoder.rs @@ -9,7 +9,8 @@ use bytes::Bytes; use tokio::io::{AsyncRead, BufReader, ReadBuf}; use crate::{ - Aes128Cfb8Dec, CompressionThreshold, PacketDecodeError, RawPacket, StreamDecryptor, + Aes128Cfb8Dec, CompressionThreshold, MAX_PACKET_SIZE, PacketDecodeError, RawPacket, + StreamDecryptor, codec::var_uint::VarUInt, ser::{NetworkReadExt, ReadingError}, }; @@ -138,6 +139,9 @@ impl UDPNetworkDecoder { })?; let packet_len = u64::from(packet_len.0); + if !(0..=MAX_PACKET_SIZE).contains(&packet_len) { + Err(PacketDecodeError::OutOfBounds)?; + } let var_header = VarUInt::decode(&mut reader)?; @@ -159,8 +163,17 @@ impl UDPNetworkDecoder { // Gamepacket ID: Remaining 10 bits (bits 4 to 13) let gamepacket_id = (header & 0x3FF) as u16; // 0x3FF is 10 bits set to 1 + let header_size = var_header.written_size(); + + if (packet_len as usize) < header_size { + return Err(PacketDecodeError::MalformedLength( + "Packet length is smaller than the header size".to_string(), + )); + } + let payload_len = packet_len as usize - header_size; + let payload = reader - .read_boxed_slice(packet_len as usize - var_header.written_size()) + .read_boxed_slice(payload_len) .map_err(|err| PacketDecodeError::FailedDecompression(err.to_string()))?; Ok(RawPacket { diff --git a/pumpkin-protocol/src/bedrock/server/login.rs b/pumpkin-protocol/src/bedrock/server/login.rs index d53dfa89a..0454ef9ea 100644 --- a/pumpkin-protocol/src/bedrock/server/login.rs +++ b/pumpkin-protocol/src/bedrock/server/login.rs @@ -1,5 +1,5 @@ use pumpkin_macros::packet; -use std::io::{Error, Read}; +use std::io::{Error, ErrorKind, Read}; use crate::{codec::var_uint::VarUInt, serial::PacketRead}; @@ -15,15 +15,30 @@ pub struct SLogin { impl PacketRead for SLogin { fn read(reader: &mut R) -> Result { + const MAX_TOKEN_SIZE: usize = 256 * 1024; // 256KB limit + let protocol_version = i32::read_be(reader)?; + let _len = VarUInt::read(reader)?; - let jwt_len = u32::read(reader)?; - let mut jwt = vec![0; jwt_len as _]; + let jwt_len = u32::read_be(reader)? as usize; + if jwt_len > MAX_TOKEN_SIZE { + return Err(Error::new( + ErrorKind::InvalidData, + "JWT length exceeds limit", + )); + } + let mut jwt = vec![0u8; jwt_len]; reader.read_exact(&mut jwt)?; - let raw_token_len = u32::read(reader)?; - let mut raw_token = vec![0; raw_token_len as _]; + let raw_token_len = u32::read_be(reader)? as usize; + if raw_token_len > MAX_TOKEN_SIZE { + return Err(Error::new( + ErrorKind::InvalidData, + "Raw token length exceeds limit", + )); + } + let mut raw_token = vec![0u8; raw_token_len]; reader.read_exact(&mut raw_token)?; Ok(Self { diff --git a/pumpkin-protocol/src/serial/deserializer.rs b/pumpkin-protocol/src/serial/deserializer.rs index 7bd022c7e..f4169da8d 100644 --- a/pumpkin-protocol/src/serial/deserializer.rs +++ b/pumpkin-protocol/src/serial/deserializer.rs @@ -1,5 +1,5 @@ use std::{ - io::{Error, Read}, + io::{Error, ErrorKind, Read}, net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, }; @@ -133,8 +133,22 @@ impl PacketRead for [T; N] { impl PacketRead for String { fn read(reader: &mut R) -> Result { - let vec = Vec::read(reader)?; - Ok(unsafe { Self::from_utf8_unchecked(vec) }) + const MAX_STRING_LENGTH: usize = 32767; + + let len = u32::read(reader)? as usize; + + if len > MAX_STRING_LENGTH { + return Err(Error::new( + ErrorKind::InvalidData, + format!("String length {len} exceeds maximum of {MAX_STRING_LENGTH}"), + )); + } + + let mut buf = vec![0u8; len]; + reader.read_exact(&mut buf)?; + + Self::from_utf8(buf) + .map_err(|_| Error::new(ErrorKind::InvalidData, "Invalid UTF-8 sequence")) } } diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/player.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/player.rs index b6131eece..8a5ca8e8d 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/player.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/player.rs @@ -401,6 +401,135 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState { Ok(player.experience_points.load(Ordering::Relaxed)) } + async fn set_experience_level( + &mut self, + player: Resource, + level: i32, + ) -> Result<(), String> { + let player = player_from_resource(self, &player)?; + player.set_experience_level(level, true).await; + Ok(()) + } + + async fn set_experience_progress( + &mut self, + player: Resource, + progress: f32, + ) -> Result<(), String> { + let player = player_from_resource(self, &player)?; + player + .set_experience( + player.experience_level.load(Ordering::Relaxed), + progress, + player.experience_points.load(Ordering::Relaxed), + ) + .await; + Ok(()) + } + + async fn set_experience_points( + &mut self, + player: Resource, + points: i32, + ) -> Result<(), String> { + let player = player_from_resource(self, &player)?; + player + .set_experience( + player.experience_level.load(Ordering::Relaxed), + player.experience_progress.load(), + points, + ) + .await; + Ok(()) + } + + async fn add_experience_levels( + &mut self, + player: Resource, + levels: i32, + ) -> Result<(), String> { + let player = player_from_resource(self, &player)?; + player.add_experience_levels(levels).await; + Ok(()) + } + + async fn add_experience_points( + &mut self, + player: Resource, + points: i32, + ) -> Result<(), String> { + let player = player_from_resource(self, &player)?; + player.add_experience_points(points).await; + Ok(()) + } + + async fn is_sneaking(&mut self, player: Resource) -> Result { + let player = player_from_resource(self, &player)?; + Ok(player.get_entity().sneaking.load(Ordering::Relaxed)) + } + + async fn is_sprinting(&mut self, player: Resource) -> Result { + let player = player_from_resource(self, &player)?; + Ok(player.get_entity().sprinting.load(Ordering::Relaxed)) + } + + async fn is_on_ground(&mut self, player: Resource) -> Result { + let player = player_from_resource(self, &player)?; + Ok(player.get_entity().on_ground.load(Ordering::Relaxed)) + } + + async fn is_flying(&mut self, player: Resource) -> Result { + let player = player_from_resource(self, &player)?; + Ok(player.is_flying().await) + } + + async fn set_flying(&mut self, player: Resource, flying: bool) -> Result<(), String> { + let player = player_from_resource(self, &player)?; + { + let mut abilities = player.abilities.lock().await; + abilities.flying = flying; + }; + player.send_abilities_update().await; + Ok(()) + } + + async fn get_abilities( + &mut self, + player: Resource, + ) -> Result { + let player = player_from_resource(self, &player)?; + let abilities = player.abilities.lock().await; + Ok(pumpkin::plugin::player::PlayerAbilities { + invulnerable: abilities.invulnerable, + flying: abilities.flying, + allow_flying: abilities.allow_flying, + creative: abilities.creative, + allow_modify_world: abilities.allow_modify_world, + fly_speed: abilities.fly_speed, + walk_speed: abilities.walk_speed, + }) + } + + async fn set_abilities( + &mut self, + player: Resource, + abilities: pumpkin::plugin::player::PlayerAbilities, + ) -> Result<(), String> { + let player = player_from_resource(self, &player)?; + { + let mut a = player.abilities.lock().await; + a.invulnerable = abilities.invulnerable; + a.flying = abilities.flying; + a.allow_flying = abilities.allow_flying; + a.creative = abilities.creative; + a.allow_modify_world = abilities.allow_modify_world; + a.fly_speed = abilities.fly_speed; + a.walk_speed = abilities.walk_speed; + }; + player.send_abilities_update().await; + Ok(()) + } + async fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { let _ = self .resource_table diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/server.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/server.rs index 3cee7ea3e..999ca6fd4 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/server.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/server.rs @@ -33,4 +33,22 @@ impl pumpkin::plugin::server::HostServer for PluginHostState { pumpkin_util::Difficulty::Hard => Difficulty::Hard, } } + + async fn get_player_count( + &mut self, + _server: Resource, + ) -> u32 { + let server = self.server.as_ref().expect("server not available"); + server.get_player_count() as u32 + } + + async fn get_mspt(&mut self, _server: Resource) -> f64 { + let server = self.server.as_ref().expect("server not available"); + server.get_mspt() + } + + async fn get_tps(&mut self, _server: Resource) -> f64 { + let server = self.server.as_ref().expect("server not available"); + server.get_tps() + } } diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/world.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/world.rs index 64d8f6f09..03d7556e2 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/world.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/world.rs @@ -1,5 +1,12 @@ +use pumpkin_data::block_state::PistonBehavior; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::world::BlockFlags; use wasmtime::component::Resource; +use crate::plugin::loader::wasm::wasm_host::wit::v0_1_0::pumpkin::plugin::world::{ + BlockFlags as WitBlockFlags, BlockPos as WitBlockPos, BlockState as WitBlockState, + PistonBehavior as WitPistonBehavior, +}; use crate::plugin::loader::wasm::wasm_host::{ DowncastResourceExt, state::{PluginHostState, WorldResource}, @@ -44,6 +51,85 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState { .to_string() } + async fn get_block_state_id(&mut self, world: Resource, pos: WitBlockPos) -> u16 { + let world_ref = world.downcast_ref(self); + let internal_pos = BlockPos::new(pos.x, pos.y, pos.z); + + world_ref.provider.get_block_state_id(&internal_pos).await + } + + async fn get_block_state(&mut self, world: Resource, pos: WitBlockPos) -> WitBlockState { + let world_ref = world.downcast_ref(self); + let internal_pos = BlockPos::new(pos.x, pos.y, pos.z); + + // Fetch the actual BlockState struct from the world + // get_block_state typically returns &'static BlockState in Pumpkin + let state = world_ref.provider.get_block_state(&internal_pos).await; + + WitBlockState { + id: state.id, + luminance: state.luminance, + opacity: state.opacity, + hardness: state.hardness, + is_air: state.is_air(), + is_liquid: state.is_liquid(), + is_solid: state.is_solid(), + is_full_cube: state.is_full_cube(), + has_random_ticks: state.has_random_ticks(), + piston_behavior: match state.piston_behavior { + PistonBehavior::Normal => WitPistonBehavior::Normal, + PistonBehavior::Destroy => WitPistonBehavior::Destroy, + PistonBehavior::Block => WitPistonBehavior::Block, + PistonBehavior::Ignore => WitPistonBehavior::Ignore, + PistonBehavior::PushOnly => WitPistonBehavior::PushOnly, + }, + } + } + + async fn set_block_state( + &mut self, + world: Resource, + pos: WitBlockPos, + state: u16, + update_flags: WitBlockFlags, + ) { + let world_ref = world.downcast_ref(self); + let internal_pos = BlockPos::new(pos.x, pos.y, pos.z); + + // Map WIT flags to your internal bitflags + let mut internal_flags = BlockFlags::empty(); + if update_flags.contains(WitBlockFlags::NOTIFY_NEIGHBORS) { + internal_flags |= BlockFlags::NOTIFY_NEIGHBORS; + } + if update_flags.contains(WitBlockFlags::NOTIFY_LISTENERS) { + internal_flags |= BlockFlags::NOTIFY_LISTENERS; + } + if update_flags.contains(WitBlockFlags::FORCE_STATE) { + internal_flags |= BlockFlags::FORCE_STATE; + } + if update_flags.contains(WitBlockFlags::SKIP_DROPS) { + internal_flags |= BlockFlags::SKIP_DROPS; + } + if update_flags.contains(WitBlockFlags::MOVED) { + internal_flags |= BlockFlags::MOVED; + } + if update_flags.contains(WitBlockFlags::SKIP_REDSTONE_WIRE_STATE_REPLACEMENT) { + internal_flags |= BlockFlags::SKIP_REDSTONE_WIRE_STATE_REPLACEMENT; + } + if update_flags.contains(WitBlockFlags::SKIP_BLOCK_ENTITY_REPLACED_CALLBACK) { + internal_flags |= BlockFlags::SKIP_BLOCK_ENTITY_REPLACED_CALLBACK; + } + if update_flags.contains(WitBlockFlags::SKIP_BLOCK_ADDED_CALLBACK) { + internal_flags |= BlockFlags::SKIP_BLOCK_ADDED_CALLBACK; + } + + // Update the world + world_ref + .provider + .set_block_state(&internal_pos, state, internal_flags) + .await; + } + async fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { let _ = self .resource_table