-
Notifications
You must be signed in to change notification settings - Fork 162
fix(security): add file content validation for map transfer #1819
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
97d61d7
73e763d
0599333
c2d0135
58ae26c
59f93ca
41048c3
c87d395
1812801
fc9a68c
2bdc12a
4505e7b
bc37447
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -457,3 +457,121 @@ Bool FileSystem::isPathInDirectory(const AsciiString& testPath, const AsciiStrin | |||||
|
|
||||||
| return true; | ||||||
| } | ||||||
|
|
||||||
| namespace | ||||||
| { | ||||||
|
|
||||||
| enum TransferFileType | ||||||
| { | ||||||
| TRANSFER_FILE_MAP, | ||||||
| TRANSFER_FILE_INI, | ||||||
| TRANSFER_FILE_STR, | ||||||
| TRANSFER_FILE_TXT, | ||||||
| TRANSFER_FILE_TGA, | ||||||
| TRANSFER_FILE_WAK, | ||||||
| TRANSFER_FILE_COUNT | ||||||
| }; | ||||||
|
|
||||||
| struct TransferFileRule | ||||||
| { | ||||||
| const char* ext; | ||||||
| Int maxSize; | ||||||
| TransferFileType type; | ||||||
| }; | ||||||
|
|
||||||
| const TransferFileRule transferFileRules[] = | ||||||
| { | ||||||
| { ".map", 5 * 1024 * 1024, TRANSFER_FILE_MAP }, | ||||||
| { ".ini", 512 * 1024, TRANSFER_FILE_INI }, | ||||||
| { ".str", 512 * 1024, TRANSFER_FILE_STR }, | ||||||
| { ".txt", 512 * 1024, TRANSFER_FILE_TXT }, | ||||||
| { ".tga", 2 * 1024 * 1024, TRANSFER_FILE_TGA }, | ||||||
| { ".wak", 512 * 1024, TRANSFER_FILE_WAK }, | ||||||
| }; | ||||||
|
|
||||||
| const TransferFileRule* getTransferFileRule(const char* extension) | ||||||
| { | ||||||
| for (Int i = 0; i < ARRAY_SIZE(transferFileRules); ++i) | ||||||
| { | ||||||
| if (stricmp(extension, transferFileRules[i].ext) == 0) | ||||||
| { | ||||||
| return &transferFileRules[i]; | ||||||
| } | ||||||
| } | ||||||
| return nullptr; | ||||||
| } | ||||||
|
|
||||||
| } // namespace | ||||||
|
|
||||||
| //============================================================================ | ||||||
| // FileSystem::hasValidTransferFileContent | ||||||
| //============================================================================ | ||||||
| // TheSuperHackers @security bobtista 12/02/2026 Validates transferred file | ||||||
| // content in memory before writing to disk. | ||||||
| Bool FileSystem::hasValidTransferFileContent(const AsciiString& filePath, const UnsignedByte* data, Int dataSize) | ||||||
| { | ||||||
| const char* lastDot = strrchr(filePath.str(), '.'); | ||||||
| if (lastDot == nullptr) | ||||||
| { | ||||||
| DEBUG_LOG(("File '%s' has no extension for content validation.", filePath.str())); | ||||||
| return false; | ||||||
| } | ||||||
|
|
||||||
| const TransferFileRule* rule = getTransferFileRule(lastDot); | ||||||
| if (rule == nullptr) | ||||||
| { | ||||||
| DEBUG_LOG(("File '%s' has unrecognized extension '%s' for content validation.", filePath.str(), lastDot)); | ||||||
| return false; | ||||||
| } | ||||||
|
|
||||||
| // Check size limit | ||||||
| if (dataSize > rule->maxSize) | ||||||
| { | ||||||
| DEBUG_LOG(("File '%s' exceeds maximum size (%d bytes, limit %d bytes).", filePath.str(), dataSize, rule->maxSize)); | ||||||
| return false; | ||||||
| } | ||||||
|
|
||||||
| // Extension-specific content validation | ||||||
| switch (rule->type) | ||||||
| { | ||||||
| case TRANSFER_FILE_MAP: | ||||||
| if (dataSize < 4 || memcmp(data, "CkMp", 4) != 0) | ||||||
| { | ||||||
| DEBUG_LOG(("Map file '%s' has invalid magic bytes.", filePath.str())); | ||||||
| return false; | ||||||
| } | ||||||
| break; | ||||||
|
|
||||||
| case TRANSFER_FILE_INI: | ||||||
| { | ||||||
| Int bytesToCheck = std::min(dataSize, 512); | ||||||
| for (Int i = 0; i < bytesToCheck; ++i) | ||||||
| { | ||||||
| if (data[i] == 0) | ||||||
| { | ||||||
| DEBUG_LOG(("INI file '%s' contains null bytes (likely binary).", filePath.str())); | ||||||
| return false; | ||||||
| } | ||||||
| } | ||||||
| break; | ||||||
| } | ||||||
|
|
||||||
| case TRANSFER_FILE_TGA: | ||||||
| if (dataSize < 44) | ||||||
| { | ||||||
| DEBUG_LOG(("TGA file '%s' is too small to be valid (minimum header 18 + footer 26 = 44 bytes).", filePath.str())); | ||||||
| return false; | ||||||
| } | ||||||
| if (memcmp(data + dataSize - 18, "TRUEVISION-XFILE.", 18) != 0) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TGA footer validation is checking wrong offset and size. The TGA 2.0 footer is 26 bytes (see Current code checks last 18 bytes, which skips the 8-byte Extension and Developer fields and reads into image data instead of the footer.
Suggested change
Also update line 560 to check Prompt To Fix With AIThis is a comment left during a code review.
Path: Core/GameEngine/Source/Common/System/FileSystem.cpp
Line: 565:565
Comment:
TGA footer validation is checking wrong offset and size. The TGA 2.0 footer is 26 bytes (see `TARGA.h:134-142`), not 18. The footer consists of Extension(4) + Developer(4) + Signature[16] + RsvdChar(1) + BZST(1) = 26 bytes total.
Current code checks last 18 bytes, which skips the 8-byte Extension and Developer fields and reads into image data instead of the footer.
```suggestion
if (memcmp(data + dataSize - 26, "TRUEVISION-XFILE.", 18) != 0)
```
Also update line 560 to check `dataSize < 26` instead of `dataSize < 18` since a valid TGA with footer needs at least header(18) + footer(26) = 44 bytes minimum.
How can I resolve this? If you propose a fix, please make it concise.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The offset is correct — you can verify in Core/Libraries/Source/WWVegas/WWLib/TARGA.h:134-142: The Signature field starts at footer offset 8, which is EOF - 18 (26 - 8). Our memcmp(data + |
||||||
| { | ||||||
| DEBUG_LOG(("TGA file '%s' is missing TRUEVISION-XFILE footer signature.", filePath.str())); | ||||||
| return false; | ||||||
| } | ||||||
| break; | ||||||
|
|
||||||
| default: | ||||||
| break; | ||||||
| } | ||||||
|
|
||||||
| return true; | ||||||
| } | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Align columns in lookup table
Per project style, data-oriented lookup tables should have aligned columns for readability.
Context Used: Rule from
dashboard- Align columns in data-oriented blocks (like lookup tables, initialization lists, or structured data)... (source)Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI