mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
chore: some fuzz fixes
This commit is contained in:
223
Cargo.lock
generated
223
Cargo.lock
generated
@@ -99,12 +99,56 @@ version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.104"
|
||||
@@ -639,6 +683,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -647,8 +692,22 @@ version = "4.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -681,6 +740,12 @@ dependencies = [
|
||||
"thiserror 2.0.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "3.1.1"
|
||||
@@ -1775,6 +1840,18 @@ dependencies = [
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi 5.3.0",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.3"
|
||||
@@ -1783,7 +1860,7 @@ checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"r-efi 6.0.0",
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
@@ -2277,6 +2354,12 @@ version = "2.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78"
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
@@ -2770,6 +2853,12 @@ version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
version = "11.1.5"
|
||||
@@ -3118,6 +3207,15 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
version = "1.4.1"
|
||||
@@ -3316,7 +3414,7 @@ dependencies = [
|
||||
"pumpkin-protocol",
|
||||
"pumpkin-util",
|
||||
"pumpkin-world",
|
||||
"rand",
|
||||
"rand 0.10.2",
|
||||
"rayon",
|
||||
"rsa",
|
||||
"rustc-hash",
|
||||
@@ -3338,8 +3436,8 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
"ureq",
|
||||
"uuid",
|
||||
"wasm-encoder 0.255.0",
|
||||
"wasmparser 0.255.0",
|
||||
"wasm-encoder 0.256.0",
|
||||
"wasmparser 0.256.0",
|
||||
"wasmtime",
|
||||
"wasmtime-wasi",
|
||||
"wasmtime-wasi-http",
|
||||
@@ -3405,10 +3503,23 @@ dependencies = [
|
||||
"phf",
|
||||
"pumpkin-nbt",
|
||||
"pumpkin-util",
|
||||
"rand",
|
||||
"rand 0.10.2",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pumpkin-fuzzer"
|
||||
version = "0.1.0-dev+26.2-26.40"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"clap",
|
||||
"colored",
|
||||
"pumpkin-protocol",
|
||||
"pumpkin-util",
|
||||
"rand 0.9.5",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pumpkin-inventory"
|
||||
version = "0.1.0-dev+26.2-26.40"
|
||||
@@ -3417,7 +3528,7 @@ dependencies = [
|
||||
"pumpkin-protocol",
|
||||
"pumpkin-util",
|
||||
"pumpkin-world",
|
||||
"rand",
|
||||
"rand 0.10.2",
|
||||
"thiserror 2.0.20",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -3458,7 +3569,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tracing",
|
||||
"tracing-serde-structured",
|
||||
"wit-bindgen",
|
||||
"wit-bindgen 0.60.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3532,7 +3643,7 @@ dependencies = [
|
||||
"pumpkin-data",
|
||||
"pumpkin-nbt",
|
||||
"pumpkin-util",
|
||||
"rand",
|
||||
"rand 0.10.2",
|
||||
"rayon",
|
||||
"rustc-hash",
|
||||
"ruzstd",
|
||||
@@ -3578,6 +3689,12 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
@@ -3593,6 +3710,16 @@ dependencies = [
|
||||
"ptr_meta",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.10.2"
|
||||
@@ -3604,6 +3731,16 @@ dependencies = [
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
@@ -3613,6 +3750,15 @@ dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.10.1"
|
||||
@@ -3817,7 +3963,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"hex",
|
||||
"log",
|
||||
"rand",
|
||||
"rand 0.10.2",
|
||||
"rcgen",
|
||||
"ring",
|
||||
"rtc-datachannel",
|
||||
@@ -3874,7 +4020,7 @@ dependencies = [
|
||||
"log",
|
||||
"p256",
|
||||
"p384 0.13.1",
|
||||
"rand",
|
||||
"rand 0.10.2",
|
||||
"rand_core 0.6.4",
|
||||
"rcgen",
|
||||
"ring",
|
||||
@@ -3898,7 +4044,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"crc",
|
||||
"log",
|
||||
"rand",
|
||||
"rand 0.10.2",
|
||||
"rtc-mdns",
|
||||
"rtc-shared",
|
||||
"rtc-stun",
|
||||
@@ -3915,7 +4061,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f66ea5650a91773d10e4d98729f6fe256681ec1314c7ded5839fa34867305f79"
|
||||
dependencies = [
|
||||
"log",
|
||||
"rand",
|
||||
"rand 0.10.2",
|
||||
"rtc-interceptor-derive",
|
||||
"rtc-rtcp",
|
||||
"rtc-rtp",
|
||||
@@ -3955,7 +4101,7 @@ checksum = "4fbc2f460a321eccdc0907819f8fc7027036edcbd94620c3328c1aefb070dfe5"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"rand",
|
||||
"rand 0.10.2",
|
||||
"rtc-rtp",
|
||||
"rtc-shared",
|
||||
"thiserror 2.0.20",
|
||||
@@ -3979,7 +4125,7 @@ checksum = "aac1a05add837d45d562f48bc769c9b264440741aab319fed48ce6a696e6aa0c"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"memchr",
|
||||
"rand",
|
||||
"rand 0.10.2",
|
||||
"rtc-shared",
|
||||
"serde",
|
||||
]
|
||||
@@ -3993,7 +4139,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"crc32c",
|
||||
"log",
|
||||
"rand",
|
||||
"rand 0.10.2",
|
||||
"rtc-shared",
|
||||
"rustc-hash",
|
||||
"slab",
|
||||
@@ -4006,7 +4152,7 @@ version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0379bb552d4813e042d376db99f2005038017fc7864676fc835334b810db015"
|
||||
dependencies = [
|
||||
"rand",
|
||||
"rand 0.10.2",
|
||||
"rtc-shared",
|
||||
"url",
|
||||
]
|
||||
@@ -4023,7 +4169,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"nix",
|
||||
"p256",
|
||||
"rand",
|
||||
"rand 0.10.2",
|
||||
"rcgen",
|
||||
"sec1 0.7.3",
|
||||
"serde",
|
||||
@@ -4063,7 +4209,7 @@ dependencies = [
|
||||
"crc",
|
||||
"lazy_static",
|
||||
"md-5",
|
||||
"rand",
|
||||
"rand 0.10.2",
|
||||
"ring",
|
||||
"rtc-shared",
|
||||
"sansio",
|
||||
@@ -4545,6 +4691,12 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "substring"
|
||||
version = "1.4.5"
|
||||
@@ -4765,6 +4917,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
@@ -5193,6 +5346,15 @@ version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.4+wasi-0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
|
||||
dependencies = [
|
||||
"wit-bindgen 0.57.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.127"
|
||||
@@ -5260,12 +5422,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.255.0"
|
||||
version = "0.256.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b524283fb5df62eec102ed0574838961bdd7ba5ac9c50d38e2756c51c971a42"
|
||||
checksum = "ec1492381bfd5ea51c2a99a919b676662559925cb8d7490547ec2e14c1ad3eb1"
|
||||
dependencies = [
|
||||
"leb128fmt",
|
||||
"wasmparser 0.255.0",
|
||||
"wasmparser 0.256.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5310,6 +5472,17 @@ name = "wasmparser"
|
||||
version = "0.255.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8e329ef4b5d46e73b91d3ac6924417cad55a8cbbf869c199283383427c3320b"
|
||||
dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.256.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60bd825ffedc6cba8a642924ba7ae424afbc47811cffbcb7b92031ec24e59b4c"
|
||||
dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
"hashbrown 0.17.1",
|
||||
@@ -5570,7 +5743,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"futures",
|
||||
"io-lifetimes 3.0.1",
|
||||
"rand",
|
||||
"rand 0.10.2",
|
||||
"rustix",
|
||||
"thiserror 2.0.20",
|
||||
"tokio",
|
||||
@@ -5972,6 +6145,12 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.57.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.60.0"
|
||||
|
||||
@@ -14,6 +14,7 @@ members = [
|
||||
"crates/pumpkin-util",
|
||||
"crates/pumpkin-world",
|
||||
"tools/pumpkin-codegen",
|
||||
"tools/pumpkin-fuzzer",
|
||||
]
|
||||
|
||||
# [workspace.lints.rust]
|
||||
|
||||
@@ -940,15 +940,20 @@ pub trait ScreenHandler: Send + Sync {
|
||||
|
||||
let mut cursor_stack = behaviour.cursor_stack.lock().await;
|
||||
let initial_count = cursor_stack.item_count;
|
||||
let slots_count = behaviour.drag_slots.len();
|
||||
for slot_index in &behaviour.drag_slots {
|
||||
let slot = behaviour.slots[*slot_index as usize].clone();
|
||||
let Some(slot) = behaviour.slots.get(*slot_index as usize).cloned() else {
|
||||
continue;
|
||||
};
|
||||
let stack = slot.get_stack().await;
|
||||
|
||||
if (stack.are_items_and_components_equal(&cursor_stack) || stack.is_empty())
|
||||
&& slot.can_insert(&cursor_stack).await
|
||||
{
|
||||
let mut inserting_count = match drag_button {
|
||||
0 => initial_count / behaviour.drag_slots.len() as u8,
|
||||
0 => (initial_count as usize)
|
||||
.checked_div(slots_count)
|
||||
.map_or(0, |c| c as u8),
|
||||
1 => 1,
|
||||
2 => {
|
||||
cursor_stack.item_count = cursor_stack.get_max_stack_size();
|
||||
|
||||
202
crates/pumpkin-nbt/fuzz/Cargo.lock
generated
202
crates/pumpkin-nbt/fuzz/Cargo.lock
generated
@@ -17,6 +17,21 @@ dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.12.0"
|
||||
@@ -56,6 +71,16 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.4.2"
|
||||
@@ -68,10 +93,14 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.16.0"
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
@@ -89,6 +118,40 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
@@ -97,10 +160,21 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"r-efi 5.3.0",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi 6.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
@@ -113,10 +187,21 @@ version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.3.4",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.186"
|
||||
@@ -133,6 +218,16 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
@@ -172,24 +267,23 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pumpkin-codecs"
|
||||
version = "0.1.0-dev+26.1"
|
||||
version = "0.1.0-dev+26.2-26.40"
|
||||
dependencies = [
|
||||
"either",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pumpkin-nbt"
|
||||
version = "0.1.0-dev+26.1"
|
||||
version = "0.1.0-dev+26.2-26.40"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cesu8",
|
||||
"flate2",
|
||||
"pumpkin-codecs",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -199,7 +293,6 @@ dependencies = [
|
||||
"arbitrary",
|
||||
"libfuzzer-sys",
|
||||
"pumpkin-nbt",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -217,6 +310,18 @@ version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
@@ -224,7 +329,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -272,6 +376,12 @@ version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
@@ -334,12 +444,37 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9"
|
||||
dependencies = [
|
||||
"getrandom 0.4.3",
|
||||
"js-sys",
|
||||
"md-5",
|
||||
"serde_core",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.3+wasi-0.2.9"
|
||||
@@ -349,6 +484,51 @@ dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.57.1"
|
||||
|
||||
@@ -51,6 +51,18 @@ impl NbtCompound {
|
||||
|
||||
/// Advances a reader past a compound's payload without allocating its tags.
|
||||
pub fn skip_content<'a, R: NbtReadHelper<'a>>(reader: &mut R) -> Result<(), Error> {
|
||||
Self::skip_content_depth(reader, 0)
|
||||
}
|
||||
|
||||
/// Advances a reader past a compound's payload with depth tracking.
|
||||
pub fn skip_content_depth<'a, R: NbtReadHelper<'a>>(
|
||||
reader: &mut R,
|
||||
depth: usize,
|
||||
) -> Result<(), Error> {
|
||||
if depth > crate::MAX_NBT_DEPTH {
|
||||
return Err(Error::MaxDepthExceeded);
|
||||
}
|
||||
|
||||
loop {
|
||||
let tag_id = match reader.get_u8() {
|
||||
Ok(id) => id,
|
||||
@@ -65,7 +77,7 @@ impl NbtCompound {
|
||||
reader.skip_string()?;
|
||||
|
||||
// Skip Value
|
||||
NbtTag::skip_data(reader, tag_id)?;
|
||||
NbtTag::skip_data_depth(reader, tag_id, depth + 1)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -73,6 +85,18 @@ impl NbtCompound {
|
||||
|
||||
/// Deserializes a compound payload, starting after the compound's name.
|
||||
pub fn deserialize_content<'a, R: NbtReadHelper<'a>>(reader: &mut R) -> Result<Self, Error> {
|
||||
Self::deserialize_content_depth(reader, 0)
|
||||
}
|
||||
|
||||
/// Deserializes a compound payload with depth tracking.
|
||||
pub fn deserialize_content_depth<'a, R: NbtReadHelper<'a>>(
|
||||
reader: &mut R,
|
||||
depth: usize,
|
||||
) -> Result<Self, Error> {
|
||||
if depth > crate::MAX_NBT_DEPTH {
|
||||
return Err(Error::MaxDepthExceeded);
|
||||
}
|
||||
|
||||
let mut compound = Self::new();
|
||||
|
||||
loop {
|
||||
@@ -87,7 +111,7 @@ impl NbtCompound {
|
||||
}
|
||||
|
||||
let name = reader.get_string()?;
|
||||
let tag = NbtTag::deserialize_data(reader, tag_id)?;
|
||||
let tag = NbtTag::deserialize_data_depth(reader, tag_id, depth + 1)?;
|
||||
|
||||
compound.child_tags.insert(name.into(), tag);
|
||||
}
|
||||
|
||||
@@ -63,7 +63,9 @@ pub const INT_ARRAY_ID: u8 = 0x0B;
|
||||
pub const LONG_ARRAY_ID: u8 = 0x0C;
|
||||
|
||||
/// Maximum number of elements accepted when decoding a list or array.
|
||||
pub const MAX_ARRAY_LENGTH: usize = 2_000_000;
|
||||
pub const MAX_ARRAY_LENGTH: usize = 512_000;
|
||||
/// Maximum nesting depth allowed when decoding NBT compound or list tags.
|
||||
pub const MAX_NBT_DEPTH: usize = 512;
|
||||
|
||||
/// Errors produced while reading, writing, or converting NBT data.
|
||||
#[derive(Error, Debug)]
|
||||
@@ -101,6 +103,12 @@ pub enum Error {
|
||||
/// A Bedrock variable-length long exceeded its maximum encoded size.
|
||||
#[error("Failed to decode varlong - value too large")]
|
||||
VarLongTooLarge,
|
||||
/// NBT nesting depth exceeded the maximum allowed limit.
|
||||
#[error("NBT depth exceeded maximum allowed limit")]
|
||||
MaxDepthExceeded,
|
||||
/// A list tag specified an invalid element tag type.
|
||||
#[error("Invalid element tag type for list: {0}")]
|
||||
InvalidListTag(u8),
|
||||
}
|
||||
|
||||
/// A complete NBT document containing a named root compound.
|
||||
|
||||
@@ -6,8 +6,7 @@ use serializer::NbtWriteHelper;
|
||||
|
||||
use crate::{
|
||||
BYTE_ARRAY_ID, BYTE_ID, COMPOUND_ID, DOUBLE_ID, END_ID, Error, FLOAT_ID, INT_ARRAY_ID, INT_ID,
|
||||
LIST_ID, LONG_ARRAY_ID, LONG_ID, MAX_ARRAY_LENGTH, SHORT_ID, STRING_ID, compound, deserializer,
|
||||
serializer,
|
||||
LIST_ID, LONG_ARRAY_ID, LONG_ID, SHORT_ID, STRING_ID, compound, deserializer, serializer,
|
||||
};
|
||||
|
||||
/// A value represented by one of the tag types defined by the NBT format.
|
||||
@@ -203,6 +202,19 @@ impl NbtTag {
|
||||
|
||||
/// Advances a reader past the payload belonging to `tag_id`.
|
||||
pub fn skip_data<'a, R: NbtReadHelper<'a>>(reader: &mut R, tag_id: u8) -> Result<(), Error> {
|
||||
Self::skip_data_depth(reader, tag_id, 0)
|
||||
}
|
||||
|
||||
/// Advances a reader past the payload belonging to `tag_id` with depth tracking.
|
||||
pub fn skip_data_depth<'a, R: NbtReadHelper<'a>>(
|
||||
reader: &mut R,
|
||||
tag_id: u8,
|
||||
depth: usize,
|
||||
) -> Result<(), Error> {
|
||||
if depth > crate::MAX_NBT_DEPTH {
|
||||
return Err(Error::MaxDepthExceeded);
|
||||
}
|
||||
|
||||
match tag_id {
|
||||
END_ID => Ok(()),
|
||||
BYTE_ID => reader.skip_i8(),
|
||||
@@ -216,7 +228,11 @@ impl NbtTag {
|
||||
if len < 0 {
|
||||
return Err(Error::NegativeLength(len));
|
||||
}
|
||||
reader.skip_bytes(i64::from(len))
|
||||
let len = len as usize;
|
||||
if len > crate::MAX_ARRAY_LENGTH {
|
||||
return Err(Error::LargeLength(len));
|
||||
}
|
||||
reader.skip_bytes(len as i64)
|
||||
}
|
||||
STRING_ID => reader.skip_string(),
|
||||
LIST_ID => {
|
||||
@@ -225,20 +241,33 @@ impl NbtTag {
|
||||
if len < 0 {
|
||||
return Err(Error::NegativeLength(len));
|
||||
}
|
||||
if tag_type_id == END_ID && len > 0 {
|
||||
return Err(Error::InvalidListTag(tag_type_id));
|
||||
}
|
||||
|
||||
let len = len as usize;
|
||||
if len > crate::MAX_ARRAY_LENGTH {
|
||||
return Err(Error::LargeLength(len));
|
||||
}
|
||||
|
||||
for _ in 0..len {
|
||||
Self::skip_data(reader, tag_type_id)?;
|
||||
Self::skip_data_depth(reader, tag_type_id, depth + 1)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
COMPOUND_ID => NbtCompound::skip_content(reader),
|
||||
COMPOUND_ID => NbtCompound::skip_content_depth(reader, depth + 1),
|
||||
INT_ARRAY_ID => {
|
||||
let len = reader.get_i32()?;
|
||||
if len < 0 {
|
||||
return Err(Error::NegativeLength(len));
|
||||
}
|
||||
|
||||
let len = len as usize;
|
||||
if len > crate::MAX_ARRAY_LENGTH {
|
||||
return Err(Error::LargeLength(len));
|
||||
}
|
||||
|
||||
for _ in 0..len {
|
||||
reader.skip_i32()?;
|
||||
}
|
||||
@@ -251,6 +280,11 @@ impl NbtTag {
|
||||
return Err(Error::NegativeLength(len));
|
||||
}
|
||||
|
||||
let len = len as usize;
|
||||
if len > crate::MAX_ARRAY_LENGTH {
|
||||
return Err(Error::LargeLength(len));
|
||||
}
|
||||
|
||||
for _ in 0..len {
|
||||
reader.skip_i64()?;
|
||||
}
|
||||
@@ -266,6 +300,20 @@ impl NbtTag {
|
||||
reader: &mut R,
|
||||
tag_id: u8,
|
||||
) -> Result<Self, Error> {
|
||||
Self::deserialize_data_depth(reader, tag_id, 0)
|
||||
}
|
||||
|
||||
/// Deserializes a payload whose type is identified by `tag_id` with depth tracking.
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn deserialize_data_depth<'a, R: NbtReadHelper<'a>>(
|
||||
reader: &mut R,
|
||||
tag_id: u8,
|
||||
depth: usize,
|
||||
) -> Result<Self, Error> {
|
||||
if depth > crate::MAX_NBT_DEPTH {
|
||||
return Err(Error::MaxDepthExceeded);
|
||||
}
|
||||
|
||||
match tag_id {
|
||||
END_ID => Ok(Self::End),
|
||||
BYTE_ID => {
|
||||
@@ -299,10 +347,10 @@ impl NbtTag {
|
||||
}
|
||||
|
||||
let len = len as usize;
|
||||
if len > MAX_ARRAY_LENGTH {
|
||||
if len > crate::MAX_ARRAY_LENGTH {
|
||||
return Err(Error::LargeLength(len));
|
||||
}
|
||||
let mut byte_array = Vec::with_capacity(len);
|
||||
let mut byte_array = Vec::with_capacity(len.min(4096));
|
||||
for _ in 0..len {
|
||||
let byte = reader.get_i8()?;
|
||||
byte_array.push(byte);
|
||||
@@ -316,22 +364,30 @@ impl NbtTag {
|
||||
if len < 0 {
|
||||
return Err(Error::NegativeLength(len));
|
||||
}
|
||||
if tag_type_id == END_ID && len > 0 {
|
||||
return Err(Error::InvalidListTag(tag_type_id));
|
||||
}
|
||||
|
||||
let len = len as usize;
|
||||
if len > MAX_ARRAY_LENGTH {
|
||||
if len > crate::MAX_ARRAY_LENGTH {
|
||||
return Err(Error::LargeLength(len));
|
||||
}
|
||||
|
||||
let mut list = Vec::with_capacity(len);
|
||||
let mut list = Vec::with_capacity(len.min(4096));
|
||||
for _ in 0..len {
|
||||
let tag = Self::deserialize_data(reader, tag_type_id)?;
|
||||
assert_eq!(tag.get_type_id(), tag_type_id);
|
||||
let tag = Self::deserialize_data_depth(reader, tag_type_id, depth + 1)?;
|
||||
if tag.get_type_id() != tag_type_id {
|
||||
return Err(Error::InvalidListTag(tag.get_type_id()));
|
||||
}
|
||||
// Try unwrapping the tag.
|
||||
list.push(Self::flatten(tag));
|
||||
}
|
||||
Ok(Self::List(list))
|
||||
}
|
||||
COMPOUND_ID => Ok(Self::Compound(NbtCompound::deserialize_content(reader)?)),
|
||||
COMPOUND_ID => Ok(Self::Compound(NbtCompound::deserialize_content_depth(
|
||||
reader,
|
||||
depth + 1,
|
||||
)?)),
|
||||
INT_ARRAY_ID => {
|
||||
let len = reader.get_i32()?;
|
||||
if len < 0 {
|
||||
@@ -339,10 +395,11 @@ impl NbtTag {
|
||||
}
|
||||
|
||||
let len = len as usize;
|
||||
if len > MAX_ARRAY_LENGTH {
|
||||
if len > crate::MAX_ARRAY_LENGTH {
|
||||
return Err(Error::LargeLength(len));
|
||||
}
|
||||
let mut int_array = Vec::with_capacity(len);
|
||||
|
||||
let mut int_array = Vec::with_capacity(len.min(4096));
|
||||
for _ in 0..len {
|
||||
let int = reader.get_i32()?;
|
||||
int_array.push(int);
|
||||
@@ -356,10 +413,11 @@ impl NbtTag {
|
||||
}
|
||||
|
||||
let len = len as usize;
|
||||
if len > MAX_ARRAY_LENGTH {
|
||||
if len > crate::MAX_ARRAY_LENGTH {
|
||||
return Err(Error::LargeLength(len));
|
||||
}
|
||||
let mut long_array = Vec::with_capacity(len);
|
||||
|
||||
let mut long_array = Vec::with_capacity(len.min(4096));
|
||||
for _ in 0..len {
|
||||
let long = reader.get_i64()?;
|
||||
long_array.push(long);
|
||||
|
||||
2832
crates/pumpkin-protocol/fuzz/Cargo.lock
generated
2832
crates/pumpkin-protocol/fuzz/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -19,9 +19,6 @@ libfuzzer-sys = "0.4"
|
||||
[dependencies.pumpkin-protocol]
|
||||
path = ".."
|
||||
|
||||
[dependencies.pumpkin]
|
||||
path = "../../pumpkin"
|
||||
|
||||
[dependencies.pumpkin-util]
|
||||
path = "../../pumpkin-util"
|
||||
|
||||
|
||||
@@ -2,18 +2,14 @@
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use pumpkin_protocol::bedrock::packet_decoder::BedrockBatchDecoder;
|
||||
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,
|
||||
request_chunk_radius::SRequestChunkRadius,
|
||||
request_network_settings::SRequestNetworkSettings,
|
||||
text::SText,
|
||||
SAnimate, SBlockPickRequest, SClientCacheBlobStatus, SClientCacheStatus, SCommandRequest,
|
||||
SContainerClose, SEmote, SEmoteList, SInteraction, SInventoryTransaction, SItemStackRequest,
|
||||
SLoadingScreen, SLogin, SMobEquipment, SModalFormResponse, SPacketViolationWarning,
|
||||
SPlayerAction, SPlayerAuthInput, SPlayerHotbar, SRequestAbility, SRequestChunkRadius,
|
||||
SRequestNetworkSettings, SResourcePackResponse, SRespawn, SSetLocalPlayerAsInitialized,
|
||||
SSetPlayerInventoryOptions, SText,
|
||||
};
|
||||
use pumpkin_protocol::serial::PacketRead;
|
||||
use pumpkin_protocol::serial::{PacketRead, PacketReadSlice};
|
||||
use std::io::Cursor;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -26,25 +22,53 @@ fn fuzz_serverbound_packets(payload: &[u8]) {
|
||||
($($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
|
||||
macro_rules! run_read_slice {
|
||||
($($packet:ty),* $(,)?) => {
|
||||
$(
|
||||
let mut slice = payload;
|
||||
let _ = <$packet>::read_slice(&mut slice);
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
// Standard Bedrock Serverbound Packets (PacketRead)
|
||||
run_read!(
|
||||
SAnimate,
|
||||
SBlockPickRequest,
|
||||
SClientCacheBlobStatus,
|
||||
SClientCacheStatus,
|
||||
SCommandRequest,
|
||||
SContainerClose,
|
||||
SEmote,
|
||||
SEmoteList,
|
||||
SInteraction,
|
||||
SInventoryTransaction,
|
||||
SItemStackRequest,
|
||||
SLoadingScreen,
|
||||
SLogin,
|
||||
SMobEquipment,
|
||||
SModalFormResponse,
|
||||
SPacketViolationWarning,
|
||||
SPlayerAction,
|
||||
SPlayerAuthInput,
|
||||
SPlayerHotbar,
|
||||
SRequestAbility,
|
||||
SRequestChunkRadius,
|
||||
SRequestNetworkSettings,
|
||||
SResourcePackResponse,
|
||||
SRespawn,
|
||||
SSetLocalPlayerAsInitialized,
|
||||
SSetPlayerInventoryOptions,
|
||||
SText,
|
||||
);
|
||||
|
||||
// Bedrock Serverbound Packets supporting zero-copy PacketReadSlice
|
||||
run_read_slice!(SCommandRequest, SEmote, SModalFormResponse, SText,);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -67,8 +91,8 @@ fuzz_target!(|data: &[u8]| {
|
||||
decoder.set_compression((threshold_raw as u32).try_into().unwrap());
|
||||
}
|
||||
// 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) {
|
||||
let mut decoder_cursor = Cursor::new(stream_data.to_vec());
|
||||
if let Ok(raw_packet) = decoder.get_game_packet(&mut decoder_cursor) {
|
||||
// If framed correctly, fuzz the internal payload
|
||||
fuzz_serverbound_packets(&raw_packet.payload);
|
||||
}
|
||||
|
||||
@@ -2,25 +2,40 @@
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use pumpkin_protocol::ServerPacket;
|
||||
use pumpkin_protocol::java::{
|
||||
client::{
|
||||
config::CFinishConfig,
|
||||
login::CLoginDisconnect,
|
||||
play::CPlayerPosition,
|
||||
status::{CPingResponse, CStatusResponse},
|
||||
},
|
||||
packet_decoder::TCPNetworkDecoder,
|
||||
server::{
|
||||
config::{
|
||||
SClientInformationConfig, SConfigCookieResponse, SConfigResourcePack, SKnownPacks,
|
||||
SPluginMessage,
|
||||
SAcknowledgeFinishConfig, SClientInformationConfig, SConfigCookieResponse,
|
||||
SConfigResourcePack, SCustomClickAction as SConfigCustomClickAction,
|
||||
SKeepAlive as SConfigKeepAlive, SKnownPacks, SPluginMessage,
|
||||
},
|
||||
handshake::SHandShake,
|
||||
login::{SEncryptionResponse, SLoginCookieResponse, SLoginPluginResponse, SLoginStart},
|
||||
login::{
|
||||
SEncryptionResponse, SLoginAcknowledged, SLoginCookieResponse, SLoginPluginResponse,
|
||||
SLoginStart,
|
||||
},
|
||||
play::{
|
||||
SChangeGameMode, SChatCommand, SChatMessage, SChunkBatch, SClickSlot, SClientCommand,
|
||||
SClientInformationPlay, SCloseContainer, SCommandSuggestion, SConfirmTeleport,
|
||||
SContainerButtonClick, SCookieResponse, SCustomPayload, SInteract, SKeepAlive,
|
||||
SMoveVehicle, SPaddleBoat, SPickItemFromBlock, SPlayPingRequest, SPlayerAbilities,
|
||||
SPlayerAction, SPlayerCommand, SPlayerInput, SPlayerLoaded, SPlayerPosition,
|
||||
SPlayerPositionRotation, SPlayerRotation, SPlayerSession, SSetCommandBlock,
|
||||
SSetCreativeSlot, SSetHeldItem, SSetPlayerGround, SSwingArm, SUpdateSign, SUseItem,
|
||||
SAttack, SBundleItemSelected, SChangeGameMode, SChatCommand, SChatMessage, SChunkBatch,
|
||||
SClickSlot, SClientCommand, SClientInformationPlay, SClientTickEnd, SCloseContainer,
|
||||
SCommandSuggestion, SConfirmTeleport, SContainerButtonClick, SCookieResponse,
|
||||
SCustomClickAction as SPlayCustomClickAction, SCustomPayload, SDebugSampleSubscription,
|
||||
SDebugSubscriptionRequest, SEditBook, SInteract, SJigsawGenerate,
|
||||
SKeepAlive as SPlayKeepAlive, SMoveVehicle, SPaddleBoat, SPickItemFromBlock,
|
||||
SPickItemFromEntity, SPlaceRecipe, SPlayPingRequest, SPlayerAbilities, SPlayerAction,
|
||||
SPlayerCommand, SPlayerInput, SPlayerLoaded, SPlayerPosition, SPlayerPositionRotation,
|
||||
SPlayerRotation, SPlayerSession, SRecipeBookChangeSettings, SRecipeBookSeenRecipe,
|
||||
SRenameItem, SSeenAdvancement, SSelectTrade, SSetBeacon, SSetCommandBlock,
|
||||
SSetCreativeSlot, SSetHeldItem, SSetJigsawBlock, SSetPlayerGround, SSetTestBlock,
|
||||
SSwingArm, STeleportToEntity, STestInstanceBlockAction, SUpdateSign, SUseItem,
|
||||
SUseItemOn,
|
||||
},
|
||||
status::SStatusPingRequest,
|
||||
status::{SStatusPingRequest, SStatusRequest},
|
||||
},
|
||||
};
|
||||
use pumpkin_util::version::JavaMinecraftVersion;
|
||||
@@ -31,16 +46,14 @@ const TARGET_VERSION: JavaMinecraftVersion = JavaMinecraftVersion::V_26_1;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: run every known ServerPacket::read against the same payload.
|
||||
// Uses a Cursor and the Version enum as required by the new signature.
|
||||
// Uses a slice and the Version enum as required by the ServerPacket signature.
|
||||
// ---------------------------------------------------------------------------
|
||||
fn fuzz_all_deserializers(payload: &[u8]) {
|
||||
let mut cursor = Cursor::new(payload);
|
||||
|
||||
macro_rules! run_read {
|
||||
($($packet:ty),* $(,)?) => {
|
||||
$(
|
||||
cursor.set_position(0);
|
||||
let _ = <$packet>::read(&mut cursor, &TARGET_VERSION);
|
||||
let mut slice = payload;
|
||||
let _ = <$packet>::read(&mut slice, &TARGET_VERSION);
|
||||
)*
|
||||
};
|
||||
}
|
||||
@@ -50,54 +63,85 @@ fn fuzz_all_deserializers(payload: &[u8]) {
|
||||
SHandShake,
|
||||
// Status
|
||||
SStatusPingRequest,
|
||||
SStatusRequest,
|
||||
// Login
|
||||
SLoginStart,
|
||||
SEncryptionResponse,
|
||||
SLoginPluginResponse,
|
||||
SLoginCookieResponse,
|
||||
SLoginAcknowledged,
|
||||
// Config
|
||||
SAcknowledgeFinishConfig,
|
||||
SClientInformationConfig,
|
||||
SPluginMessage,
|
||||
SKnownPacks,
|
||||
SConfigCookieResponse,
|
||||
SConfigCustomClickAction,
|
||||
SConfigKeepAlive,
|
||||
SKnownPacks,
|
||||
SPluginMessage,
|
||||
SConfigResourcePack,
|
||||
// Play
|
||||
SConfirmTeleport,
|
||||
SAttack,
|
||||
SBundleItemSelected,
|
||||
SChangeGameMode,
|
||||
SChatCommand,
|
||||
SChatMessage,
|
||||
SClientInformationPlay,
|
||||
SChunkBatch,
|
||||
SClickSlot,
|
||||
SClientCommand,
|
||||
SPlayerInput,
|
||||
SClientInformationPlay,
|
||||
SClientTickEnd,
|
||||
SCloseContainer,
|
||||
SCommandSuggestion,
|
||||
SConfirmTeleport,
|
||||
SContainerButtonClick,
|
||||
SCookieResponse,
|
||||
SPlayCustomClickAction,
|
||||
SCustomPayload,
|
||||
SDebugSampleSubscription,
|
||||
SDebugSubscriptionRequest,
|
||||
SEditBook,
|
||||
SInteract,
|
||||
SJigsawGenerate,
|
||||
SPlayKeepAlive,
|
||||
SMoveVehicle,
|
||||
SPaddleBoat,
|
||||
SInteract,
|
||||
SKeepAlive,
|
||||
SPickItemFromBlock,
|
||||
SPickItemFromEntity,
|
||||
SPlayPingRequest,
|
||||
SPlaceRecipe,
|
||||
SPlayerAbilities,
|
||||
SPlayerAction,
|
||||
SPlayerCommand,
|
||||
SPlayerInput,
|
||||
SPlayerLoaded,
|
||||
SPlayerPosition,
|
||||
SPlayerPositionRotation,
|
||||
SPlayerRotation,
|
||||
SSetPlayerGround,
|
||||
SPickItemFromBlock,
|
||||
SPlayerAbilities,
|
||||
SPlayerAction,
|
||||
SSetCommandBlock,
|
||||
SPlayerCommand,
|
||||
SPlayerLoaded,
|
||||
SPlayPingRequest,
|
||||
SClickSlot,
|
||||
SContainerButtonClick,
|
||||
SSetHeldItem,
|
||||
SSetCreativeSlot,
|
||||
SSwingArm,
|
||||
SUpdateSign,
|
||||
SUseItemOn,
|
||||
SUseItem,
|
||||
SCommandSuggestion,
|
||||
SCookieResponse,
|
||||
SCloseContainer,
|
||||
SChunkBatch,
|
||||
SPlayerSession,
|
||||
SCustomPayload,
|
||||
SRecipeBookChangeSettings,
|
||||
SRecipeBookSeenRecipe,
|
||||
SRenameItem,
|
||||
SSeenAdvancement,
|
||||
SSelectTrade,
|
||||
SSetBeacon,
|
||||
SSetCommandBlock,
|
||||
SSetCreativeSlot,
|
||||
SSetHeldItem,
|
||||
SSetJigsawBlock,
|
||||
SSetPlayerGround,
|
||||
SSetTestBlock,
|
||||
SSwingArm,
|
||||
STeleportToEntity,
|
||||
STestInstanceBlockAction,
|
||||
SUpdateSign,
|
||||
SUseItem,
|
||||
SUseItemOn,
|
||||
// Clientbound packets implementing ServerPacket
|
||||
CFinishConfig,
|
||||
CLoginDisconnect,
|
||||
CPlayerPosition,
|
||||
CPingResponse,
|
||||
CStatusResponse,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -131,13 +175,13 @@ fuzz_target!(|data: &[u8]| {
|
||||
2 => {
|
||||
let mut aes_key = [0u8; 16];
|
||||
aes_key.copy_from_slice(key);
|
||||
decoder.set_encryption(&aes_key);
|
||||
let _ = 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.set_encryption(&aes_key);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use pumpkin_protocol::bedrock::packet_encoder::BedrockBatchEncoder;
|
||||
use pumpkin_protocol::bedrock::SubClient;
|
||||
use pumpkin_protocol::bedrock::packet_encoder::BedrockBatchEncoder;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
if data.len() < 10 {
|
||||
@@ -33,11 +33,5 @@ fuzz_target!(|data: &[u8]| {
|
||||
}
|
||||
|
||||
let mut out = Vec::new();
|
||||
let _ = encoder.write_game_packet(
|
||||
packet_id,
|
||||
sender,
|
||||
target,
|
||||
packet_payload,
|
||||
&mut out
|
||||
);
|
||||
let _ = encoder.write_game_packet(packet_id, sender, target, packet_payload, &mut out);
|
||||
});
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use pumpkin_protocol::java::packet_encoder::TCPNetworkEncoder;
|
||||
use pumpkin_protocol::codec::var_int::VarInt;
|
||||
use pumpkin_protocol::ser::NetworkWriteExt;
|
||||
use pumpkin_protocol::ServerPacket;
|
||||
use pumpkin_protocol::java::client::play::CPlayerPosition;
|
||||
use pumpkin_protocol::packet::MultiVersionJavaPacket;
|
||||
use pumpkin_protocol::ClientPacket;
|
||||
use pumpkin_protocol::ServerPacket;
|
||||
use pumpkin_protocol::codec::var_int::VarInt;
|
||||
use pumpkin_protocol::java::client::play::CPlayerPosition;
|
||||
use pumpkin_protocol::java::packet_encoder::TCPNetworkEncoder;
|
||||
use pumpkin_protocol::packet::MultiVersionJavaPacket;
|
||||
use pumpkin_protocol::ser::NetworkWriteExt;
|
||||
use pumpkin_util::version::JavaMinecraftVersion;
|
||||
use std::io::Cursor;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
const TARGET_VERSION: JavaMinecraftVersion = JavaMinecraftVersion::V_1_21_4;
|
||||
@@ -34,24 +33,28 @@ fuzz_target!(|data: &[u8]| {
|
||||
encoder.set_compression((compression_threshold, compression_level));
|
||||
}
|
||||
if use_encryption {
|
||||
encoder.set_encryption(&encryption_key);
|
||||
let _ = encoder.set_encryption(&encryption_key);
|
||||
}
|
||||
|
||||
// 1. Fuzz with raw bytes (simulating various packet payloads)
|
||||
let mut buf = Vec::new();
|
||||
let packet_id = if packet_data.is_empty() { 0 } else { packet_data[0] as i32 };
|
||||
let packet_id = if packet_data.is_empty() {
|
||||
0
|
||||
} else {
|
||||
packet_data[0] as i32
|
||||
};
|
||||
let _ = buf.write_var_int(&VarInt(packet_id));
|
||||
buf.extend_from_slice(packet_data);
|
||||
let _ = encoder.write_packet(buf.into()).await;
|
||||
|
||||
// 2. Fuzz with actual packets if they can be partially read
|
||||
let mut cursor = Cursor::new(packet_data);
|
||||
if let Ok(packet) = CPlayerPosition::read(&mut cursor, &TARGET_VERSION) {
|
||||
let mut packet_buf = Vec::new();
|
||||
let id = CPlayerPosition::to_id(TARGET_VERSION);
|
||||
let _ = packet_buf.write_var_int(&VarInt(id));
|
||||
let _ = packet.write_packet_data(&mut packet_buf, &TARGET_VERSION);
|
||||
let _ = encoder.write_packet(packet_buf.into()).await;
|
||||
let mut slice = packet_data;
|
||||
if let Ok(packet) = CPlayerPosition::read(&mut slice, &TARGET_VERSION) {
|
||||
let mut packet_buf = Vec::new();
|
||||
let id = CPlayerPosition::to_id(TARGET_VERSION);
|
||||
let _ = packet_buf.write_var_int(&VarInt(id));
|
||||
let _ = packet.write_packet_data(&mut packet_buf, &TARGET_VERSION);
|
||||
let _ = encoder.write_packet(packet_buf.into()).await;
|
||||
}
|
||||
|
||||
let _ = encoder.flush().await;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use pumpkin::net::rcon::packet::Packet;
|
||||
use pumpkin_protocol::rcon::Packet;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
let mut incoming = data.to_vec();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::io::{Error, Read, Write};
|
||||
use std::io::{Error, ErrorKind, Read, Write};
|
||||
use std::num::NonZeroI32;
|
||||
|
||||
use pumpkin_data::item::{BedrockItem, JavaToBedrockItemMapping};
|
||||
@@ -47,8 +47,14 @@ impl PacketRead for NetworkItemDescriptor {
|
||||
|
||||
let block_runtime_id = VarInt(VarUInt::read(buf)?.0 as i32);
|
||||
|
||||
let user_data_len = VarUInt::read(buf)?.0;
|
||||
let mut user_data = vec![0u8; user_data_len as usize];
|
||||
let user_data_len = VarUInt::read(buf)?.0 as usize;
|
||||
if user_data_len > 1_048_576 {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
"user_data_len exceeds 1MB limit",
|
||||
));
|
||||
}
|
||||
let mut user_data = vec![0u8; user_data_len];
|
||||
buf.read_exact(&mut user_data)?;
|
||||
|
||||
let (nbt_data, place_on_blocks, destroy_blocks, shield_blocking_tick) =
|
||||
@@ -193,8 +199,14 @@ impl PacketRead for ItemStackWrapper {
|
||||
|
||||
let block_runtime_id = VarInt(VarUInt::read(buf)?.0 as i32);
|
||||
|
||||
let user_data_len = VarUInt::read(buf)?.0;
|
||||
let mut user_data = vec![0u8; user_data_len as usize];
|
||||
let user_data_len = VarUInt::read(buf)?.0 as usize;
|
||||
if user_data_len > 1_048_576 {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
"user_data_len exceeds 1MB limit",
|
||||
));
|
||||
}
|
||||
let mut user_data = vec![0u8; user_data_len];
|
||||
buf.read_exact(&mut user_data)?;
|
||||
|
||||
let (nbt_data, place_on_blocks, destroy_blocks, shield_blocking_tick) =
|
||||
@@ -285,8 +297,14 @@ impl PacketRead for NetworkItemStackDescriptor {
|
||||
|
||||
let block_runtime_id = VarUInt::read(buf)?;
|
||||
|
||||
let extra_data_len = VarUInt::read(buf)?.0;
|
||||
let mut extra_data = vec![0u8; extra_data_len as usize];
|
||||
let extra_data_len = VarUInt::read(buf)?.0 as usize;
|
||||
if extra_data_len > 1_048_576 {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
"extra_data_len exceeds 1MB limit",
|
||||
));
|
||||
}
|
||||
let mut extra_data = vec![0u8; extra_data_len];
|
||||
buf.read_exact(&mut extra_data)?;
|
||||
|
||||
Ok(Self {
|
||||
@@ -513,8 +531,14 @@ impl PacketRead for NetworkItemStack {
|
||||
let aux_value = VarUInt::read(buf)?;
|
||||
let block_runtime_id = VarInt::read(buf)?;
|
||||
|
||||
let extra_data_len = VarUInt::read(buf)?.0;
|
||||
let mut extra_data = vec![0u8; extra_data_len as usize];
|
||||
let extra_data_len = VarUInt::read(buf)?.0 as usize;
|
||||
if extra_data_len > 1_048_576 {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
"extra_data_len exceeds 1MB limit",
|
||||
));
|
||||
}
|
||||
let mut extra_data = vec![0u8; extra_data_len];
|
||||
buf.read_exact(&mut extra_data)?;
|
||||
|
||||
Ok(Self {
|
||||
@@ -541,17 +565,24 @@ fn write_user_data_strings<W: Write>(writer: &mut W, values: &[String]) -> Resul
|
||||
|
||||
fn read_user_data_strings<R: Read>(reader: &mut R) -> Result<Vec<String>, Error> {
|
||||
let len = i32::read(reader)?;
|
||||
if len < 0 {
|
||||
if !(0..=1024).contains(&len) {
|
||||
return Err(Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"negative item string array length",
|
||||
"item string array length out of bounds",
|
||||
));
|
||||
}
|
||||
let mut values = Vec::with_capacity(len as usize);
|
||||
let mut values = Vec::with_capacity((len as usize).min(32));
|
||||
for _ in 0..len {
|
||||
let mut length = [0; 2];
|
||||
reader.read_exact(&mut length)?;
|
||||
let mut bytes = vec![0; usize::from(u16::from_be_bytes(length))];
|
||||
let str_len = usize::from(u16::from_be_bytes(length));
|
||||
if str_len > 32767 {
|
||||
return Err(Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"item string too long",
|
||||
));
|
||||
}
|
||||
let mut bytes = vec![0; str_len];
|
||||
reader.read_exact(&mut bytes)?;
|
||||
values.push(
|
||||
String::from_utf8(bytes)
|
||||
|
||||
@@ -13,7 +13,13 @@ pub struct SClientCacheBlobStatus {
|
||||
impl PacketRead for SClientCacheBlobStatus {
|
||||
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
|
||||
let miss_count = VarUInt::read(reader)?.0 as usize;
|
||||
let mut miss_hashes = Vec::with_capacity(miss_count);
|
||||
if miss_count > 4096 {
|
||||
return Err(Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"miss_count exceeds limit",
|
||||
));
|
||||
}
|
||||
let mut miss_hashes = Vec::with_capacity(miss_count.min(256));
|
||||
for _ in 0..miss_count {
|
||||
let mut bytes = [0u8; 8];
|
||||
reader.read_exact(&mut bytes)?;
|
||||
@@ -21,7 +27,13 @@ impl PacketRead for SClientCacheBlobStatus {
|
||||
}
|
||||
|
||||
let hit_count = VarUInt::read(reader)?.0 as usize;
|
||||
let mut hit_hashes = Vec::with_capacity(hit_count);
|
||||
if hit_count > 4096 {
|
||||
return Err(Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"hit_count exceeds limit",
|
||||
));
|
||||
}
|
||||
let mut hit_hashes = Vec::with_capacity(hit_count.min(256));
|
||||
for _ in 0..hit_count {
|
||||
let mut bytes = [0u8; 8];
|
||||
reader.read_exact(&mut bytes)?;
|
||||
|
||||
@@ -53,6 +53,12 @@ impl PacketRead for StackRequestItem {
|
||||
let count = i16::read(reader)? as u16;
|
||||
let block_runtime_id = VarUInt::read(reader)?;
|
||||
let data_len = VarUInt::read(reader)?.0 as usize;
|
||||
if data_len > 1_048_576 {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
"extra_data length exceeds limit",
|
||||
));
|
||||
}
|
||||
let mut extra_data = vec![0; data_len];
|
||||
reader.read_exact(&mut extra_data)?;
|
||||
Ok(Self {
|
||||
|
||||
@@ -94,7 +94,13 @@ impl PacketRead for SPlayerAuthInput {
|
||||
// 3. Block Actions
|
||||
let block_actions = if bool::read(reader)? && bool::read(reader)? {
|
||||
let count = VarUInt::read(reader)?.0 as usize;
|
||||
let mut actions = Vec::with_capacity(count);
|
||||
if count > 1024 {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
"block_actions count exceeds limit",
|
||||
));
|
||||
}
|
||||
let mut actions = Vec::with_capacity(count.min(64));
|
||||
for _ in 0..count {
|
||||
actions.push(PlayerBlockAction::read(reader)?);
|
||||
}
|
||||
@@ -155,7 +161,14 @@ impl PacketRead for PlayerInventoryAction {
|
||||
let legacy_request_id = VarInt::read(buf)?;
|
||||
let mut legacy_slots = Vec::new();
|
||||
if bool::read(buf)? && legacy_request_id.0 < -1 && (legacy_request_id.0 & 1) == 0 {
|
||||
let slots_len = VarUInt::read(buf)?.0;
|
||||
let slots_len = VarUInt::read(buf)?.0 as usize;
|
||||
if slots_len > 1024 {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
"slots_len exceeds limit",
|
||||
));
|
||||
}
|
||||
legacy_slots.reserve(slots_len.min(64));
|
||||
for _ in 0..slots_len {
|
||||
legacy_slots.push(
|
||||
crate::bedrock::server::inventory_transaction::LegacySetItemSlot::read(buf)?,
|
||||
@@ -164,8 +177,14 @@ impl PacketRead for PlayerInventoryAction {
|
||||
}
|
||||
let mut actions = Vec::new();
|
||||
if bool::read(buf)? && bool::read(buf)? {
|
||||
let actions_len = VarUInt::read(buf)?.0;
|
||||
actions.reserve(actions_len as usize);
|
||||
let actions_len = VarUInt::read(buf)?.0 as usize;
|
||||
if actions_len > 1024 {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
"actions_len exceeds limit",
|
||||
));
|
||||
}
|
||||
actions.reserve(actions_len.min(64));
|
||||
for _ in 0..actions_len {
|
||||
actions.push(
|
||||
crate::bedrock::server::inventory_transaction::InventoryAction::read(buf)?,
|
||||
|
||||
@@ -13,12 +13,22 @@ pub struct SResourcePackResponse {
|
||||
impl PacketRead for SResourcePackResponse {
|
||||
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
|
||||
let encoded_status = VarUInt::read(reader)?.0;
|
||||
let response = u8::try_from(encoded_status + 1)
|
||||
.map_err(|_| Error::new(ErrorKind::InvalidData, "resource pack status is too large"))?;
|
||||
let response = encoded_status
|
||||
.checked_add(1)
|
||||
.and_then(|v| u8::try_from(v).ok())
|
||||
.ok_or_else(|| {
|
||||
Error::new(ErrorKind::InvalidData, "resource pack status is too large")
|
||||
})?;
|
||||
let _status_name = String::read(reader)?;
|
||||
|
||||
let pack_ids = if response == Self::STATUS_SEND_PACKS {
|
||||
let count = VarUInt::read(reader)?.0;
|
||||
if count > 1024 {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
"too many resource pack identifiers",
|
||||
));
|
||||
}
|
||||
(0..count)
|
||||
.map(|_| String::read(reader))
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
|
||||
@@ -578,22 +578,32 @@ impl DataComponentCodec<Self> for PotionContentsImpl {
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to skip hidden effect parameters recursively
|
||||
/// Helper to skip hidden effect parameters iteratively with a depth cap
|
||||
fn skip_effect_parameters(seq: &mut impl NetworkReadExt) -> Result<(), ReadingError> {
|
||||
// amplifier
|
||||
seq.get_var_int()?;
|
||||
// duration
|
||||
seq.get_var_int()?;
|
||||
// ambient
|
||||
seq.get_bool()?;
|
||||
// show_particles
|
||||
seq.get_bool()?;
|
||||
// show_icon
|
||||
seq.get_bool()?;
|
||||
// has_hidden (recursive)
|
||||
let has_hidden = seq.get_bool()?;
|
||||
if has_hidden {
|
||||
skip_effect_parameters(seq)?;
|
||||
const MAX_EFFECT_DEPTH: usize = 32;
|
||||
let mut depth = 0;
|
||||
loop {
|
||||
// amplifier
|
||||
seq.get_var_int()?;
|
||||
// duration
|
||||
seq.get_var_int()?;
|
||||
// ambient
|
||||
seq.get_bool()?;
|
||||
// show_particles
|
||||
seq.get_bool()?;
|
||||
// show_icon
|
||||
seq.get_bool()?;
|
||||
// has_hidden
|
||||
let has_hidden = seq.get_bool()?;
|
||||
if !has_hidden {
|
||||
break;
|
||||
}
|
||||
depth += 1;
|
||||
if depth > MAX_EFFECT_DEPTH {
|
||||
return Err(ReadingError::TooLarge(
|
||||
"Potion effect hidden depth exceeded".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -109,6 +109,9 @@ fn read_length_prefixed_component(
|
||||
let byte_len: usize = byte_len
|
||||
.try_into()
|
||||
.map_err(|_| ReadingError::Message("Negative component data length".into()))?;
|
||||
if byte_len > crate::MAX_PACKET_DATA_SIZE {
|
||||
return Err(ReadingError::TooLarge("Component data too large".into()));
|
||||
}
|
||||
|
||||
let component_impl = if byte_len <= 256 {
|
||||
let mut stack_buf = [0u8; 256];
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
use crate::{ServerPacket, ser::ReadingError};
|
||||
use pumpkin_data::packet::serverbound::PLAY_CLIENT_TICK_END;
|
||||
use pumpkin_macros::java_packet;
|
||||
use pumpkin_util::version::JavaMinecraftVersion;
|
||||
|
||||
#[java_packet(PLAY_CLIENT_TICK_END)]
|
||||
pub struct SClientTickEnd;
|
||||
|
||||
impl<'a> ServerPacket<'a> for SClientTickEnd {
|
||||
fn read(
|
||||
_bytebuf: &mut &'a [u8],
|
||||
_protocol_version: &JavaMinecraftVersion,
|
||||
) -> Result<Self, ReadingError> {
|
||||
Ok(Self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,19 @@ impl<'a> ServerPacket<'a> for SPlayerSession {
|
||||
let session_id = read.get_uuid()?;
|
||||
let expires_at = read.get_i64_be()?;
|
||||
|
||||
let public_key_length = read.get_var_int()?.0 as usize;
|
||||
let public_key_length = usize::try_from(read.get_var_int()?.0)
|
||||
.map_err(|_| ReadingError::Message("Negative public key length".into()))?;
|
||||
if public_key_length > 2048 {
|
||||
return Err(ReadingError::TooLarge("Public key too long".into()));
|
||||
}
|
||||
let mut public_key = vec![0u8; public_key_length];
|
||||
read.read_bytes_to_buf(&mut public_key)?;
|
||||
|
||||
let key_signature_length = read.get_var_int()?.0 as usize;
|
||||
let key_signature_length = usize::try_from(read.get_var_int()?.0)
|
||||
.map_err(|_| ReadingError::Message("Negative key signature length".into()))?;
|
||||
if key_signature_length > 4096 {
|
||||
return Err(ReadingError::TooLarge("Key signature too long".into()));
|
||||
}
|
||||
let mut key_signature = vec![0u8; key_signature_length];
|
||||
read.read_bytes_to_buf(&mut key_signature)?;
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ pub mod java;
|
||||
pub mod packet;
|
||||
#[cfg(feature = "query")]
|
||||
pub mod query;
|
||||
pub mod rcon;
|
||||
pub mod ser;
|
||||
pub mod serial;
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ pub enum ClientboundPacket {
|
||||
impl ClientboundPacket {
|
||||
#[must_use]
|
||||
pub fn write_buf(self, id: i32, body: &str) -> BytesMut {
|
||||
// let len = outgoing.len() as u64;
|
||||
let mut buf = BytesMut::new();
|
||||
// 10 is for 4 bytes ty, 4 bytes id, and 2 terminating nul bytes.
|
||||
buf.put_i32_le(10 + body.len() as i32);
|
||||
@@ -53,9 +52,9 @@ impl ClientboundPacket {
|
||||
pub enum PacketError {
|
||||
#[error("Invalid length")]
|
||||
InvalidLength,
|
||||
#[error("Dailed to send packet")]
|
||||
#[error("Failed to send packet")]
|
||||
FailedSend(std::io::Error),
|
||||
#[error("Missing packet lull terminator")]
|
||||
#[error("Missing packet null terminator")]
|
||||
MissingNullTerminator,
|
||||
#[error("Invalid packet string body")]
|
||||
InvalidBody(std::str::Utf8Error),
|
||||
@@ -105,7 +105,7 @@ pub trait NetworkReadExt {
|
||||
fn get_str_bounded(&mut self, bound: usize) -> Result<Box<str>, ReadingError>;
|
||||
#[inline]
|
||||
fn get_str(&mut self) -> Result<Box<str>, ReadingError> {
|
||||
self.get_str_bounded(i32::MAX as usize)
|
||||
self.get_str_bounded(32767)
|
||||
}
|
||||
fn get_uuid(&mut self) -> Result<uuid::Uuid, ReadingError>;
|
||||
fn get_fixed_bitset(&mut self, bits: usize) -> Result<FixedBitSet, ReadingError>;
|
||||
@@ -164,7 +164,7 @@ pub trait NetworkReadSliceExt<'a> {
|
||||
|
||||
#[inline]
|
||||
fn get_cow_str_borrowed(&mut self) -> Result<Cow<'a, str>, ReadingError> {
|
||||
self.get_cow_str_bounded_borrowed(i32::MAX as usize)
|
||||
self.get_cow_str_bounded_borrowed(32767)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -203,7 +203,7 @@ impl<'a> NetworkReadSliceExt<'a> for &'a [u8] {
|
||||
fn get_str_bounded_borrowed(&mut self, bound: usize) -> Result<&'a str, ReadingError> {
|
||||
let bytes_len = self.get_var_uint()?.0 as usize;
|
||||
|
||||
let maximum_utf8_bytes = bound.saturating_mul(3);
|
||||
let maximum_utf8_bytes = bound.saturating_mul(3).min(crate::MAX_PACKET_DATA_SIZE);
|
||||
if bytes_len > maximum_utf8_bytes {
|
||||
return Err(ReadingError::TooLarge(format!(
|
||||
"string has too many bytes ({bytes_len} > {maximum_utf8_bytes})"
|
||||
@@ -225,7 +225,7 @@ impl<'a> NetworkReadSliceExt<'a> for &'a [u8] {
|
||||
|
||||
#[inline]
|
||||
fn get_str_borrowed(&mut self) -> Result<&'a str, ReadingError> {
|
||||
self.get_str_bounded_borrowed(i32::MAX as usize)
|
||||
self.get_str_bounded_borrowed(32767)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,7 +314,7 @@ impl<R: Read> NetworkReadExt for R {
|
||||
|
||||
// First, check if there are too many bytes to even fit in the UTF-16 bound.
|
||||
// 1 Java `char` takes a maximum of 3 bytes in UTF-8:
|
||||
let maximum_utf8_bytes = bound.saturating_mul(3);
|
||||
let maximum_utf8_bytes = bound.saturating_mul(3).min(crate::MAX_PACKET_DATA_SIZE);
|
||||
if bytes_len > maximum_utf8_bytes {
|
||||
return Err(ReadingError::TooLarge(format!(
|
||||
"string has too many bytes ({bytes_len} > {maximum_utf8_bytes})"
|
||||
|
||||
@@ -172,8 +172,14 @@ impl PacketRead for String {
|
||||
|
||||
impl<T: PacketRead> PacketRead for Vec<T> {
|
||||
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
|
||||
let len = VarUInt::read(reader)?.0 as _;
|
||||
let mut buf = Self::with_capacity(len);
|
||||
let len = VarUInt::read(reader)?.0 as usize;
|
||||
if len > 65536 {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
format!("Vector length {len} exceeds limit of 65536"),
|
||||
));
|
||||
}
|
||||
let mut buf = Self::with_capacity(len.min(1024));
|
||||
for _ in 0..len {
|
||||
buf.push(T::read(reader)?);
|
||||
}
|
||||
|
||||
@@ -72,8 +72,8 @@ rsa.workspace = true
|
||||
# verification & WASM parsing
|
||||
ed25519-dalek.workspace = true
|
||||
hex.workspace = true
|
||||
wasmparser = "0.255"
|
||||
wasm-encoder = "0.255"
|
||||
wasmparser = "0.256"
|
||||
wasm-encoder = "0.256"
|
||||
|
||||
# authentication
|
||||
ureq = { workspace = true, features = ["json"] }
|
||||
|
||||
@@ -47,7 +47,7 @@ pub struct MojangPublicKeys {
|
||||
}
|
||||
|
||||
const MOJANG_AUTHENTICATION_URL: &str = "https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}";
|
||||
const MOJANG_PREVENT_PROXY_AUTHENTICATION_URL: &str = "https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}";
|
||||
const MOJANG_PREVENT_PROXY_AUTHENTICATION_URL: &str = "https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}&ip={ip}";
|
||||
const MOJANG_SERVICES_URL: &str = "https://api.minecraftservices.com/";
|
||||
const MOJANG_PROFILE_BY_NAME_URL: &str =
|
||||
"https://api.mojang.com/users/profiles/minecraft/{username}";
|
||||
|
||||
@@ -811,7 +811,7 @@ impl BedrockClient {
|
||||
Ok(packet) => Some(packet),
|
||||
Err(err) => {
|
||||
if !matches!(err, PacketDecodeError::ConnectionClosed) {
|
||||
warn!("Failed to decode packet from client: {err}");
|
||||
debug!("Failed to decode packet from client: {err}");
|
||||
let text = format!("Error while reading incoming packet {err}");
|
||||
self.kick(DisconnectReason::BadPacket, text).await;
|
||||
}
|
||||
|
||||
@@ -411,7 +411,7 @@ impl JavaClient {
|
||||
Ok(packet) => Some(packet),
|
||||
Err(err) => {
|
||||
if !matches!(err, PacketDecodeError::ConnectionClosed) {
|
||||
warn!("Failed to decode packet from client {}: {}", self.id, err);
|
||||
debug!("Failed to decode packet from client {}: {}", self.id, err);
|
||||
let text = format!("Error while reading incoming packet {err}");
|
||||
self.kick(TextComponent::text(text)).await;
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ impl PendingConnection {
|
||||
Ok(packet) => Some(packet),
|
||||
Err(err) => {
|
||||
if !matches!(err, PacketDecodeError::ConnectionClosed) {
|
||||
warn!("Failed to decode packet from client {}: {}", self.id, err);
|
||||
debug!("Failed to decode packet from client {}: {}", self.id, err);
|
||||
let text = format!("Error while reading incoming packet {err}");
|
||||
self.kick(TextComponent::text(text)).await;
|
||||
}
|
||||
|
||||
@@ -65,6 +65,9 @@ pub async fn velocity_login(connection: &mut PendingConnection) {
|
||||
|
||||
#[must_use]
|
||||
pub fn check_integrity(data: (&[u8], &[u8]), secret: &str) -> bool {
|
||||
if secret.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let (signature, data_without_signature) = data;
|
||||
// Our fault, we can panic/expect?
|
||||
let Ok(mut mac) = HmacSha256::new_from_slice(secret.as_bytes()) else {
|
||||
|
||||
@@ -12,12 +12,19 @@ use tracing::{debug, error, info};
|
||||
use crate::command::CommandSender;
|
||||
use crate::{SHOULD_STOP, STOP_INTERRUPT, server::Server};
|
||||
|
||||
pub mod packet;
|
||||
pub use pumpkin_protocol::rcon as packet;
|
||||
|
||||
pub struct RCONServer;
|
||||
|
||||
impl RCONServer {
|
||||
pub async fn run(config: &RCONConfig, server: Arc<Server>) {
|
||||
if config.password.trim().is_empty() {
|
||||
error!(
|
||||
"RCON is enabled but password is empty! Refusing to start RCON server for security."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let listener = match tokio::net::TcpListener::bind(config.address).await {
|
||||
Ok(l) => l,
|
||||
Err(e) => {
|
||||
@@ -109,7 +116,7 @@ impl RCONClient {
|
||||
let config = &server.advanced_config.networking.rcon;
|
||||
match packet.get_type() {
|
||||
ServerboundPacket::Auth => {
|
||||
if packet.get_body() == password {
|
||||
if !password.is_empty() && packet.get_body() == password {
|
||||
self.send(ClientboundPacket::AuthResponse, packet.get_id(), "")
|
||||
.await?;
|
||||
if config.logging.logged_successfully {
|
||||
|
||||
22
tools/pumpkin-fuzzer/Cargo.toml
Normal file
22
tools/pumpkin-fuzzer/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "pumpkin-fuzzer"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pumpkin-protocol.workspace = true
|
||||
pumpkin-util.workspace = true
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
colored.workspace = true
|
||||
bytes.workspace = true
|
||||
rand = "0.9"
|
||||
|
||||
[lints.clippy]
|
||||
expect_used = "allow"
|
||||
unwrap_used = "allow"
|
||||
panic = "allow"
|
||||
pedantic = { level = "allow", priority = -1 }
|
||||
all = { level = "allow", priority = -1 }
|
||||
385
tools/pumpkin-fuzzer/src/main.rs
Normal file
385
tools/pumpkin-fuzzer/src/main.rs
Normal file
@@ -0,0 +1,385 @@
|
||||
use clap::{Parser, ValueEnum};
|
||||
use colored::Colorize;
|
||||
use rand::Rng;
|
||||
use std::{
|
||||
net::SocketAddr,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::TcpStream,
|
||||
time::timeout,
|
||||
};
|
||||
|
||||
#[derive(ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum FuzzMode {
|
||||
All,
|
||||
Raw,
|
||||
Framed,
|
||||
Stateful,
|
||||
CorruptVarint,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about = "Pumpkin Server Network Packet Fuzzer & Stress Tool", long_about = None)]
|
||||
struct Args {
|
||||
/// Target server host
|
||||
#[arg(long, default_value = "127.0.0.1")]
|
||||
host: String,
|
||||
|
||||
/// Target server port
|
||||
#[arg(short, long, default_value_t = 25565)]
|
||||
port: u16,
|
||||
|
||||
/// Number of concurrent async fuzz worker tasks
|
||||
#[arg(short, long, default_value_t = 8)]
|
||||
concurrency: usize,
|
||||
|
||||
/// Duration to fuzz in seconds
|
||||
#[arg(short, long, default_value_t = 30)]
|
||||
duration: u64,
|
||||
|
||||
/// Fuzzing mode
|
||||
#[arg(short, long, value_enum, default_value_t = FuzzMode::All)]
|
||||
mode: FuzzMode,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Stats {
|
||||
connections: AtomicU64,
|
||||
packets_sent: AtomicU64,
|
||||
bytes_sent: AtomicU64,
|
||||
server_closes: AtomicU64,
|
||||
errors: AtomicU64,
|
||||
}
|
||||
|
||||
fn encode_var_int(val: i32, buf: &mut Vec<u8>) {
|
||||
let mut uval = val as u32;
|
||||
loop {
|
||||
let mut byte = (uval & 0x7F) as u8;
|
||||
uval >>= 7;
|
||||
if uval != 0 {
|
||||
byte |= 0x80;
|
||||
}
|
||||
buf.push(byte);
|
||||
if uval == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_packet(packet_id: i32, payload: &[u8]) -> Vec<u8> {
|
||||
let mut id_buf = Vec::new();
|
||||
encode_var_int(packet_id, &mut id_buf);
|
||||
let total_len = id_buf.len() + payload.len();
|
||||
let mut out = Vec::new();
|
||||
encode_var_int(total_len as i32, &mut out);
|
||||
out.extend_from_slice(&id_buf);
|
||||
out.extend_from_slice(payload);
|
||||
out
|
||||
}
|
||||
|
||||
fn make_handshake_packet(next_state: i32) -> Vec<u8> {
|
||||
let mut payload = Vec::new();
|
||||
encode_var_int(769, &mut payload); // Protocol version 769 (1.21.4)
|
||||
let addr = b"127.0.0.1";
|
||||
encode_var_int(addr.len() as i32, &mut payload);
|
||||
payload.extend_from_slice(addr);
|
||||
payload.extend_from_slice(&25565u16.to_be_bytes());
|
||||
encode_var_int(next_state, &mut payload);
|
||||
encode_packet(0x00, &payload)
|
||||
}
|
||||
|
||||
async fn check_server_alive(addr: SocketAddr) -> bool {
|
||||
let Ok(Ok(mut stream)) = timeout(Duration::from_millis(1500), TcpStream::connect(addr)).await
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let handshake = make_handshake_packet(1); // Status state
|
||||
if stream.write_all(&handshake).await.is_err() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let status_req = encode_packet(0x00, &[]);
|
||||
if stream.write_all(&status_req).await.is_err() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut buf = [0u8; 1024];
|
||||
matches!(
|
||||
timeout(Duration::from_millis(1500), stream.read(&mut buf)).await,
|
||||
Ok(Ok(n)) if n > 0
|
||||
)
|
||||
}
|
||||
|
||||
async fn fuzz_worker(
|
||||
target_addr: SocketAddr,
|
||||
mode: FuzzMode,
|
||||
stop: Arc<AtomicBool>,
|
||||
stats: Arc<Stats>,
|
||||
) {
|
||||
while !stop.load(Ordering::Relaxed) {
|
||||
let stream_result =
|
||||
timeout(Duration::from_millis(1500), TcpStream::connect(target_addr)).await;
|
||||
let mut stream = match stream_result {
|
||||
Ok(Ok(s)) => {
|
||||
stats.connections.fetch_add(1, Ordering::Relaxed);
|
||||
s
|
||||
}
|
||||
_ => {
|
||||
stats.errors.fetch_add(1, Ordering::Relaxed);
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let selected_mode = match mode {
|
||||
FuzzMode::All => match rand::rng().random_range(0..4) {
|
||||
0 => FuzzMode::Stateful,
|
||||
1 => FuzzMode::Framed,
|
||||
2 => FuzzMode::CorruptVarint,
|
||||
_ => FuzzMode::Raw,
|
||||
},
|
||||
other => other,
|
||||
};
|
||||
|
||||
let res: Result<(), tokio::io::Error> = async {
|
||||
match selected_mode {
|
||||
FuzzMode::Stateful => {
|
||||
let next_state = match rand::rng().random_range(0..3) {
|
||||
0 => 1, // Status
|
||||
1 => 2, // Login
|
||||
_ => 7, // Config
|
||||
};
|
||||
let handshake = make_handshake_packet(next_state);
|
||||
stream.write_all(&handshake).await?;
|
||||
stats.packets_sent.fetch_add(1, Ordering::Relaxed);
|
||||
stats
|
||||
.bytes_sent
|
||||
.fetch_add(handshake.len() as u64, Ordering::Relaxed);
|
||||
|
||||
let count = rand::rng().random_range(10..40);
|
||||
for _ in 0..count {
|
||||
if stop.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
let (frame, sleep_ms) = {
|
||||
let mut rng = rand::rng();
|
||||
let pkt_id = rng.random_range(0..0x60);
|
||||
let pkt_len = rng.random_range(0..512);
|
||||
let mut payload = vec![0u8; pkt_len];
|
||||
rng.fill(&mut payload[..]);
|
||||
(encode_packet(pkt_id, &payload), rng.random_range(1..5))
|
||||
};
|
||||
stream.write_all(&frame).await?;
|
||||
stats.packets_sent.fetch_add(1, Ordering::Relaxed);
|
||||
stats
|
||||
.bytes_sent
|
||||
.fetch_add(frame.len() as u64, Ordering::Relaxed);
|
||||
tokio::time::sleep(Duration::from_millis(sleep_ms)).await;
|
||||
}
|
||||
}
|
||||
FuzzMode::Framed => {
|
||||
let count = rand::rng().random_range(5..30);
|
||||
for _ in 0..count {
|
||||
if stop.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
let (frame, sleep_ms) = {
|
||||
let mut rng = rand::rng();
|
||||
let pkt_id = rng.random_range(0..255);
|
||||
let pkt_len = rng.random_range(0..2048);
|
||||
let mut payload = vec![0u8; pkt_len];
|
||||
rng.fill(&mut payload[..]);
|
||||
(encode_packet(pkt_id, &payload), rng.random_range(1..4))
|
||||
};
|
||||
stream.write_all(&frame).await?;
|
||||
stats.packets_sent.fetch_add(1, Ordering::Relaxed);
|
||||
stats
|
||||
.bytes_sent
|
||||
.fetch_add(frame.len() as u64, Ordering::Relaxed);
|
||||
tokio::time::sleep(Duration::from_millis(sleep_ms)).await;
|
||||
}
|
||||
}
|
||||
FuzzMode::CorruptVarint => {
|
||||
let count = rand::rng().random_range(5..20);
|
||||
for _ in 0..count {
|
||||
if stop.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
let bad_varint = {
|
||||
let mut rng = rand::rng();
|
||||
let byte_count = rng.random_range(5..10);
|
||||
let mut v = Vec::with_capacity(byte_count + 64);
|
||||
for _ in 0..byte_count {
|
||||
v.push(0x80 | rng.random_range(0..128));
|
||||
}
|
||||
let extra_len = rng.random_range(10..100);
|
||||
let mut extra = vec![0u8; extra_len];
|
||||
rng.fill(&mut extra[..]);
|
||||
v.extend_from_slice(&extra);
|
||||
v
|
||||
};
|
||||
|
||||
stream.write_all(&bad_varint).await?;
|
||||
stats.packets_sent.fetch_add(1, Ordering::Relaxed);
|
||||
stats
|
||||
.bytes_sent
|
||||
.fetch_add(bad_varint.len() as u64, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
FuzzMode::Raw | FuzzMode::All => {
|
||||
let count = rand::rng().random_range(5..20);
|
||||
for _ in 0..count {
|
||||
if stop.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
let (chunk, sleep_ms) = {
|
||||
let mut rng = rand::rng();
|
||||
let chunk_len = rng.random_range(1..4096);
|
||||
let mut c = vec![0u8; chunk_len];
|
||||
rng.fill(&mut c[..]);
|
||||
(c, rng.random_range(1..5))
|
||||
};
|
||||
stream.write_all(&chunk).await?;
|
||||
stats.packets_sent.fetch_add(1, Ordering::Relaxed);
|
||||
stats
|
||||
.bytes_sent
|
||||
.fetch_add(chunk.len() as u64, Ordering::Relaxed);
|
||||
tokio::time::sleep(Duration::from_millis(sleep_ms)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
.await;
|
||||
|
||||
if res.is_ok() {
|
||||
stats.server_closes.fetch_add(1, Ordering::Relaxed);
|
||||
} else {
|
||||
stats.errors.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
let sleep_ms = rand::rng().random_range(5..30);
|
||||
tokio::time::sleep(Duration::from_millis(sleep_ms)).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let args = Args::parse();
|
||||
let addr_str = format!("{}:{}", args.host, args.port);
|
||||
let target_addr: SocketAddr = match addr_str.parse() {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"{} Failed to parse address '{}': {}",
|
||||
"Error:".red().bold(),
|
||||
addr_str,
|
||||
e
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
println!(
|
||||
"{}",
|
||||
"=== Pumpkin Server Network Packet Fuzzer ===".cyan().bold()
|
||||
);
|
||||
println!("{:<15} {}", "Target:".bold(), addr_str.yellow());
|
||||
println!("{:<15} {}", "Concurrency:".bold(), args.concurrency);
|
||||
println!("{:<15} {}s", "Duration:".bold(), args.duration);
|
||||
println!("{:<15} {:?}", "Mode:".bold(), args.mode);
|
||||
println!(
|
||||
"{}",
|
||||
"-------------------------------------------------".dimmed()
|
||||
);
|
||||
|
||||
print!("Probing target server... ");
|
||||
if check_server_alive(target_addr).await {
|
||||
println!("{}", "ONLINE (Responding to status pings)".green().bold());
|
||||
} else {
|
||||
println!(
|
||||
"{}",
|
||||
"UNRESPONSIVE / OFFLINE (will proceed with test)"
|
||||
.yellow()
|
||||
.bold()
|
||||
);
|
||||
}
|
||||
|
||||
let stats = Arc::new(Stats::default());
|
||||
let stop = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let mut handles = Vec::new();
|
||||
for _ in 0..args.concurrency {
|
||||
let handle = tokio::spawn(fuzz_worker(
|
||||
target_addr,
|
||||
args.mode,
|
||||
stop.clone(),
|
||||
stats.clone(),
|
||||
));
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
let start = Instant::now();
|
||||
let duration = Duration::from_secs(args.duration);
|
||||
|
||||
while start.elapsed() < duration {
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
let elapsed = start.elapsed().as_secs();
|
||||
let conns = stats.connections.load(Ordering::Relaxed);
|
||||
let pkts = stats.packets_sent.load(Ordering::Relaxed);
|
||||
let bytes = stats.bytes_sent.load(Ordering::Relaxed);
|
||||
let closes = stats.server_closes.load(Ordering::Relaxed);
|
||||
let errs = stats.errors.load(Ordering::Relaxed);
|
||||
let rate_kb = (bytes as f64 / elapsed.max(1) as f64) / 1024.0;
|
||||
|
||||
println!(
|
||||
"[{:3}s] Conns: {:5} | Packets: {:6} | Data: {:7.1} KB ({:6.1} KB/s) | Closes: {:4} | Resets/Timeouts: {:4}",
|
||||
elapsed,
|
||||
conns,
|
||||
pkts,
|
||||
bytes as f64 / 1024.0,
|
||||
rate_kb,
|
||||
closes,
|
||||
errs
|
||||
);
|
||||
}
|
||||
|
||||
stop.store(true, Ordering::Relaxed);
|
||||
for h in handles {
|
||||
let _ = h.await;
|
||||
}
|
||||
|
||||
println!(
|
||||
"{}",
|
||||
"-------------------------------------------------".dimmed()
|
||||
);
|
||||
let conns = stats.connections.load(Ordering::Relaxed);
|
||||
let pkts = stats.packets_sent.load(Ordering::Relaxed);
|
||||
let bytes = stats.bytes_sent.load(Ordering::Relaxed);
|
||||
let closes = stats.server_closes.load(Ordering::Relaxed);
|
||||
let errs = stats.errors.load(Ordering::Relaxed);
|
||||
|
||||
println!("{}", "Fuzzing Session Complete:".green().bold());
|
||||
println!(" Total Connections: {}", conns);
|
||||
println!(" Total Packets Sent: {}", pkts);
|
||||
println!(" Total Data Sent: {:.2} KB", bytes as f64 / 1024.0);
|
||||
println!(" Server Normal Drops: {}", closes);
|
||||
println!(" Connection Resets: {}", errs);
|
||||
|
||||
print!("\nVerifying server health post-fuzzing... ");
|
||||
if check_server_alive(target_addr).await {
|
||||
println!(
|
||||
"{}",
|
||||
"SUCCESS: Server is alive and responsive!".green().bold()
|
||||
);
|
||||
} else {
|
||||
println!("{}", "WARNING: Server did not respond to post-fuzz query. Check server logs for any crash or hang.".red().bold());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user