mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-14 07:47:13 +00:00
The AMF3 reader read an array inside an array to any depth and kept reading values after the data had ended. It now refuses arrays nested more than 64 deep and throws when the data ends before a value does. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
174 lines
5.8 KiB
C++
174 lines
5.8 KiB
C++
#include "AMFDeserialize.h"
|
|
|
|
#include <stdexcept>
|
|
|
|
#include "Amf3.h"
|
|
#include "StringifiedEnum.h"
|
|
|
|
/**
|
|
* AMF3 Reference document https://rtmp.veriskope.com/pdf/amf3-file-format-spec.pdf
|
|
* AMF3 Deserializer written by EmosewaMC
|
|
*/
|
|
|
|
std::unique_ptr<AMFBaseValue> AMFDeserialize::Read(RakNet::BitStream& inStream) {
|
|
// Every value counts against the budget for this deserializer, so one message
|
|
// cannot make the server build an unbounded tree of values.
|
|
if (++m_ValuesRead > MaxValues) {
|
|
LOG("AMF value budget of %u exceeded, possible spoof, aborting deserialize.", MaxValues);
|
|
throw std::invalid_argument("AMF value budget exceeded");
|
|
}
|
|
|
|
// Read in the value type from the bitStream
|
|
eAmf marker;
|
|
if (!inStream.Read(marker)) {
|
|
throw std::invalid_argument("The AMF3 data ended before its value did");
|
|
}
|
|
// Based on the typing, create the value associated with that and return the base value class
|
|
switch (marker) {
|
|
case eAmf::Undefined:
|
|
return std::make_unique<AMFBaseValue>();
|
|
case eAmf::Null:
|
|
return std::make_unique<AMFNullValue>();
|
|
case eAmf::False:
|
|
return std::make_unique<AMFBoolValue>(false);
|
|
case eAmf::True:
|
|
return std::make_unique<AMFBoolValue>(true);
|
|
case eAmf::Integer:
|
|
return ReadAmfInteger(inStream);
|
|
case eAmf::Double:
|
|
return ReadAmfDouble(inStream);
|
|
case eAmf::String:
|
|
return ReadAmfString(inStream);
|
|
case eAmf::Array:
|
|
return ReadAmfArray(inStream);
|
|
|
|
// These values are unimplemented in the live client and will remain unimplemented
|
|
// unless someone modifies the client to allow serializing of these values.
|
|
case eAmf::XMLDoc:
|
|
[[fallthrough]];
|
|
case eAmf::Date:
|
|
[[fallthrough]];
|
|
case eAmf::Object:
|
|
[[fallthrough]];
|
|
case eAmf::XML:
|
|
[[fallthrough]];
|
|
case eAmf::ByteArray:
|
|
[[fallthrough]];
|
|
case eAmf::VectorInt:
|
|
[[fallthrough]];
|
|
case eAmf::VectorUInt:
|
|
[[fallthrough]];
|
|
case eAmf::VectorDouble:
|
|
[[fallthrough]];
|
|
case eAmf::VectorObject:
|
|
[[fallthrough]];
|
|
case eAmf::Dictionary:
|
|
throw std::invalid_argument(StringifiedEnum::ToString(marker).data());
|
|
default:
|
|
throw std::invalid_argument("Invalid AMF3 marker" + std::to_string(static_cast<int32_t>(marker)));
|
|
}
|
|
}
|
|
|
|
uint32_t AMFDeserialize::ReadU29(RakNet::BitStream& inStream) {
|
|
bool byteFlag = true;
|
|
uint32_t actualNumber{};
|
|
uint8_t numberOfBytesRead{};
|
|
while (byteFlag && numberOfBytesRead < 4) {
|
|
uint8_t byte{};
|
|
inStream.Read(byte);
|
|
// Parse the byte
|
|
if (numberOfBytesRead < 3) {
|
|
byteFlag = byte & static_cast<uint8_t>(1 << 7);
|
|
byte = byte << 1UL;
|
|
}
|
|
// Combine the read byte with our current read in number
|
|
actualNumber <<= 8UL;
|
|
actualNumber |= static_cast<uint32_t>(byte);
|
|
// If we are not done reading in bytes, shift right 1 bit
|
|
if (numberOfBytesRead < 3) actualNumber = actualNumber >> 1UL;
|
|
numberOfBytesRead++;
|
|
}
|
|
return actualNumber;
|
|
}
|
|
|
|
const std::string AMFDeserialize::ReadString(RakNet::BitStream& inStream) {
|
|
auto length = ReadU29(inStream);
|
|
// Check if this is a reference
|
|
bool isReference = length % 2 == 1;
|
|
// Right shift by 1 bit to get index if reference or size of next string if value
|
|
length = length >> 1;
|
|
if (isReference) {
|
|
constexpr int32_t maxStringSize = 1024 * 1024;
|
|
if (length > maxStringSize) {
|
|
LOG("1MB string attempted to be allocated in AMF deserialize, possible spoof, aborting deserialize.");
|
|
throw std::invalid_argument("1MB string attempted to be allocated in AMF deserialize, possible spoof, aborting deserialize.");
|
|
}
|
|
std::string value(length, 0);
|
|
inStream.Read(&value[0], length);
|
|
// Empty strings are never sent by reference
|
|
if (!value.empty()) accessedElements.push_back(value);
|
|
return value;
|
|
} else {
|
|
// Length is a reference to a previous index - use that as the read in value
|
|
return accessedElements.at(length);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<AMFDoubleValue> AMFDeserialize::ReadAmfDouble(RakNet::BitStream& inStream) {
|
|
double value;
|
|
inStream.Read<double>(value);
|
|
return std::make_unique<AMFDoubleValue>(value);
|
|
}
|
|
|
|
std::unique_ptr<AMFArrayValue> AMFDeserialize::ReadAmfArray(RakNet::BitStream& inStream) {
|
|
// An array inside an array inside an array and so on only goes so deep
|
|
if (m_Depth >= MaxDepth) {
|
|
LOG("AMF arrays nested deeper than %u, aborting deserialize.", MaxDepth);
|
|
throw std::invalid_argument("AMF3 arrays nested too deep");
|
|
}
|
|
|
|
// Counts this array for as long as it is being read, also when a value in it throws
|
|
struct DepthGuard {
|
|
uint32_t& depth;
|
|
explicit DepthGuard(uint32_t& depth) : depth(depth) { ++depth; }
|
|
~DepthGuard() { --depth; }
|
|
} depthGuard(m_Depth);
|
|
|
|
auto arrayValue = std::make_unique<AMFArrayValue>();
|
|
|
|
// Read size of dense array
|
|
const auto sizeOfDenseArray = (ReadU29(inStream) >> 1);
|
|
if (sizeOfDenseArray > MaxArraySize) {
|
|
LOG("Someone sent %u dense array entries, probably a bad packet.", sizeOfDenseArray);
|
|
throw std::invalid_argument("Too many dense AMF array entries");
|
|
}
|
|
|
|
// Then read associative portion
|
|
uint32_t associativeEntries = 0;
|
|
while (true) {
|
|
const auto key = ReadString(inStream);
|
|
// No more associative values when we encounter an empty string key
|
|
if (key.size() == 0) break;
|
|
if (++associativeEntries > MaxArraySize) {
|
|
LOG("Someone sent more than %u associative array entries, probably a bad packet.", MaxArraySize);
|
|
throw std::invalid_argument("Too many associative AMF array entries");
|
|
}
|
|
arrayValue->Insert(key, Read(inStream));
|
|
}
|
|
|
|
// Finally read dense portion
|
|
for (uint32_t i = 0; i < sizeOfDenseArray; i++) {
|
|
arrayValue->Insert(i, Read(inStream));
|
|
}
|
|
|
|
return arrayValue;
|
|
}
|
|
|
|
std::unique_ptr<AMFStringValue> AMFDeserialize::ReadAmfString(RakNet::BitStream& inStream) {
|
|
return std::make_unique<AMFStringValue>(ReadString(inStream));
|
|
}
|
|
|
|
std::unique_ptr<AMFIntValue> AMFDeserialize::ReadAmfInteger(RakNet::BitStream& inStream) {
|
|
return std::make_unique<AMFIntValue>(ReadU29(inStream)); // NOTE: NARROWING CONVERSION FROM UINT TO INT. IS THIS INTENDED?
|
|
}
|