Skip to content

Commit

Permalink
Verify pk3 integrity after completing the download
Browse files Browse the repository at this point in the history
Engine does not verify CRCs when loading pk3.
This check protects against bitrot (but not spoofing).

from 3d0b2006
  • Loading branch information
taysta committed May 11, 2024
1 parent 3beb186 commit cb32e03
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 1 deletion.
10 changes: 10 additions & 0 deletions codemp/client/cl_parse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,11 @@ void CL_ParseDownload ( msg_t *msg ) {
FS_FCloseFile( clc.download );
clc.download = 0;

int checksum;
if (FS_SV_VerifyZipFile(clc.downloadTempName, &checksum)) {
Com_Error(ERR_DROP, "Download Error: pk3 archive corrupted");
}

// rename the file
FS_SV_Rename ( clc.downloadTempName, clc.downloadName, qfalse );
}
Expand Down Expand Up @@ -741,6 +746,11 @@ HTTP download ended

void CL_EndHTTPDownload(dlHandle_t handle, qboolean success, const char *err_msg) {
if (success) {
int checksum;
if (FS_SV_VerifyZipFile(clc.downloadTempName, &checksum)) {
Com_Error(ERR_DROP, "Download Error: pk3 archive corrupted");
}

FS_SV_Rename(clc.downloadTempName, clc.downloadName, qfalse);
} else {
Com_Error(ERR_DROP, "Download Error: %s", err_msg);
Expand Down
107 changes: 107 additions & 0 deletions codemp/qcommon/files.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2545,6 +2545,113 @@ static pack_t *FS_LoadZipFile( const char *zipfile, const char *basename )
return pack;
}

/*
=================
FS_SV_VerifyZipFile
Verify zip data integrity with CRCs
Calculate checksum for zip file the same way as in pack->checksum
This is just a hash of CRCs from ZIP central directory
It does not check the actual content and zip metadata
=================
*/
qboolean FS_SV_VerifyZipFile( const char *zipfile, int *checksum )
{
const char *ospath;
unzFile uf;
int err;
unz_global_info gi;
unz_file_info file_info;
ZPOS64_T i;
int fs_numHeaderLongs;
int *fs_headerLongs = NULL;
int chksum;
char *read_buffer = NULL;
const int read_buffer_size = 16384; // UNZ_BUFSIZE

if ( !fs_searchpaths ) {
Com_Error( ERR_FATAL, "Filesystem call made without initialization" );
}

ospath = FS_BuildOSPath( fs_homepath->string, zipfile );

uf = unzOpen(ospath);
if (uf == NULL)
goto unzip_error;

if (unzGetGlobalInfo(uf, &gi))
goto unzip_error;

fs_numHeaderLongs = 0;
fs_headerLongs = (int *)Hunk_AllocateTempMemory(gi.number_entry * sizeof(int));
read_buffer = (char *)Hunk_AllocateTempMemory(read_buffer_size);

if (unzGoToFirstFile(uf))
goto unzip_error;

for (i = 0; i < gi.number_entry; i++)
{
if (unzGetCurrentFileInfo(uf, &file_info, NULL, 0, NULL, 0, NULL, 0))
goto unzip_error;

if (file_info.uncompressed_size > 0) {
fs_headerLongs[fs_numHeaderLongs++] = LittleLong(file_info.crc);
}

if (unzOpenCurrentFile(uf))
goto unzip_error;

// read whole file to make minizip calculate CRC
do {
err = unzReadCurrentFile(uf, read_buffer, read_buffer_size);

if (err < 0) {
unzCloseCurrentFile(uf);
goto unzip_error;
}
} while (err != UNZ_EOF);

// decompression may fail early due to bitrot
// unzCloseCurrentFile() does not verify CRC unless unzeof() returns 1
if (unzeof(uf) != 1) {
unzCloseCurrentFile(uf);
goto unzip_error;
}

// returns UNZ_CRCERROR if CRC does not match
if (unzCloseCurrentFile(uf))
goto unzip_error;

unzGoToNextFile(uf);
}

if (checksum) {
chksum = Com_BlockChecksum( fs_headerLongs, 4 * fs_numHeaderLongs );
chksum = LittleLong( chksum );
*checksum = chksum;
}

unzClose(uf);

Hunk_FreeTempMemory(read_buffer);
Hunk_FreeTempMemory(fs_headerLongs);

return qfalse;

unzip_error:
if (uf)
unzClose(uf);

if (read_buffer)
Hunk_FreeTempMemory(read_buffer);

if (fs_headerLongs)
Hunk_FreeTempMemory(fs_headerLongs);

return qtrue;
}

/*
=================
FS_FreePak
Expand Down
2 changes: 1 addition & 1 deletion codemp/qcommon/qcommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -790,7 +790,7 @@ void FS_Rename( const char *from, const char *to );

qboolean FS_WriteToTemporaryFile( const void *data, size_t dataLength, char **tempFileName );
const char *FS_MV_VerifyDownloadPath(const char *pk3file);

qboolean FS_SV_VerifyZipFile( const char *zipfile, int *checksum );

/*
==============================================================
Expand Down

0 comments on commit cb32e03

Please sign in to comment.