mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-06-08 15:54:22 +00:00
fix: security vulnerabilities (#1980)
* fix: security vulnerabilities Tested that all functions related to the touched files work will test sqlite on a CI build * fix failing test * ai feedback * add buffer size checking * use c_str * dont log session key * Try this for a mac definition * be quiet apple
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
#include <stdexcept>
|
||||
|
||||
#include "Amf3.h"
|
||||
#include "StringifiedEnum.h"
|
||||
|
||||
/**
|
||||
* AMF3 Reference document https://rtmp.veriskope.com/pdf/amf3-file-format-spec.pdf
|
||||
@@ -53,7 +54,7 @@ std::unique_ptr<AMFBaseValue> AMFDeserialize::Read(RakNet::BitStream& inStream)
|
||||
case eAmf::VectorObject:
|
||||
[[fallthrough]];
|
||||
case eAmf::Dictionary:
|
||||
throw marker;
|
||||
throw std::invalid_argument(StringifiedEnum::ToString(marker).data());
|
||||
default:
|
||||
throw std::invalid_argument("Invalid AMF3 marker" + std::to_string(static_cast<int32_t>(marker)));
|
||||
}
|
||||
@@ -88,6 +89,11 @@ const std::string AMFDeserialize::ReadString(RakNet::BitStream& inStream) {
|
||||
// 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
|
||||
@@ -117,6 +123,12 @@ std::unique_ptr<AMFArrayValue> AMFDeserialize::ReadAmfArray(RakNet::BitStream& i
|
||||
if (key.size() == 0) break;
|
||||
arrayValue->Insert(key, Read(inStream));
|
||||
}
|
||||
|
||||
constexpr int32_t maxArraySize = 10'000;
|
||||
if (sizeOfDenseArray > maxArraySize) {
|
||||
LOG("Someone sent 10,000 dense array entries, probably a bad packet.");
|
||||
throw std::invalid_argument("Someone sent 10,000 dense array entries, probably a bad packet.");
|
||||
}
|
||||
// Finally read dense portion
|
||||
for (uint32_t i = 0; i < sizeOfDenseArray; i++) {
|
||||
arrayValue->Insert(i, Read(inStream));
|
||||
|
||||
@@ -52,8 +52,7 @@ uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() {
|
||||
|
||||
if (actualUncompressedSize != -1) {
|
||||
uint32_t previousSize = completeUncompressedModel.size();
|
||||
completeUncompressedModel.append(reinterpret_cast<char*>(uncompressedChunk.get()));
|
||||
completeUncompressedModel.resize(previousSize + actualUncompressedSize);
|
||||
completeUncompressedModel.append(reinterpret_cast<char*>(uncompressedChunk.get()), actualUncompressedSize);
|
||||
} else {
|
||||
LOG("Failed to inflate chunk %i for model %llu. Error: %i", chunkCount, model.id, err);
|
||||
break;
|
||||
|
||||
@@ -308,8 +308,9 @@ std::vector<std::string> GeneralUtils::GetSqlFileNamesFromFolder(const std::stri
|
||||
for (const auto& t : std::filesystem::directory_iterator(folder)) {
|
||||
if (t.is_directory() || t.is_symlink()) continue;
|
||||
auto filename = t.path().filename().string();
|
||||
const auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0));
|
||||
filenames.emplace(index, std::move(filename));
|
||||
// Ensure the file has a name in the format of xxxxxxxx_anything_goes_here.sql
|
||||
const auto migrationNumber = TryParse<uint32_t>(GeneralUtils::SplitString(filename, '_').at(0));
|
||||
if (migrationNumber.has_value()) filenames.emplace(migrationNumber.value(), std::move(filename));
|
||||
}
|
||||
|
||||
// Now sort the map by the oldest migration.
|
||||
|
||||
@@ -205,6 +205,12 @@ namespace GeneralUtils {
|
||||
return isParsed ? static_cast<T>(result) : std::optional<T>{};
|
||||
}
|
||||
|
||||
// A version of TryParse that will return `errorVal` if `str` failed to parse.
|
||||
template <Numeric T>
|
||||
[[nodiscard]] T TryParse(std::string_view str, const T errorVal) {
|
||||
return TryParse<T>(str).value_or(errorVal);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires(!Numeric<T>)
|
||||
[[nodiscard]] std::optional<T> TryParse(std::string_view str);
|
||||
@@ -237,6 +243,12 @@ namespace GeneralUtils {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// A version of TryParse that will return `errorVal` if `str` failed to parse.
|
||||
template <std::floating_point T>
|
||||
[[nodiscard]] T TryParse(std::string_view str, const T errorVal) {
|
||||
return TryParse<T>(str).value_or(errorVal);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
@@ -258,6 +270,11 @@ namespace GeneralUtils {
|
||||
return z ? std::make_optional<T>(x.value(), y.value(), z.value()) : std::nullopt;
|
||||
}
|
||||
|
||||
// Alternative overload of TryParse with a default value
|
||||
[[nodiscard]] inline NiPoint3 TryParse(const std::string_view strX, const std::string_view strY, const std::string_view strZ, const NiPoint3 errorVal) {
|
||||
return TryParse<NiPoint3>(strX, strY, strZ).value_or(errorVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* The TryParse overload for handling NiPoint3 by passing a span of three strings
|
||||
* @param str The string vector representing the X, Y, and Z coordinates
|
||||
@@ -268,6 +285,11 @@ namespace GeneralUtils {
|
||||
return (str.size() == 3) ? TryParse<T>(str[0], str[1], str[2]) : std::nullopt;
|
||||
}
|
||||
|
||||
// Alternative overload of TryParse with a default value
|
||||
[[nodiscard]] inline NiPoint3 TryParse(const std::span<const std::string> str, const NiPoint3 errorVal) {
|
||||
return TryParse<NiPoint3>(str).value_or(errorVal);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::u16string to_u16string(const T value) {
|
||||
return GeneralUtils::ASCIIToUTF16(std::to_string(value));
|
||||
|
||||
@@ -53,16 +53,21 @@ Lxfml::Result Lxfml::NormalizePositionOnlyFirstPart(const std::string_view data)
|
||||
continue;
|
||||
}
|
||||
|
||||
auto x = GeneralUtils::TryParse<float>(split[9]).value();
|
||||
auto y = GeneralUtils::TryParse<float>(split[10]).value();
|
||||
auto z = GeneralUtils::TryParse<float>(split[11]).value();
|
||||
if (x < lowest.x) lowest.x = x;
|
||||
if (y < lowest.y) lowest.y = y;
|
||||
if (z < lowest.z) lowest.z = z;
|
||||
try {
|
||||
auto x = GeneralUtils::TryParse<float>(split[9]).value();
|
||||
auto y = GeneralUtils::TryParse<float>(split[10]).value();
|
||||
auto z = GeneralUtils::TryParse<float>(split[11]).value();
|
||||
if (x < lowest.x) lowest.x = x;
|
||||
if (y < lowest.y) lowest.y = y;
|
||||
if (z < lowest.z) lowest.z = z;
|
||||
|
||||
if (highest.x < x) highest.x = x;
|
||||
if (highest.y < y) highest.y = y;
|
||||
if (highest.z < z) highest.z = z;
|
||||
if (highest.x < x) highest.x = x;
|
||||
if (highest.y < y) highest.y = y;
|
||||
if (highest.z < z) highest.z = z;
|
||||
} catch (std::exception& e) {
|
||||
LOG("Failed to parse a split value of either (%s), (%s), or (%s).", split[9].c_str(), split[10].c_str(), split[11].c_str());
|
||||
return toReturn; // Early return since we failed to parse this lxfml.
|
||||
}
|
||||
}
|
||||
|
||||
auto delta = (highest - lowest) / 2.0f;
|
||||
|
||||
@@ -45,6 +45,12 @@ Sd0::Sd0(std::istream& buffer) {
|
||||
uint32_t bufferSize = buffer.tellg();
|
||||
buffer.seekg(0, std::ios::beg);
|
||||
WriteSize(firstChunk, bufferSize);
|
||||
// its expected that if we got here, we got an old sd0 buffer where we ignored the sd0 part
|
||||
// that means this can be at most the compressed chunk limit.
|
||||
if (bufferSize > MAX_UNCOMPRESSED_CHUNK_SIZE) {
|
||||
LOG("Possible bad chunk size of %i specified, rejecting.", bufferSize);
|
||||
return;
|
||||
}
|
||||
firstChunk.resize(firstChunk.size() + bufferSize);
|
||||
auto* dataStart = reinterpret_cast<char*>(firstChunk.data() + GetDataOffset(true));
|
||||
if (!buffer.read(dataStart, bufferSize)) {
|
||||
@@ -71,6 +77,12 @@ Sd0::Sd0(std::istream& buffer) {
|
||||
|
||||
WriteSize(chunk, chunkSize);
|
||||
|
||||
// Assuming a good buffer that is large enough to take up 2 zlib buffers
|
||||
// any buffer should be compressed enough to take up less size than its uncompressed counterpart
|
||||
if (chunkSize > MAX_UNCOMPRESSED_CHUNK_SIZE) {
|
||||
LOG("Possible bad chunk size of %i specified, rejecting.", chunkSize);
|
||||
break;
|
||||
}
|
||||
chunk.resize(chunkSize + dataOffset);
|
||||
auto* dataStart = reinterpret_cast<char*>(chunk.data() + dataOffset);
|
||||
if (!buffer.read(dataStart, chunkSize)) {
|
||||
@@ -95,6 +107,11 @@ void Sd0::FromData(const uint8_t* data, size_t bufferSize) {
|
||||
startOffset, numToCopy,
|
||||
compressedChunk.data(), compressedChunk.size());
|
||||
|
||||
if (compressedSize == -1) {
|
||||
LOG("Failed to compress chunk, aborting");
|
||||
break;
|
||||
}
|
||||
|
||||
auto& chunk = m_Chunks.emplace_back();
|
||||
bool firstBuffer = m_Chunks.size() == 1;
|
||||
auto dataOffset = GetDataOffset(firstBuffer);
|
||||
@@ -119,6 +136,12 @@ std::string Sd0::GetAsStringUncompressed() const {
|
||||
auto dataOffset = GetDataOffset(first);
|
||||
first = false;
|
||||
const auto chunkSize = chunk.size();
|
||||
if (chunkSize <= static_cast<size_t>(dataOffset)) {
|
||||
LOG("Bad chunkSize for data, aborting");
|
||||
toReturn = "";
|
||||
totalSize = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
auto oldSize = toReturn.size();
|
||||
toReturn.resize(oldSize + MAX_UNCOMPRESSED_CHUNK_SIZE);
|
||||
@@ -128,6 +151,13 @@ std::string Sd0::GetAsStringUncompressed() const {
|
||||
reinterpret_cast<uint8_t*>(toReturn.data()) + oldSize, MAX_UNCOMPRESSED_CHUNK_SIZE,
|
||||
error);
|
||||
|
||||
if (uncompressedSize == -1) {
|
||||
LOG("Failed to decompress chunk, aborting");
|
||||
toReturn = "";
|
||||
totalSize = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
totalSize += uncompressedSize;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
#include "zlib.h"
|
||||
|
||||
namespace ZCompression {
|
||||
int32_t GetMaxCompressedLength(int32_t nLenSrc) {
|
||||
int32_t n16kBlocks = (nLenSrc + 16383) / 16384; // round up any fraction of a block
|
||||
uint32_t GetMaxCompressedLength(uint32_t nLenSrc) {
|
||||
uint32_t n16kBlocks = (nLenSrc + 16383) / 16384; // round up any fraction of a block
|
||||
return (nLenSrc + 6 + (n16kBlocks * 5));
|
||||
}
|
||||
|
||||
int32_t Compress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst) {
|
||||
int32_t Compress(const uint8_t* abSrc, uint32_t nLenSrc, uint8_t* abDst, uint32_t nLenDst) {
|
||||
z_stream zInfo = { 0 };
|
||||
zInfo.total_in = zInfo.avail_in = nLenSrc;
|
||||
zInfo.total_out = zInfo.avail_out = nLenDst;
|
||||
@@ -27,7 +27,7 @@ namespace ZCompression {
|
||||
return(nRet);
|
||||
}
|
||||
|
||||
int32_t Decompress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst, int32_t& nErr) {
|
||||
int32_t Decompress(const uint8_t* abSrc, uint32_t nLenSrc, uint8_t* abDst, uint32_t nLenDst, int32_t& nErr) {
|
||||
// Get the size of the decompressed data
|
||||
z_stream zInfo = { 0 };
|
||||
zInfo.total_in = zInfo.avail_in = nLenSrc;
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
#include <cstdint>
|
||||
|
||||
namespace ZCompression {
|
||||
int32_t GetMaxCompressedLength(int32_t nLenSrc);
|
||||
uint32_t GetMaxCompressedLength(uint32_t nLenSrc);
|
||||
|
||||
int32_t Compress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst);
|
||||
int32_t Compress(const uint8_t* abSrc, uint32_t nLenSrc, uint8_t* abDst, uint32_t nLenDst);
|
||||
|
||||
int32_t Decompress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst, int32_t& nErr);
|
||||
int32_t Decompress(const uint8_t* abSrc, uint32_t nLenSrc, uint8_t* abDst, uint32_t nLenDst, int32_t& nErr);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "zlib.h"
|
||||
|
||||
constexpr uint32_t CRC32_INIT = 0xFFFFFFFF;
|
||||
constexpr auto NULL_TERMINATOR = std::string_view{"\0\0\0", 4};
|
||||
constexpr auto NULL_TERMINATOR = std::string_view{ "\0\0\0", 4 };
|
||||
|
||||
AssetManager::AssetManager(const std::filesystem::path& path) {
|
||||
if (!std::filesystem::is_directory(path)) {
|
||||
@@ -25,7 +25,7 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
|
||||
if (!std::filesystem::exists(m_Path / ".." / "versions")) {
|
||||
throw std::runtime_error("No \"versions\" directory found in the parent directories of \"res\" - packed asset bundle cannot be loaded.");
|
||||
}
|
||||
|
||||
|
||||
m_AssetBundleType = eAssetBundleType::Packed;
|
||||
|
||||
m_RootPath = (m_Path / "..");
|
||||
@@ -34,7 +34,7 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
|
||||
if (!std::filesystem::exists(m_Path / ".." / ".." / "versions")) {
|
||||
throw std::runtime_error("No \"versions\" directory found in the parent directories of \"res\" - packed asset bundle cannot be loaded.");
|
||||
}
|
||||
|
||||
|
||||
m_AssetBundleType = eAssetBundleType::Packed;
|
||||
|
||||
m_RootPath = (m_Path / ".." / "..");
|
||||
@@ -54,15 +54,15 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
|
||||
}
|
||||
|
||||
switch (m_AssetBundleType) {
|
||||
case eAssetBundleType::Packed: {
|
||||
this->LoadPackIndex();
|
||||
break;
|
||||
}
|
||||
case eAssetBundleType::None:
|
||||
[[fallthrough]];
|
||||
case eAssetBundleType::Unpacked: {
|
||||
break;
|
||||
}
|
||||
case eAssetBundleType::Packed: {
|
||||
this->LoadPackIndex();
|
||||
break;
|
||||
}
|
||||
case eAssetBundleType::None:
|
||||
[[fallthrough]];
|
||||
case eAssetBundleType::Unpacked: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ bool AssetManager::HasFile(std::string fixedName) const {
|
||||
std::replace(fixedName.begin(), fixedName.end(), '\\', '/');
|
||||
if (std::filesystem::exists(m_ResPath / fixedName)) return true;
|
||||
|
||||
if (this->m_AssetBundleType == eAssetBundleType::Unpacked) return false;
|
||||
if (this->m_AssetBundleType == eAssetBundleType::Unpacked || !m_PackIndex) return false;
|
||||
|
||||
std::replace(fixedName.begin(), fixedName.end(), '/', '\\');
|
||||
if (fixedName.rfind("client\\res\\", 0) != 0) fixedName = "client\\res\\" + fixedName;
|
||||
@@ -145,8 +145,12 @@ bool AssetManager::GetFile(std::string fixedName, char** data, uint32_t* len) co
|
||||
}
|
||||
|
||||
const auto& pack = this->m_PackIndex->GetPacks().at(packIndex);
|
||||
const bool success = pack.ReadFileFromPack(crc, data, len);
|
||||
|
||||
bool success = false;
|
||||
try {
|
||||
success = pack.ReadFileFromPack(crc, data, len);
|
||||
} catch (std::exception& e) {
|
||||
LOG("Failed to read file %s from pack file", fixedName.c_str());
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ bool Pack::HasFile(const uint32_t crc) const {
|
||||
}
|
||||
|
||||
bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) const {
|
||||
const auto pathStr = m_FilePath.string();
|
||||
// Time for some wacky C file reading for speed reasons
|
||||
|
||||
PackRecord pkRecord{};
|
||||
@@ -65,16 +66,21 @@ bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) cons
|
||||
bool isCompressed = (pkRecord.m_IsCompressed & 0xff) > 0;
|
||||
auto inPackSize = isCompressed ? pkRecord.m_CompressedSize : pkRecord.m_UncompressedSize;
|
||||
|
||||
FILE* file;
|
||||
FILE* file = nullptr;
|
||||
#ifdef _WIN32
|
||||
fopen_s(&file, m_FilePath.string().c_str(), "rb");
|
||||
fopen_s(&file, pathStr.c_str(), "rb");
|
||||
#elif __APPLE__
|
||||
// macOS has 64bit file IO by default
|
||||
file = fopen(m_FilePath.string().c_str(), "rb");
|
||||
file = fopen(pathStr.c_str(), "rb");
|
||||
#else
|
||||
file = fopen64(m_FilePath.string().c_str(), "rb");
|
||||
file = fopen64(pathStr.c_str(), "rb");
|
||||
#endif
|
||||
|
||||
if (!file) {
|
||||
LOG("No file found for path %s", pathStr.c_str());
|
||||
throw std::runtime_error("Could not find file " + pathStr);
|
||||
}
|
||||
|
||||
fseek(file, pos, SEEK_SET);
|
||||
|
||||
if (!isCompressed) {
|
||||
@@ -102,14 +108,18 @@ bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) cons
|
||||
int32_t readInData = fread(&size, sizeof(uint32_t), 1, file);
|
||||
pos += 4; // Move pointer position 4 to the right
|
||||
|
||||
char* chunk = static_cast<char*>(malloc(size));
|
||||
int32_t readInData2 = fread(chunk, sizeof(int8_t), size, file);
|
||||
std::unique_ptr<char[]> chunk(new char[size]);
|
||||
int32_t readInData2 = fread(chunk.get(), sizeof(int8_t), size, file);
|
||||
pos += size; // Move pointer position the amount of bytes read to the right
|
||||
|
||||
int32_t err;
|
||||
currentReadPos += ZCompression::Decompress(reinterpret_cast<uint8_t*>(chunk), size, reinterpret_cast<uint8_t*>(decompressedData + currentReadPos), Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE, err);
|
||||
const auto countToRead = ZCompression::Decompress(reinterpret_cast<uint8_t*>(chunk.get()), size, reinterpret_cast<uint8_t*>(decompressedData + currentReadPos), Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE, err);
|
||||
if (countToRead == -1) {
|
||||
LOG("Error decompressing zlib data from file %s", pathStr.c_str());
|
||||
throw std::runtime_error("Error decompressing zlib data from file " + pathStr);
|
||||
}
|
||||
currentReadPos += countToRead;
|
||||
|
||||
free(chunk);
|
||||
}
|
||||
|
||||
*data = decompressedData;
|
||||
|
||||
@@ -84,3 +84,7 @@ void dConfig::ProcessLine(const std::string& line) {
|
||||
|
||||
this->m_ConfigValues.insert(std::make_pair(key, value));
|
||||
}
|
||||
|
||||
std::string dConfig::GetValue(const std::string& key, const char* emptyValue) {
|
||||
return GetValue(key, std::string(emptyValue));
|
||||
};
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "GeneralUtils.h"
|
||||
|
||||
class dConfig {
|
||||
public:
|
||||
dConfig(const std::string& filepath);
|
||||
@@ -22,6 +24,14 @@ public:
|
||||
*/
|
||||
const std::string& GetValue(std::string key);
|
||||
|
||||
// Gets a value from the config and returns the parsed value, or the default value should parsing have failed.
|
||||
template<typename T>
|
||||
T GetValue(const std::string& key, const T emptyValue = T()) {
|
||||
return GeneralUtils::TryParse<T>(GetValue(key)).value_or(emptyValue);
|
||||
}
|
||||
|
||||
std::string GetValue(const std::string& key, const char* emptyValue);
|
||||
|
||||
/**
|
||||
* Loads the config from a file
|
||||
*/
|
||||
@@ -43,3 +53,9 @@ private:
|
||||
std::vector<std::function<void()>> m_ConfigHandlers;
|
||||
std::string m_ConfigFilePath;
|
||||
};
|
||||
|
||||
template<>
|
||||
inline std::string dConfig::GetValue(const std::string& key, const std::string emptyValue) {
|
||||
const auto& value = GetValue(key);
|
||||
return value.empty() ? emptyValue : value;
|
||||
};
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
// These are the same define, but they mean two different things in different contexts
|
||||
// so a different define to distinguish what calculation is happening will help clarity.
|
||||
#define FRAMES_TO_MS(x) 1000 / x
|
||||
#define MS_TO_FRAMES(x) 1000 / x
|
||||
#define FRAMES_TO_MS(x) (1000 / (x))
|
||||
#define MS_TO_FRAMES(x) (1000 / (x))
|
||||
|
||||
//=========== FRAME TIMINGS ===========
|
||||
constexpr uint32_t highFramerate = 60;
|
||||
|
||||
Reference in New Issue
Block a user