obscure security updates

This commit is contained in:
Martin Michelsen
2025-09-18 23:48:14 -07:00
parent 9493e2d3e7
commit 277be9bcd6
3 changed files with 174 additions and 18 deletions
+11 -13
View File
@@ -4202,19 +4202,20 @@ struct G_VolOptBossActions_6x15 {
struct G_VolOptBossActions_6x16 {
G_EntityIDHeader header;
parray<uint8_t, 6> unknown_a2;
le_uint16_t unknown_a3 = 0;
parray<uint8_t, 6> entity_index_table;
le_uint16_t entity_index_count = 0;
} __packed_ws__(G_VolOptBossActions_6x16, 0x0C);
// 6x17: Vol Opt phase 2 boss actions (not valid on Episode 3)
// 6x17: Set entity position and angle (not valid on Episode 3)
// This command sets an entity's position and angle without performing any
// validity checks, even on v3 and later. We unconditionally block this if it
// affects a player other than the sender.
struct G_VolOpt2BossActions_6x17 {
struct G_SetEntityPositionAndAngle_6x17 {
G_EntityIDHeader header;
le_float unknown_a2 = 0.0f;
le_float unknown_a3 = 0.0f;
le_float unknown_a4 = 0.0f;
le_uint32_t unknown_a5 = 0;
} __packed_ws__(G_VolOpt2BossActions_6x17, 0x14);
VectorXYZF pos;
le_uint32_t angle = 0;
} __packed_ws__(G_SetEntityPositionAndAngle_6x17, 0x14);
// 6x18: Vol Opt phase 2 boss actions (not valid on Episode 3)
@@ -5416,10 +5417,7 @@ struct G_PlaceTrap_6x83 {
// 6x84: Vol Opt boss actions (not valid on Episode 3)
// Same format and usage as 6x16, except unknown_a2 is ignored in 6x84.
struct G_VolOptBossActions_6x84 {
G_UnusedHeader header;
parray<uint8_t, 6> unknown_a1;
le_uint16_t unknown_a2 = 0;
struct G_VolOptBossActions_6x84 : G_VolOptBossActions_6x16 {
le_uint16_t unknown_a3 = 0;
le_uint16_t unused = 0;
} __packed_ws__(G_VolOptBossActions_6x84, 0x10);
+70
View File
@@ -915,6 +915,40 @@ static asio::awaitable<HandlerResult> S_6x(shared_ptr<Client> c, Channel::Messag
c->log.warning_f("Blocking invalid subcommand from server");
co_return HandlerResult::SUPPRESS;
case 0x16:
case 0x84: {
const auto& cmd = msg.check_size_t<G_VolOptBossActions_6x16>(0xFFFF);
if (cmd.entity_index_count > 6) {
c->log.warning_f("Blocking subcommand 6x16/6x84 with invalid entity index count");
co_return HandlerResult::SUPPRESS;
}
for (size_t z = 0; z < cmd.entity_index_table.size(); z++) {
if (cmd.entity_index_table[z] >= 6) {
c->log.warning_f("Blocking subcommand 6x16/6x84 with invalid entity index");
co_return HandlerResult::SUPPRESS;
}
}
break;
}
case 0x17: {
const auto& cmd = msg.check_size_t<G_SetEntityPositionAndAngle_6x17>();
if (cmd.header.entity_id == c->lobby_client_id) {
c->log.warning_f("Blocking subcommand 6x17 targeting local client");
co_return HandlerResult::SUPPRESS;
}
break;
}
case 0x2F: {
const auto& cmd = msg.check_size_t<G_ChangePlayerHP_6x2F>();
if (cmd.client_id == c->lobby_client_id) {
c->log.warning_f("Blocking subcommand 6x2F targeting local player");
co_return HandlerResult::SUPPRESS;
}
break;
}
case 0x46: {
const auto& header = msg.check_size_t<G_AttackFinished_Header_6x46>(0xFFFF);
if (header.target_count > min<size_t>(header.header.size - sizeof(G_AttackFinished_Header_6x46) / 4, 10)) {
@@ -954,6 +988,42 @@ static asio::awaitable<HandlerResult> S_6x(shared_ptr<Client> c, Channel::Messag
case 0xA2:
co_return co_await SC_6x60_6xA2(c, msg);
case 0x6A: {
auto& cmd = msg.check_size_t<G_SetBossWarpFlags_6x6A>();
if (c->proxy_session->map_state) {
shared_ptr<MapState::ObjectState> obj_st;
try {
obj_st = c->proxy_session->map_state->object_state_for_index(c->version(), c->floor, cmd.header.entity_id - 0x4000);
} catch (const exception& e) {
c->log.warning_f("Invalid object reference ({})", e.what());
}
if (!obj_st || !obj_st->super_obj) {
c->log.warning_f("Blocking subcommand 6x6A with missing object");
co_return HandlerResult::SUPPRESS;
}
auto set_entry = obj_st->super_obj->version(c->version()).set_entry;
if (!set_entry) {
c->log.warning_f("Blocking subcommand 6x6A with missing set entry");
co_return HandlerResult::SUPPRESS;
}
if (set_entry->base_type != 0x0019 && set_entry->base_type != 0x0055) {
c->log.warning_f("Blocking subcommand 6x6A with incorrect object type");
co_return HandlerResult::SUPPRESS;
}
}
break;
}
case 0x7D: {
const auto& cmd = msg.check_size_t<G_SetBattleModeData_6x7D>();
if ((cmd.what == 3 || cmd.what == 4) && cmd.params[0] >= 4) {
c->log.warning_f("Blocking subcommand 6x7D with invalid client ID");
co_return HandlerResult::SUPPRESS;
}
break;
}
case 0xB3:
case 0xB4:
case 0xB5: {
+93 -5
View File
@@ -3679,6 +3679,79 @@ static asio::awaitable<void> on_gol_dragon_actions(shared_ptr<Client> c, Subcomm
}
}
template <typename CmdT>
static asio::awaitable<void> on_vol_opt_actions_t(shared_ptr<Client> c, SubcommandMessage& msg) {
auto& cmd = msg.check_size_t<CmdT>();
if (command_is_private(msg.command)) {
co_return;
}
auto l = c->require_lobby();
if (!l->is_game()) {
co_return;
}
if (cmd.entity_index_count > 6) {
throw runtime_error("invalid 6x16/6x84 command");
}
for (size_t z = 0; z < cmd.entity_index_table.size(); z++) {
if (cmd.entity_index_table[z] >= 6) {
throw runtime_error("invalid 6x16/6x84 command");
}
}
co_await forward_subcommand_with_entity_id_transcode_t<CmdT>(c, msg);
}
static asio::awaitable<void> on_set_entity_pos_and_angle_6x17(shared_ptr<Client> c, SubcommandMessage& msg) {
auto& cmd = msg.check_size_t<G_SetEntityPositionAndAngle_6x17>();
if (command_is_private(msg.command)) {
co_return;
}
auto l = c->require_lobby();
if (!l->is_game()) {
co_return;
}
if ((cmd.header.entity_id < 0x1000) &&
(cmd.header.entity_id != c->lobby_client_id) &&
(l->clients.at(cmd.header.entity_id) != nullptr)) {
throw runtime_error("client sent 6x17 command affecting another player");
}
co_await forward_subcommand_with_entity_id_transcode_t<G_SetEntityPositionAndAngle_6x17>(c, msg);
}
static asio::awaitable<void> on_set_boss_warp_flags_6x6A(shared_ptr<Client> c, SubcommandMessage& msg) {
auto& cmd = msg.check_size_t<G_SetBossWarpFlags_6x6A>();
if (command_is_private(msg.command)) {
co_return;
}
auto l = c->require_lobby();
if (!l->is_game()) {
co_return;
}
if (cmd.header.entity_id < 0x4000) {
throw runtime_error("6x6A sent for non-object entity");
}
auto obj_st = l->map_state->object_state_for_index(c->version(), c->floor, cmd.header.entity_id - 0x4000);
if (!obj_st->super_obj) {
throw runtime_error("missing object for 6x6A command");
}
auto set_entry = obj_st->super_obj->version(c->version()).set_entry;
if (!set_entry) {
throw runtime_error("missing set entry for 6x6A command");
}
if (set_entry->base_type != 0x0019 && set_entry->base_type != 0x0055) {
throw runtime_error("incorrect object type for 6x6A command");
}
co_await forward_subcommand_with_entity_id_transcode_t<G_SetBossWarpFlags_6x6A>(c, msg);
}
static asio::awaitable<void> on_charge_attack_bb(shared_ptr<Client> c, SubcommandMessage& msg) {
auto l = c->require_lobby();
if (c->version() != Version::BB_V4) {
@@ -4673,6 +4746,21 @@ static asio::awaitable<void> on_challenge_update_records(shared_ptr<Client> c, S
}
}
static asio::awaitable<void> on_update_battle_data_6x7D(shared_ptr<Client> c, SubcommandMessage& msg) {
auto l = c->lobby.lock();
if (!l) {
c->log.warning_f("Not in any lobby; dropping command");
co_return;
}
const auto& cmd = msg.check_size_t<G_SetBattleModeData_6x7D>(0xFFFF);
if ((cmd.what == 3 || cmd.what == 4) && cmd.params[0] >= 4) {
throw runtime_error("invalid client ID in 6x7D command");
}
co_await on_forward_check_game(c, msg);
}
static asio::awaitable<void> on_quest_exchange_item_bb(shared_ptr<Client> c, SubcommandMessage& msg) {
auto l = c->require_lobby();
if (c->version() != Version::BB_V4) {
@@ -5210,8 +5298,8 @@ const vector<SubcommandDefinition> subcommand_definitions{
/* 6x13 */ {0x11, 0x11, 0x13, forward_subcommand_with_entity_id_transcode_t<G_DeRolLeBossActions_6x13>},
/* 6x14 */ {0x12, 0x12, 0x14, forward_subcommand_with_entity_id_transcode_t<G_DeRolLeBossActions_6x14>},
/* 6x15 */ {0x13, 0x13, 0x15, forward_subcommand_with_entity_id_transcode_t<G_VolOptBossActions_6x15>},
/* 6x16 */ {0x14, 0x14, 0x16, forward_subcommand_with_entity_id_transcode_t<G_VolOptBossActions_6x16>},
/* 6x17 */ {0x15, 0x15, 0x17, forward_subcommand_with_entity_id_transcode_t<G_VolOpt2BossActions_6x17>},
/* 6x16 */ {0x14, 0x14, 0x16, on_vol_opt_actions_t<G_VolOptBossActions_6x16>},
/* 6x17 */ {0x15, 0x15, 0x17, on_set_entity_pos_and_angle_6x17},
/* 6x18 */ {0x16, 0x16, 0x18, forward_subcommand_with_entity_id_transcode_t<G_VolOpt2BossActions_6x18>},
/* 6x19 */ {0x17, 0x17, 0x19, forward_subcommand_with_entity_id_transcode_t<G_DarkFalzActions_6x19>},
/* 6x1A */ {NONE, NONE, 0x1A, on_invalid},
@@ -5295,7 +5383,7 @@ const vector<SubcommandDefinition> subcommand_definitions{
/* 6x67 */ {0x58, 0x5F, 0x67, on_trigger_set_event},
/* 6x68 */ {0x59, 0x60, 0x68, on_update_telepipe_state},
/* 6x69 */ {0x5A, 0x61, 0x69, on_npc_control},
/* 6x6A */ {0x5B, 0x62, 0x6A, forward_subcommand_with_entity_id_transcode_t<G_SetBossWarpFlags_6x6A>},
/* 6x6A */ {0x5B, 0x62, 0x6A, on_set_boss_warp_flags_6x6A},
/* 6x6B */ {0x5C, 0x63, 0x6B, on_sync_joining_player_compressed_state},
/* 6x6C */ {0x5D, 0x64, 0x6C, on_sync_joining_player_compressed_state},
/* 6x6D */ {0x5E, 0x65, 0x6D, on_sync_joining_player_compressed_state},
@@ -5314,14 +5402,14 @@ const vector<SubcommandDefinition> subcommand_definitions{
/* 6x7A */ {NONE, NONE, 0x7A, on_forward_check_game_client},
/* 6x7B */ {NONE, NONE, 0x7B, forward_subcommand_m},
/* 6x7C */ {NONE, NONE, 0x7C, on_challenge_update_records},
/* 6x7D */ {NONE, NONE, 0x7D, on_forward_check_game},
/* 6x7D */ {NONE, NONE, 0x7D, on_update_battle_data_6x7D},
/* 6x7E */ {NONE, NONE, 0x7E, forward_subcommand_m},
/* 6x7F */ {NONE, NONE, 0x7F, on_battle_scores},
/* 6x80 */ {NONE, NONE, 0x80, on_forward_check_game},
/* 6x81 */ {NONE, NONE, 0x81, on_forward_check_game},
/* 6x82 */ {NONE, NONE, 0x82, on_forward_check_game},
/* 6x83 */ {NONE, NONE, 0x83, on_forward_check_game},
/* 6x84 */ {NONE, NONE, 0x84, on_forward_check_game},
/* 6x84 */ {NONE, NONE, 0x84, on_vol_opt_actions_t<G_VolOptBossActions_6x84>},
/* 6x85 */ {NONE, NONE, 0x85, on_forward_check_game},
/* 6x86 */ {NONE, NONE, 0x86, on_update_object_state_t<G_HitDestructibleObject_6x86>},
/* 6x87 */ {NONE, NONE, 0x87, on_forward_check_game},