From fe35213daafbd6e9afd99a59a4f804ae4606cce8 Mon Sep 17 00:00:00 2001 From: e1pupper Date: Sat, 11 Oct 2025 19:03:06 +0100 Subject: [PATCH 1/5] Ignore Certs --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6e3b899a..0325a24e 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ vendor/ .trigger-debug .trigger valence/ +/certs From d22cb3e5af1a1ccc05fc2122e4584f528510f441 Mon Sep 17 00:00:00 2001 From: e1pupper Date: Sat, 11 Oct 2025 19:03:44 +0100 Subject: [PATCH 2/5] Validate slot_changes in the click slot packet #931 --- crates/hyperion/src/simulation/inventory.rs | 85 +++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/crates/hyperion/src/simulation/inventory.rs b/crates/hyperion/src/simulation/inventory.rs index af6792b8..1c249c70 100644 --- a/crates/hyperion/src/simulation/inventory.rs +++ b/crates/hyperion/src/simulation/inventory.rs @@ -441,6 +441,23 @@ fn handle_click_slot_inner<'a>( return; } + // Validate slot_changes to prevent exploits + if !validate_slot_changes( + &packet.slot_changes, + &inventories_mut, + &cursor_item.0, + player_only, + ) { + resync_inventory( + compose, + &inventories_mut, + inv_state, + cursor_item, + packet.connection_id(), + ); + return; + } + let mut cursor = cursor_item.0.clone(); let slots = packet.slot_changes.clone(); @@ -1191,6 +1208,74 @@ fn handle_drop_key( event_writer.write(event); } +fn validate_slot_changes( + slot_changes: &[SlotChange], + inventories_mut: &[&mut ItemSlot], + cursor_item: &ItemStack, + player_only: bool, +) -> bool { + // If cursor is empty, no slot changes should be valid + if cursor_item.is_empty() { + return false; + } + + for slot_change in slot_changes { + // Validate slot index bounds + let Ok(slot_idx) = usize::try_from(slot_change.idx) else { + return false; + }; + + // Check if slot exists in our inventory + if slot_idx >= inventories_mut.len() { + return false; + } + + let slot = &inventories_mut[slot_idx]; + + // Skip readonly slots + if slot.readonly { + return false; + } + + // For player-only inventories, validate armor slot restrictions + if player_only && (5..=8).contains(&slot_idx) { + let is_valid = match slot_idx { + 5 => cursor_item.item.is_helmet(), + 6 => cursor_item.item.is_chestplate(), + 7 => cursor_item.item.is_leggings(), + 8 => cursor_item.item.is_boots(), + _ => true, + }; + if !is_valid { + return false; + } + } + + // Validate that the slot is either empty or contains the same item type + // This prevents creating items out of thin air + if !slot.stack.is_empty() { + // The slot should either be empty or contain the same item type as cursor + if slot.stack.item != cursor_item.item || slot.stack.nbt != cursor_item.nbt { + return false; + } + + // For non-empty slots, they should have space available + if slot.stack.count >= slot.stack.item.max_stack() { + return false; + } + } + } + + // Validate that the total number of items we're trying to distribute + // doesn't exceed what's in the cursor + let total_distributed_items = slot_changes.len() as i32; + if total_distributed_items > cursor_item.count as i32 { + return false; + } + + true +} + fn try_move_to_slot(source: &mut ItemStack, target: &mut ItemSlot) -> bool { // Try to stack with existing items if !target.stack.is_empty() From d9ad6017a1c53bdf8e9da5a281bc4d4426e542f5 Mon Sep 17 00:00:00 2001 From: e1pupper Date: Sat, 11 Oct 2025 19:13:20 +0100 Subject: [PATCH 3/5] Clippy changes --- crates/hyperion/src/simulation/inventory.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/hyperion/src/simulation/inventory.rs b/crates/hyperion/src/simulation/inventory.rs index 1c249c70..81e88212 100644 --- a/crates/hyperion/src/simulation/inventory.rs +++ b/crates/hyperion/src/simulation/inventory.rs @@ -1268,8 +1268,8 @@ fn validate_slot_changes( // Validate that the total number of items we're trying to distribute // doesn't exceed what's in the cursor - let total_distributed_items = slot_changes.len() as i32; - if total_distributed_items > cursor_item.count as i32 { + let total_distributed_items = i32::try_from(slot_changes.len()).unwrap(); + if total_distributed_items > i32::from(cursor_item.count) { return false; } From c8a84edc82826a9b33acdb1da35e429deaffb87f Mon Sep 17 00:00:00 2001 From: e1pupper Date: Mon, 13 Oct 2025 06:38:19 +0100 Subject: [PATCH 4/5] Remove Manual bounds check, Remove total distributed items check as we already account for item stack --- crates/hyperion/src/simulation/inventory.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/crates/hyperion/src/simulation/inventory.rs b/crates/hyperion/src/simulation/inventory.rs index 81e88212..c87a68fb 100644 --- a/crates/hyperion/src/simulation/inventory.rs +++ b/crates/hyperion/src/simulation/inventory.rs @@ -1225,12 +1225,9 @@ fn validate_slot_changes( return false; }; - // Check if slot exists in our inventory - if slot_idx >= inventories_mut.len() { + let Some(slot) = inventories_mut.get(slot_idx) else { return false; - } - - let slot = &inventories_mut[slot_idx]; + }; // Skip readonly slots if slot.readonly { @@ -1266,13 +1263,6 @@ fn validate_slot_changes( } } - // Validate that the total number of items we're trying to distribute - // doesn't exceed what's in the cursor - let total_distributed_items = i32::try_from(slot_changes.len()).unwrap(); - if total_distributed_items > i32::from(cursor_item.count) { - return false; - } - true } From f1377c2fcb9880865b355e8fa9cbecf0c8c97e80 Mon Sep 17 00:00:00 2001 From: TestingPlant <44930139+TestingPlant@users.noreply.github.com> Date: Sat, 18 Oct 2025 04:09:10 +0000 Subject: [PATCH 5/5] Revert "Ignore Certs" This reverts commit fe35213daafbd6e9afd99a59a4f804ae4606cce8. --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0325a24e..6e3b899a 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,3 @@ vendor/ .trigger-debug .trigger valence/ -/certs