fix(security): add file content validation for map transfer#1819
fix(security): add file content validation for map transfer#1819bobtista wants to merge 13 commits intoTheSuperHackers:mainfrom
Conversation
630dbcb to
218f4b9
Compare
218f4b9 to
97d61d7
Compare
|
Were all comments from SkyAero worked on? |
| DEBUG_LOG(("Wrote %d bytes to file %s!", len, realFileName.str())); | ||
|
|
||
| // TheSuperHackers @security bobtista 06/11/2025 Validate file content after writing | ||
| if (!FileSystem::hasValidTransferFileContent(realFileName)) |
There was a problem hiding this comment.
Why is this validated after write? Better validate when in memory, before file write.
|
| Filename | Overview |
|---|---|
| Core/GameEngine/Include/Common/FileSystem.h | Adds hasValidTransferFileContent static method declaration. Clean, minimal change to the public interface. |
| Core/GameEngine/Source/Common/System/FileSystem.cpp | Implements file content validation with per-type rules (magic bytes, null-byte detection, TGA 2.0 footer). Lookup table columns should be aligned per project style. Logic is sound and TGA footer offset is correct per TARGA.h definitions. |
| Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp | Integrates content validation call after TGA decompression but before disk write. Memory cleanup for COMPRESS_TARGAS path is correctly handled. Early return pattern is consistent with existing security checks in the function. |
Flowchart
flowchart TD
A[processFile receives NetFileCommandMsg] --> B{Path normalized?}
B -->|No| C[Abort: invalid path]
B -->|Yes| D{Valid extension?}
D -->|No| E[Abort: bad extension]
D -->|Yes| F{TGA compressed?}
F -->|Yes| G[Decompress TGA]
F -->|No| H[hasValidTransferFileContent]
G --> H
H --> I{Lookup extension rule}
I -->|Unknown ext| J[Reject]
I -->|Known ext| K{Size <= maxSize?}
K -->|No| L[Reject: too large]
K -->|Yes| M{Type-specific check}
M -->|.map| N{Magic bytes CkMp?}
M -->|.ini| O{No null bytes in first 512?}
M -->|.tga| P{Size >= 44 AND footer signature?}
M -->|.str/.txt/.wak| Q[Pass]
N -->|Fail| R[Reject]
N -->|Pass| Q
O -->|Fail| S[Reject]
O -->|Pass| Q
P -->|Fail| T[Reject]
P -->|Pass| Q
Q --> U[Write file to disk]
U --> V[Send 100% progress]
Last reviewed commit: bc37447
Skyaero42
left a comment
There was a problem hiding this comment.
This looks good to me.
Nice security addition to the game.
| else if (stricmp(lastDot, ".ini") == 0) | ||
| { | ||
| // Check for null bytes to ensure text format | ||
| Int bytesToCheck = dataSize < 512 ? dataSize : 512; |
There was a problem hiding this comment.
I think std::min here actually
| DEBUG_LOG(("TGA file '%s' is too small to be valid (minimum 18 bytes).", filePath.str())); | ||
| return false; | ||
| } | ||
| if (memcmp(data + dataSize - 18, "TRUEVISION-XFILE.", 18) != 0) |
There was a problem hiding this 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.
| if (memcmp(data + dataSize - 18, "TRUEVISION-XFILE.", 18) != 0) | |
| 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.
Prompt To Fix With AI
This 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.There was a problem hiding this comment.
The offset is correct — you can verify in Core/Libraries/Source/WWVegas/WWLib/TARGA.h:134-142:
typedef struct _TGA2Footer {
long Extension; // 4 bytes (offset 0 from footer start)
long Developer; // 4 bytes (offset 4)
char Signature[16]; // 16 bytes (offset 8)
char RsvdChar; // 1 byte '.' (offset 24)
char BZST; // 1 byte '\0' (offset 25)
} TGA2Footer; // 26 bytes total
The Signature field starts at footer offset 8, which is EOF - 18 (26 - 8). Our memcmp(data +
dataSize - 18, "TRUEVISION-XFILE.", 18) checks exactly Signature + RsvdChar + BZST — the last 18
bytes. Starting at dataSize - 26 would read the Extension and Developer fields instead.
| { ".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 }, |
There was a problem hiding this comment.
Align columns in lookup table
Per project style, data-oriented lookup tables should have aligned columns for readability.
| { ".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 }, | |
| { ".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 }, |
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
This is a comment left during a code review.
Path: Core/GameEngine/Source/Common/System/FileSystem.cpp
Line: 484:489
Comment:
**Align columns in lookup table**
Per project style, data-oriented lookup tables should have aligned columns for readability.
```suggestion
{ ".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 },
```
**Context Used:** Rule from `dashboard` - Align columns in data-oriented blocks (like lookup tables, initialization lists, or structured data)... ([source](https://app.greptile.com/review/custom-context?memory=e61e83cf-a14f-4759-bed8-3bab008b25b5))
<sub>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!</sub>
How can I resolve this? If you propose a fix, please make it concise.
Implements file content validation during map transfer operations to prevent malformed or malicious files from being processed.
Implementation
Adds
FileSystem::hasValidTransferFileContent()which validates transferred file content in memory before writing to disk. If validation fails, the file is never written and the transfer is silently aborted.Validation methods:
.mapfiles - Validates magic bytesCkMp(Chunky Map header).inifiles - Checks for null bytes to ensure text format (rejects binary).tgafiles - Validates minimum size (18 bytes for valid TGA header).str,.txt,.wak- Size validation onlyMaximum file sizes:
.map: 5 MB.ini: 512 KB.str/.txt: 512 KB.tga: 2 MB.wak: 512 KB