diff options
| author | mat <git@matdoes.dev> | 2026-01-11 23:01:30 -1030 |
|---|---|---|
| committer | mat <git@matdoes.dev> | 2026-01-11 23:01:30 -1030 |
| commit | 736edae2ad243f6eb3e7b01ca9b6266745cdeb24 (patch) | |
| tree | 3d1ae581c5a1addca1ac48febb59a29de0fb180b | |
| parent | 1accbac964168af5fa0d87cb170389f0a9d01363 (diff) | |
| download | azalea-drasl-736edae2ad243f6eb3e7b01ca9b6266745cdeb24.tar.xz | |
add fuzzer for azalea-protocol and fix a few panics
20 files changed, 254 insertions, 4 deletions
@@ -115,6 +115,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -456,6 +462,14 @@ dependencies = [ ] [[package]] +name = "azalea-fuzz" +version = "0.0.0" +dependencies = [ + "azalea-protocol", + "libfuzzer-sys", +] + +[[package]] name = "azalea-inventory" version = "0.15.0+mc1.21.11" dependencies = [ @@ -2163,6 +2177,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] +name = "libfuzzer-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] name = "libm" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -14,6 +14,7 @@ members = [ "azalea-language", "azalea-physics", "azalea-protocol", + "azalea-protocol/fuzz", "azalea-registry", "azalea-world", ] diff --git a/azalea-chat/src/component.rs b/azalea-chat/src/component.rs index 637aafc7..c64ee559 100644 --- a/azalea-chat/src/component.rs +++ b/azalea-chat/src/component.rs @@ -408,6 +408,9 @@ impl<'de> Deserialize<'de> for FormattedText { )); } let json_array = json.as_array().unwrap(); + if json_array.is_empty() { + return Ok(FormattedText::default()); + } // the first item in the array is the one that we're gonna return, the others // are siblings let mut component = diff --git a/azalea-protocol/Cargo.toml b/azalea-protocol/Cargo.toml index 59991274..c7b0fd57 100644 --- a/azalea-protocol/Cargo.toml +++ b/azalea-protocol/Cargo.toml @@ -43,7 +43,7 @@ indexmap.workspace = true reqwest = { workspace = true, optional = true, features = ["socks"] } [features] -default = ["online-mode"] +default = ["online-mode", "connecting"] connecting = [] online-mode = ["azalea-auth/online-mode", "dep:reqwest"] bevy_ecs = ["dep:bevy_ecs", "azalea-entity/bevy_ecs", "azalea-core/bevy_ecs"] diff --git a/azalea-protocol/fuzz/.gitignore b/azalea-protocol/fuzz/.gitignore new file mode 100644 index 00000000..1a45eee7 --- /dev/null +++ b/azalea-protocol/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/azalea-protocol/fuzz/Cargo.toml b/azalea-protocol/fuzz/Cargo.toml new file mode 100644 index 00000000..37e4e606 --- /dev/null +++ b/azalea-protocol/fuzz/Cargo.toml @@ -0,0 +1,75 @@ +[package] +name = "azalea-fuzz" +version = "0.0.0" +publish = false +edition = "2024" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +azalea-protocol = { path = "..", default-features = false } + +[[bin]] +name = "clientbound_config" +path = "fuzz_targets/clientbound_config.rs" +test = false +doc = false +bench = false +[[bin]] +name = "clientbound_game" +path = "fuzz_targets/clientbound_game.rs" +test = false +doc = false +bench = false +[[bin]] +name = "clientbound_handshake" +path = "fuzz_targets/clientbound_handshake.rs" +test = false +doc = false +bench = false +[[bin]] +name = "clientbound_login" +path = "fuzz_targets/clientbound_login.rs" +test = false +doc = false +bench = false +[[bin]] +name = "clientbound_status" +path = "fuzz_targets/clientbound_status.rs" +test = false +doc = false +bench = false + + +[[bin]] +name = "serverbound_config" +path = "fuzz_targets/serverbound_config.rs" +test = false +doc = false +bench = false +[[bin]] +name = "serverbound_game" +path = "fuzz_targets/serverbound_game.rs" +test = false +doc = false +bench = false +[[bin]] +name = "serverbound_handshake" +path = "fuzz_targets/serverbound_handshake.rs" +test = false +doc = false +bench = false +[[bin]] +name = "serverbound_login" +path = "fuzz_targets/serverbound_login.rs" +test = false +doc = false +bench = false +[[bin]] +name = "serverbound_status" +path = "fuzz_targets/serverbound_status.rs" +test = false +doc = false +bench = false diff --git a/azalea-protocol/fuzz/README.md b/azalea-protocol/fuzz/README.md new file mode 100644 index 00000000..42ae188c --- /dev/null +++ b/azalea-protocol/fuzz/README.md @@ -0,0 +1,10 @@ +Fuzzing for `azalea-protocol`. + +## Usage + +```sh +cargo fuzz run clientbound_game # {clientbound,serverbound}_{config,game,handshake,login,status} +# optionally, add `-s none` for a speedup at the cost of catching less memory safety issues +# see https://appsec.guide/docs/fuzzing/rust/cargo-fuzz/#addresssanitizer +``` + diff --git a/azalea-protocol/fuzz/fuzz_targets/clientbound_config.rs b/azalea-protocol/fuzz/fuzz_targets/clientbound_config.rs new file mode 100644 index 00000000..79ffd95b --- /dev/null +++ b/azalea-protocol/fuzz/fuzz_targets/clientbound_config.rs @@ -0,0 +1,10 @@ +#![no_main] + +use std::io::Cursor; + +use azalea_protocol::{packets::config::ClientboundConfigPacket, read::deserialize_packet}; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let _ = deserialize_packet::<ClientboundConfigPacket>(&mut Cursor::new(data)); +}); diff --git a/azalea-protocol/fuzz/fuzz_targets/clientbound_game.rs b/azalea-protocol/fuzz/fuzz_targets/clientbound_game.rs new file mode 100644 index 00000000..a253a859 --- /dev/null +++ b/azalea-protocol/fuzz/fuzz_targets/clientbound_game.rs @@ -0,0 +1,10 @@ +#![no_main] + +use std::io::Cursor; + +use azalea_protocol::{packets::game::ClientboundGamePacket, read::deserialize_packet}; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let _ = deserialize_packet::<ClientboundGamePacket>(&mut Cursor::new(data)); +}); diff --git a/azalea-protocol/fuzz/fuzz_targets/clientbound_handshake.rs b/azalea-protocol/fuzz/fuzz_targets/clientbound_handshake.rs new file mode 100644 index 00000000..84061965 --- /dev/null +++ b/azalea-protocol/fuzz/fuzz_targets/clientbound_handshake.rs @@ -0,0 +1,10 @@ +#![no_main] + +use std::io::Cursor; + +use azalea_protocol::{packets::handshake::ClientboundHandshakePacket, read::deserialize_packet}; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let _ = deserialize_packet::<ClientboundHandshakePacket>(&mut Cursor::new(data)); +}); diff --git a/azalea-protocol/fuzz/fuzz_targets/clientbound_login.rs b/azalea-protocol/fuzz/fuzz_targets/clientbound_login.rs new file mode 100644 index 00000000..6339fcea --- /dev/null +++ b/azalea-protocol/fuzz/fuzz_targets/clientbound_login.rs @@ -0,0 +1,10 @@ +#![no_main] + +use std::io::Cursor; + +use azalea_protocol::{packets::login::ClientboundLoginPacket, read::deserialize_packet}; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let _ = deserialize_packet::<ClientboundLoginPacket>(&mut Cursor::new(data)); +}); diff --git a/azalea-protocol/fuzz/fuzz_targets/clientbound_status.rs b/azalea-protocol/fuzz/fuzz_targets/clientbound_status.rs new file mode 100644 index 00000000..38264f64 --- /dev/null +++ b/azalea-protocol/fuzz/fuzz_targets/clientbound_status.rs @@ -0,0 +1,10 @@ +#![no_main] + +use std::io::Cursor; + +use azalea_protocol::{packets::status::ClientboundStatusPacket, read::deserialize_packet}; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let _ = deserialize_packet::<ClientboundStatusPacket>(&mut Cursor::new(data)); +}); diff --git a/azalea-protocol/fuzz/fuzz_targets/serverbound_config.rs b/azalea-protocol/fuzz/fuzz_targets/serverbound_config.rs new file mode 100644 index 00000000..d2a13d1d --- /dev/null +++ b/azalea-protocol/fuzz/fuzz_targets/serverbound_config.rs @@ -0,0 +1,10 @@ +#![no_main] + +use std::io::Cursor; + +use azalea_protocol::{packets::config::ServerboundConfigPacket, read::deserialize_packet}; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let _ = deserialize_packet::<ServerboundConfigPacket>(&mut Cursor::new(data)); +}); diff --git a/azalea-protocol/fuzz/fuzz_targets/serverbound_game.rs b/azalea-protocol/fuzz/fuzz_targets/serverbound_game.rs new file mode 100644 index 00000000..8891485c --- /dev/null +++ b/azalea-protocol/fuzz/fuzz_targets/serverbound_game.rs @@ -0,0 +1,10 @@ +#![no_main] + +use std::io::Cursor; + +use azalea_protocol::{packets::game::ServerboundGamePacket, read::deserialize_packet}; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let _ = deserialize_packet::<ServerboundGamePacket>(&mut Cursor::new(data)); +}); diff --git a/azalea-protocol/fuzz/fuzz_targets/serverbound_handshake.rs b/azalea-protocol/fuzz/fuzz_targets/serverbound_handshake.rs new file mode 100644 index 00000000..be3fca35 --- /dev/null +++ b/azalea-protocol/fuzz/fuzz_targets/serverbound_handshake.rs @@ -0,0 +1,10 @@ +#![no_main] + +use std::io::Cursor; + +use azalea_protocol::{packets::handshake::ServerboundHandshakePacket, read::deserialize_packet}; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let _ = deserialize_packet::<ServerboundHandshakePacket>(&mut Cursor::new(data)); +}); diff --git a/azalea-protocol/fuzz/fuzz_targets/serverbound_login.rs b/azalea-protocol/fuzz/fuzz_targets/serverbound_login.rs new file mode 100644 index 00000000..e0e4a384 --- /dev/null +++ b/azalea-protocol/fuzz/fuzz_targets/serverbound_login.rs @@ -0,0 +1,10 @@ +#![no_main] + +use std::io::Cursor; + +use azalea_protocol::{packets::login::ServerboundLoginPacket, read::deserialize_packet}; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let _ = deserialize_packet::<ServerboundLoginPacket>(&mut Cursor::new(data)); +}); diff --git a/azalea-protocol/fuzz/fuzz_targets/serverbound_status.rs b/azalea-protocol/fuzz/fuzz_targets/serverbound_status.rs new file mode 100644 index 00000000..65429b29 --- /dev/null +++ b/azalea-protocol/fuzz/fuzz_targets/serverbound_status.rs @@ -0,0 +1,10 @@ +#![no_main] + +use std::io::Cursor; + +use azalea_protocol::{packets::status::ServerboundStatusPacket, read::deserialize_packet}; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let _ = deserialize_packet::<ServerboundStatusPacket>(&mut Cursor::new(data)); +}); diff --git a/azalea-protocol/src/common/tags.rs b/azalea-protocol/src/common/tags.rs index f22175ee..3f9a2ef2 100644 --- a/azalea-protocol/src/common/tags.rs +++ b/azalea-protocol/src/common/tags.rs @@ -19,11 +19,11 @@ pub struct Tags { impl AzaleaRead for TagMap { fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { let length = u32::azalea_read_var(buf)? as usize; - let mut data = IndexMap::with_capacity(length); + let mut data = IndexMap::new(); for _ in 0..length { let tag_type = Identifier::azalea_read(buf)?; let tags_count = i32::azalea_read_var(buf)? as usize; - let mut tags_vec = Vec::with_capacity(tags_count); + let mut tags_vec = Vec::new(); for _ in 0..tags_count { let tags = Tags::azalea_read(buf)?; tags_vec.push(tags); diff --git a/azalea-protocol/src/read.rs b/azalea-protocol/src/read.rs index d6c8c65a..664e2593 100644 --- a/azalea-protocol/src/read.rs +++ b/azalea-protocol/src/read.rs @@ -401,3 +401,36 @@ where Ok(Some(buf)) } + +#[cfg(test)] +mod tests { + use std::io::Cursor; + + use crate::{packets::game::ClientboundGamePacket, read::deserialize_packet}; + + #[test] + fn fuzzed_1() { + // oom: checks for unbounded TagMap + let _ = deserialize_packet::<ClientboundGamePacket>(&mut Cursor::new( + [132, 1, 255, 255, 255, 255, 255].as_slice(), + )); + } + #[test] + fn fuzzed_2() { + // oom: also checks for unbounded TagMap + let _ = deserialize_packet::<ClientboundGamePacket>(&mut Cursor::new( + [132, 1, 75, 0, 255, 255, 255, 255, 24, 0].as_slice(), + )); + } + #[test] + fn fuzzed_3() { + // panic: integer overflow in HolderSet::azalea_read + let _ = deserialize_packet::<ClientboundGamePacket>(&mut Cursor::new( + [ + 94, 44, 157, 38, 61, 37, 37, 37, 37, 37, 37, 65, 128, 128, 1, 1, 255, 252, 128, + 128, 128, 128, 128, 128, 128, 40, 0, + ] + .as_slice(), + )); + } +} diff --git a/azalea-registry/src/lib.rs b/azalea-registry/src/lib.rs index 1d146508..fa197412 100644 --- a/azalea-registry/src/lib.rs +++ b/azalea-registry/src/lib.rs @@ -133,7 +133,7 @@ pub enum HolderSet<D: Registry, Identifier: AzaleaRead + AzaleaWrite> { } impl<D: Registry, Identifier: AzaleaRead + AzaleaWrite> AzaleaRead for HolderSet<D, Identifier> { fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> { - let size = i32::azalea_read_var(buf)? - 1; + let size = i32::azalea_read_var(buf)?.wrapping_sub(1); if size == -1 { let key = Identifier::azalea_read(buf)?; Ok(Self::Named { |
