From c2eba51b29ded1691e2a13eaf56e8ed96244b24a Mon Sep 17 00:00:00 2001 From: Houmgaor Date: Thu, 19 Feb 2026 00:28:28 +0100 Subject: [PATCH] fix(channelserver): add max-size guards to binary blob save handlers A malicious or buggy client could send arbitrarily large payloads that get written directly to PostgreSQL, wasting disk and memory. Each save handler now rejects payloads exceeding a generous upper bound derived from the known data format sizes. Covers all remaining items from #158: partner, hunternavi, savemercenary, scenariodata, platedata, platebox, platemyset, rengokudata, mezfes, savefavoritequest, house_furniture, mission. Closes #158 --- server/channelserver/handlers_data.go | 5 +++++ server/channelserver/handlers_festa.go | 5 +++++ server/channelserver/handlers_house.go | 10 ++++++++++ server/channelserver/handlers_mercenary.go | 15 +++++++++++++++ server/channelserver/handlers_plate.go | 15 +++++++++++++++ server/channelserver/handlers_quest.go | 5 +++++ server/channelserver/handlers_rengoku.go | 4 ++-- 7 files changed, 57 insertions(+), 2 deletions(-) diff --git a/server/channelserver/handlers_data.go b/server/channelserver/handlers_data.go index dbda81a80..bc7e34896 100644 --- a/server/channelserver/handlers_data.go +++ b/server/channelserver/handlers_data.go @@ -183,6 +183,11 @@ func handleMsgMhfLoaddata(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSaveScenarioData(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSaveScenarioData) + if len(pkt.RawDataPayload) > 65536 { + s.logger.Warn("Scenario payload too large", zap.Int("len", len(pkt.RawDataPayload))) + doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) + return + } dumpSaveData(s, pkt.RawDataPayload, "scenario") _, err := s.server.db.Exec("UPDATE characters SET scenariodata = $1 WHERE id = $2", pkt.RawDataPayload, s.charID) if err != nil { diff --git a/server/channelserver/handlers_festa.go b/server/channelserver/handlers_festa.go index 32c444511..e0452b385 100644 --- a/server/channelserver/handlers_festa.go +++ b/server/channelserver/handlers_festa.go @@ -17,6 +17,11 @@ import ( func handleMsgMhfSaveMezfesData(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSaveMezfesData) + if len(pkt.RawDataPayload) > 4096 { + s.logger.Warn("MezFes payload too large", zap.Int("len", len(pkt.RawDataPayload))) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return + } if _, err := s.server.db.Exec(`UPDATE characters SET mezfes=$1 WHERE id=$2`, pkt.RawDataPayload, s.charID); err != nil { s.logger.Error("Failed to save mezfes data", zap.Error(err)) } diff --git a/server/channelserver/handlers_house.go b/server/channelserver/handlers_house.go index 5d608a2c8..323676edd 100644 --- a/server/channelserver/handlers_house.go +++ b/server/channelserver/handlers_house.go @@ -41,6 +41,11 @@ FROM warehouse func handleMsgMhfUpdateInterior(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfUpdateInterior) + if len(pkt.InteriorData) > 64 { + s.logger.Warn("Interior payload too large", zap.Int("len", len(pkt.InteriorData))) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return + } if _, err := s.server.db.Exec(`UPDATE user_binary SET house_furniture=$1 WHERE id=$2`, pkt.InteriorData, s.charID); err != nil { s.logger.Error("Failed to update house furniture", zap.Error(err)) } @@ -253,6 +258,11 @@ func handleMsgMhfGetMyhouseInfo(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfUpdateMyhouseInfo(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfUpdateMyhouseInfo) + if len(pkt.Data) > 512 { + s.logger.Warn("MyhouseInfo payload too large", zap.Int("len", len(pkt.Data))) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return + } if _, err := s.server.db.Exec("UPDATE user_binary SET mission=$1 WHERE id=$2", pkt.Data, s.charID); err != nil { s.logger.Error("Failed to update myhouse mission", zap.Error(err)) } diff --git a/server/channelserver/handlers_mercenary.go b/server/channelserver/handlers_mercenary.go index 9ecc355de..7d433cae6 100644 --- a/server/channelserver/handlers_mercenary.go +++ b/server/channelserver/handlers_mercenary.go @@ -25,6 +25,11 @@ func handleMsgMhfLoadPartner(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSavePartner(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSavePartner) + if len(pkt.RawDataPayload) > 65536 { + s.logger.Warn("Partner payload too large", zap.Int("len", len(pkt.RawDataPayload))) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return + } dumpSaveData(s, pkt.RawDataPayload, "partner") _, err := s.server.db.Exec("UPDATE characters SET partner=$1 WHERE id=$2", pkt.RawDataPayload, s.charID) if err != nil { @@ -69,6 +74,11 @@ func handleMsgMhfLoadHunterNavi(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSaveHunterNavi) + if len(pkt.RawDataPayload) > 4096 { + s.logger.Warn("HunterNavi payload too large", zap.Int("len", len(pkt.RawDataPayload))) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return + } saveStart := time.Now() s.logger.Debug("Hunter Navi save request", @@ -187,6 +197,11 @@ func handleMsgMhfCreateMercenary(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSaveMercenary(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSaveMercenary) + if len(pkt.MercData) > 65536 { + s.logger.Warn("Mercenary payload too large", zap.Int("len", len(pkt.MercData))) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return + } dumpSaveData(s, pkt.MercData, "mercenary") if len(pkt.MercData) >= 4 { temp := byteframe.NewByteFrameFromBytes(pkt.MercData) diff --git a/server/channelserver/handlers_plate.go b/server/channelserver/handlers_plate.go index 61d629d87..3199c66a7 100644 --- a/server/channelserver/handlers_plate.go +++ b/server/channelserver/handlers_plate.go @@ -41,6 +41,11 @@ func handleMsgMhfLoadPlateData(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSavePlateData) + if len(pkt.RawDataPayload) > 262144 { + s.logger.Warn("PlateData payload too large", zap.Int("len", len(pkt.RawDataPayload))) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return + } saveStart := time.Now() s.logger.Debug("PlateData save request", @@ -149,6 +154,11 @@ func handleMsgMhfLoadPlateBox(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSavePlateBox) + if len(pkt.RawDataPayload) > 32768 { + s.logger.Warn("PlateBox payload too large", zap.Int("len", len(pkt.RawDataPayload))) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return + } if pkt.IsDataDiff { var data []byte @@ -224,6 +234,11 @@ func handleMsgMhfLoadPlateMyset(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSavePlateMyset(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSavePlateMyset) + if len(pkt.RawDataPayload) > 4096 { + s.logger.Warn("PlateMyset payload too large", zap.Int("len", len(pkt.RawDataPayload))) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return + } saveStart := time.Now() s.logger.Debug("PlateMyset save request", diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index fa5e30dc2..2a6610c13 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -206,6 +206,11 @@ func handleMsgMhfLoadFavoriteQuest(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSaveFavoriteQuest(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSaveFavoriteQuest) + if len(pkt.Data) > 65536 { + s.logger.Warn("FavoriteQuest payload too large", zap.Int("len", len(pkt.Data))) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return + } dumpSaveData(s, pkt.Data, "favquest") if _, err := s.server.db.Exec("UPDATE characters SET savefavoritequest=$1 WHERE id=$2", pkt.Data, s.charID); err != nil { s.logger.Error("Failed to save favorite quest", zap.Error(err)) diff --git a/server/channelserver/handlers_rengoku.go b/server/channelserver/handlers_rengoku.go index b1d40fb7b..210e756de 100644 --- a/server/channelserver/handlers_rengoku.go +++ b/server/channelserver/handlers_rengoku.go @@ -16,8 +16,8 @@ func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) { // saved every floor on road, holds values such as floors progressed, points etc. // can be safely handled by the client pkt := p.(*mhfpacket.MsgMhfSaveRengokuData) - if len(pkt.RawDataPayload) < 91 { - s.logger.Warn("Rengoku payload too short", zap.Int("len", len(pkt.RawDataPayload))) + if len(pkt.RawDataPayload) < 91 || len(pkt.RawDataPayload) > 4096 { + s.logger.Warn("Rengoku payload size out of range", zap.Int("len", len(pkt.RawDataPayload))) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) return }