From 7e53f398e7b1380c1ba729d3e19f9ef68cf36057 Mon Sep 17 00:00:00 2001 From: Daniil Ryvkin Date: Sun, 19 Jul 2026 00:08:52 +0000 Subject: [PATCH 1/2] Fix unsafe strcpy/sprintf/vsprintf/strcat call sites and bounds-check buffer overflows --- client/cl_input.c | 6 ++-- client/cl_input_w3.c | 4 +-- client/cl_layout.c | 6 ++-- client/cl_view.c | 9 ++++-- common/cmd.c | 2 +- common/common.c | 2 +- common/mpq.c | 8 ++--- common/msg.c | 19 +++++++++++- games/starcraft-2/common/world_sc2.c | 2 +- games/starcraft-2/game/g_model.c | 6 ++-- games/warcraft-3/game/api/api_misc.h | 6 ++-- games/warcraft-3/game/api/api_sound.h | 2 +- games/warcraft-3/game/g_items.c | 2 +- games/warcraft-3/game/g_model.c | 4 +-- games/warcraft-3/game/g_monster.c | 2 +- games/warcraft-3/game/skills/s_build.c | 2 +- games/warcraft-3/jass/jlex.c | 30 ++++++++++++++----- games/warcraft-3/jass/jparser.c | 2 +- games/warcraft-3/renderer/r_game.c | 25 +++++++++++----- .../renderer/w3m/r_war3map_cliffs.c | 6 ++-- .../renderer/w3m/r_war3map_ground.c | 2 +- games/warcraft-3/sheet/sheet.c | 2 +- games/world-of-warcraft/game/g_model.c | 8 ++--- games/world-of-warcraft/renderer/r_game.c | 8 +++-- server/sv_game.c | 6 ++-- server/sv_init.c | 2 +- server/sv_lobby.c | 2 +- server/sv_main.c | 12 ++++---- server/sv_user.c | 2 +- tools/blp2jpg.c | 10 +++---- tools/isoextract.c | 11 +++---- tools/jass.c | 4 +-- 32 files changed, 134 insertions(+), 80 deletions(-) diff --git a/client/cl_input.c b/client/cl_input.c index 01685acb..f3ef0143 100644 --- a/client/cl_input.c +++ b/client/cl_input.c @@ -302,13 +302,13 @@ void IN_SelectUp(void) { num = mn; memcpy(selected, merged, sizeof(DWORD) * mn); } - strcpy(buffer, "select"); + strlcpy(buffer, "select", sizeof(buffer)); FOR_LOOP(i, num) { size_t used = strlen(buffer); snprintf(buffer + used, sizeof(buffer) - used, " %d", selected[i]); } MSG_WriteByte(&cls.netchan.message, clc_stringcmd); - SZ_Printf(&cls.netchan.message, buffer); + SZ_Printf(&cls.netchan.message, "%s", buffer); /* Store selected entities and request UI data (Phase 8.6) */ cl.selection.num_selected = num; @@ -320,7 +320,7 @@ void IN_SelectUp(void) { void CL_ForwardToServer_f(void) { extern LPCSTR current_command; MSG_WriteByte(&cls.netchan.message, clc_stringcmd); - SZ_Printf(&cls.netchan.message, current_command+4); + SZ_Printf(&cls.netchan.message, "%s", current_command+4); } void CL_InitInput(void) { diff --git a/client/cl_input_w3.c b/client/cl_input_w3.c index 950c8e81..22b8e941 100644 --- a/client/cl_input_w3.c +++ b/client/cl_input_w3.c @@ -48,13 +48,13 @@ static void CL_ApplySelection(DWORD const *ids, DWORD n) { char buffer[1024]; if (n == 0) return; if (n > MAX_SELECTED_ENTITIES) n = MAX_SELECTED_ENTITIES; - strcpy(buffer, "select"); + strlcpy(buffer, "select", sizeof(buffer)); FOR_LOOP(i, n) { size_t used = strlen(buffer); snprintf(buffer + used, sizeof(buffer) - used, " %d", ids[i]); } MSG_WriteByte(&cls.netchan.message, clc_stringcmd); - SZ_Printf(&cls.netchan.message, buffer); + SZ_Printf(&cls.netchan.message, "%s", buffer); cl.selection.num_selected = n; memcpy(cl.selection.entity_nums, ids, sizeof(DWORD) * n); CL_RequestUnitUI(n, cl.selection.entity_nums); diff --git a/client/cl_layout.c b/client/cl_layout.c index d0d5d810..5983fb18 100644 --- a/client/cl_layout.c +++ b/client/cl_layout.c @@ -107,16 +107,16 @@ LPCSTR SCR_GetStringValue(LPCUIFRAME frame) { static char text[1024] = { 0 }; if (frame->stat >= MAX_STATS) { if (cl.playerstate.texts[frame->stat - MAX_STATS]) { - strcpy(text, cl.playerstate.texts[frame->stat - MAX_STATS]); + strlcpy(text, cl.playerstate.texts[frame->stat - MAX_STATS], sizeof(text)); } else { memset(text, 0, sizeof(text)); } } else if (frame->stat == PLAYERSTATE_RESOURCE_FOOD_USED) { DWORD food_used = cl.playerstate.stats[PLAYERSTATE_RESOURCE_FOOD_USED]; DWORD food_made = cl.playerstate.stats[PLAYERSTATE_RESOURCE_FOOD_CAP]; - sprintf(text, "%d/%d", food_used, food_made); + snprintf(text, sizeof(text), "%d/%d", food_used, food_made); } else if (frame->stat > 0) { - sprintf(text, "%d", cl.playerstate.stats[frame->stat]); + snprintf(text, sizeof(text), "%d", cl.playerstate.stats[frame->stat]); } else if (frame->text) { return frame->text; } else { diff --git a/client/cl_view.c b/client/cl_view.c index 72e11f0d..60836989 100644 --- a/client/cl_view.c +++ b/client/cl_view.c @@ -426,8 +426,13 @@ void CL_PrepRefresh(void) { PATHSTR portrait = { 0 }; LPCSTR ext = strstr(filename, ".m"); if (ext) { - memcpy(portrait, filename, ext - filename); - sprintf(portrait + strlen(portrait), "_Portrait%s", ext); + size_t base_len = (size_t)(ext - filename); + if (base_len >= sizeof(portrait)) { + base_len = sizeof(portrait) - 1; + } + memcpy(portrait, filename, base_len); + portrait[base_len] = '\0'; + snprintf(portrait + base_len, sizeof(portrait) - base_len, "_Portrait%s", ext); } cl.models[i] = re.LoadModel(filename); if (!cl.models[i]) { diff --git a/common/cmd.c b/common/cmd.c index a669d624..b2966454 100644 --- a/common/cmd.c +++ b/common/cmd.c @@ -62,7 +62,7 @@ char *va(char *format, ...) { va_list argptr; static char string[1024]; va_start (argptr, format); - vsprintf (string, format,argptr); + vsnprintf (string, sizeof(string), format,argptr); va_end (argptr); return string; } diff --git a/common/common.c b/common/common.c index 07f67a31..cb87688a 100644 --- a/common/common.c +++ b/common/common.c @@ -1396,7 +1396,7 @@ void Com_Error(errorCode_t code, LPCSTR fmt, ...) { recursive = true; va_start(argptr,fmt); - vsprintf(msg,fmt,argptr); + vsnprintf(msg,sizeof(msg),fmt,argptr); va_end(argptr); switch (code) { diff --git a/common/mpq.c b/common/mpq.c index 97b56e2b..c1f4fe13 100644 --- a/common/mpq.c +++ b/common/mpq.c @@ -309,7 +309,7 @@ static void CacheBlockLookup(MPQ_ARCHIVE *mpq, const char *fileName, DWORD block free(entry); return; } - strcpy(entry->name, key); + memcpy(entry->name, key, strlen(key) + 1); entry->block_index = block_index; entry->next = mpq->lookup_cache[hash & (mpq->lookup_cache_size - 1)]; mpq->lookup_cache[hash & (mpq->lookup_cache_size - 1)] = entry; @@ -632,7 +632,7 @@ static BOOL WriterAddData(MPQ_ARCHIVE *mpq, const char *archivedName, const BYTE mpq->write_count--; return FALSE; } - strcpy(entry->name, path); + memcpy(entry->name, path, strlen(path) + 1); entry->offset = (DWORD)pos; entry->block_size = write_size; entry->file_size = size; @@ -1760,7 +1760,7 @@ static void PreloadListfileCache(MPQ_ARCHIVE *mpq) if (!entries[i].name) { break; } - strcpy(entries[i].name, line); + memcpy(entries[i].name, line, strlen(line) + 1); CanonicalizeMpqKey(entries[i].name, entries[i].name, strlen(entries[i].name) + 1); entries[i].hash1 = HashString(entries[i].name, MPQ_HASH_NAME_A); entries[i].hash2 = HashString(entries[i].name, MPQ_HASH_NAME_B); @@ -1989,7 +1989,7 @@ static BOOL AppendFindListEntry(MPQ_FIND *find, const char *name) return FALSE; } - strcpy(copy, name); + memcpy(copy, name, strlen(name) + 1); find->files = next; find->files[find->file_count++] = copy; return TRUE; diff --git a/common/msg.c b/common/msg.c index 77e100f1..0bd0f055 100644 --- a/common/msg.c +++ b/common/msg.c @@ -437,9 +437,26 @@ void MSG_ReadDeltaPlayerState(LPSIZEBUF msg, void SZ_Printf(LPSIZEBUF msg, LPCSTR fmt, ...) { va_list argptr; + int written; + + if (msg->cursize >= msg->maxsize) { + msg->overflowed = true; + return; + } + va_start(argptr, fmt); - msg->cursize += vsprintf((LPSTR)(msg->data + msg->cursize), fmt, argptr) + 1; + written = vsnprintf((LPSTR)(msg->data + msg->cursize), msg->maxsize - msg->cursize, fmt, argptr); va_end(argptr); + + if (written < 0 || (DWORD)written >= msg->maxsize - msg->cursize) { + fprintf(stderr, + "Write buffer overflow (msg): maxsize=%u cursize=%u\n", + (unsigned)msg->maxsize, + (unsigned)msg->cursize); + msg->overflowed = true; + return; + } + msg->cursize += written + 1; } void MSG_WriteEntityBits(LPSIZEBUF buf, DWORD bits, DWORD number) { diff --git a/games/starcraft-2/common/world_sc2.c b/games/starcraft-2/common/world_sc2.c index 0aa9c02d..7a20c4e9 100644 --- a/games/starcraft-2/common/world_sc2.c +++ b/games/starcraft-2/common/world_sc2.c @@ -23,7 +23,7 @@ bool CM_LoadMapFormat(LPCSTR mapFilename) { world.map->height = height + 1; world.map->center = map->origin; world.info.mapName = MemAlloc(strlen(map->map_name) + 1); - strcpy(world.info.mapName, map->map_name); + memcpy(world.info.mapName, map->map_name, strlen(map->map_name) + 1); world.info.playableArea.width = width; world.info.playableArea.height = height; world.info.players[0].used = true; diff --git a/games/starcraft-2/game/g_model.c b/games/starcraft-2/game/g_model.c index 4a3202d1..2ddded9f 100644 --- a/games/starcraft-2/game/g_model.c +++ b/games/starcraft-2/game/g_model.c @@ -158,10 +158,10 @@ static BYTE *ReadModelFile(LPCSTR filename, DWORD *out_size) { if (len == 0 || len >= sizeof(path)) return NULL; - strcpy(path, filename); + memcpy(path, filename, len + 1); ext = strstr(path, ".m3"); - if (ext && ext[3] == '\0') { - strcpy(ext, ".m3x"); + if (ext && ext[3] == '\0' && (size_t)(ext - path) + 5 <= sizeof(path)) { + memcpy(ext, ".m3x", 5); data = gi.ReadFile(path, out_size); } } diff --git a/games/warcraft-3/game/api/api_misc.h b/games/warcraft-3/game/api/api_misc.h index 8127d41a..6ffcad09 100644 --- a/games/warcraft-3/game/api/api_misc.h +++ b/games/warcraft-3/game/api/api_misc.h @@ -107,13 +107,13 @@ MATH_FUNC(S2R, atoi, string, number); DWORD I2S(LPJASS j) { LONG i = jass_checkinteger(j, 1); char buffer[64] = { 0 }; - sprintf(buffer, "%d", i); + snprintf(buffer, sizeof(buffer), "%d", i); return jass_pushstring(j, buffer); } DWORD R2S(LPJASS j) { FLOAT r = jass_checknumber(j, 1); char buffer[64] = { 0 }; - sprintf(buffer, "%f", r); + snprintf(buffer, sizeof(buffer), "%f", r); return jass_pushstring(j, buffer); } DWORD R2SW(LPJASS j) { @@ -769,7 +769,7 @@ DWORD DialogDisplay(LPJASS j) { DWORD InitGameCache(LPJASS j) { API_ALLOC(ggamecache_t, gamecache); LPCSTR campaignFile = jass_checkstring(j, 1); - strcpy(gamecache->campaign, campaignFile); + strlcpy(gamecache->campaign, campaignFile, sizeof(gamecache->campaign)); return 1; } DWORD SaveGameCache(LPJASS j) { diff --git a/games/warcraft-3/game/api/api_sound.h b/games/warcraft-3/game/api/api_sound.h index 3af1ab0b..60983415 100644 --- a/games/warcraft-3/game/api/api_sound.h +++ b/games/warcraft-3/game/api/api_sound.h @@ -8,7 +8,7 @@ DWORD CreateSound(LPJASS j) { LPCSTR eaxSetting = jass_checkstring(j, 7); (void)eaxSetting; API_ALLOC(gsound_t, sound); - strcpy(sound->fileName, fileName); + strlcpy(sound->fileName, fileName, sizeof(sound->fileName)); sound->looping = looping; sound->is3D = is3D; sound->stopwhenoutofrange = stopwhenoutofrange; diff --git a/games/warcraft-3/game/g_items.c b/games/warcraft-3/game/g_items.c index 560653ab..c5789ba9 100644 --- a/games/warcraft-3/game/g_items.c +++ b/games/warcraft-3/game/g_items.c @@ -19,7 +19,7 @@ static FLOAT G_MiscVectorValue(LPCSTR name, DWORD index) { void SP_SpawnItem(LPEDICT self) { PATHSTR model_filename; - strcpy(model_filename, ITEM_FILE(self->class_id)); + strlcpy(model_filename, ITEM_FILE(self->class_id), sizeof(model_filename)); self->s.model = G_RegisterModel(model_filename); self->s.shadow = G_LoadShadowTexture(FS_FindSheetCell(game.config.misc, "Misc", "ItemShadowFile"), false); self->s.shadow_rect = ShadowPackRect( diff --git a/games/warcraft-3/game/g_model.c b/games/warcraft-3/game/g_model.c index 6452eba7..306d0255 100644 --- a/games/warcraft-3/game/g_model.c +++ b/games/warcraft-3/game/g_model.c @@ -19,7 +19,7 @@ static void ConvertMDLXAnimationName(LPANIMATION seq) { char buffer[80]; char *last_char = buffer; memset(buffer, 0, sizeof(buffer)); - strcpy(buffer, seq->name); + strlcpy(buffer, seq->name, sizeof(buffer)); for (char *ch = buffer; *ch; ch++) { if (isdigit(*ch) || *ch == '-') { while (*(++last_char)) { @@ -214,7 +214,7 @@ static BYTE *ReadModelFile(LPCSTR filename, DWORD *out_size) { size_t len = strlen(filename); if (len == 0 || len >= sizeof(path)) return NULL; - strcpy(path, filename); + memcpy(path, filename, len + 1); path[len - 1] = 'x'; data = gi.ReadFile(path, out_size); } diff --git a/games/warcraft-3/game/g_monster.c b/games/warcraft-3/game/g_monster.c index 2492d6d2..a691545e 100644 --- a/games/warcraft-3/game/g_monster.c +++ b/games/warcraft-3/game/g_monster.c @@ -398,7 +398,7 @@ void SP_SpawnUnit(LPEDICT self) { PATHSTR model_filename; LPCSTR uber_splat = UNIT_UBER_SPLAT(self->class_id); LPCSTR path_tex = UNIT_PATH_TEX(self->class_id); - sprintf(model_filename, "%s.mdx", UNIT_MODEL(self->class_id)); + snprintf(model_filename, sizeof(model_filename), "%s.mdx", UNIT_MODEL(self->class_id)); self->s.model = G_RegisterModel(model_filename); self->s.splat = M_LoadUberSplat(uber_splat); if (UNIT_IS_BUILDING(self->class_id)) { diff --git a/games/warcraft-3/game/skills/s_build.c b/games/warcraft-3/game/skills/s_build.c index 5509cb57..6d4ac001 100644 --- a/games/warcraft-3/game/skills/s_build.c +++ b/games/warcraft-3/game/skills/s_build.c @@ -36,7 +36,7 @@ static void FillUnitData(LPENTITYSTATE ent, DWORD unit_id, LPCSTR anim) { LPCSTR model_filename = UNIT_MODEL(unit_id); if (!model_filename) return; - sprintf(buffer, "%s.mdx", model_filename); + snprintf(buffer, sizeof(buffer), "%s.mdx", model_filename); memset(ent, 0, sizeof(entityState_t)); ent->class_id = unit_id; ent->model = G_RegisterModel(buffer); diff --git a/games/warcraft-3/jass/jlex.c b/games/warcraft-3/jass/jlex.c index 4ec570eb..95ef0e8d 100644 --- a/games/warcraft-3/jass/jlex.c +++ b/games/warcraft-3/jass/jlex.c @@ -62,16 +62,27 @@ LPCSTR parse_segment(LPPARSER p) { if (*p->buffer == '\"') { ++start; p->buffer = strchr(start, '\"'); - memcpy(segment, start, p->buffer - start); - segment[p->buffer - start] = '\0'; + if (!p->buffer) { + p->buffer = start + strlen(start); + } + size_t seglen = (size_t)(p->buffer - start); + if (seglen >= MAX_SEGMENT_SIZE) { + seglen = MAX_SEGMENT_SIZE - 1; + } + memcpy(segment, start, seglen); + segment[seglen] = '\0'; p->buffer = strchr(p->buffer, ','); } else { p->buffer = strchr(p->buffer, ','); if (p->buffer) { - memcpy(segment, start, p->buffer - start); - segment[p->buffer - start] = '\0'; // Null-terminate the segment + size_t seglen = (size_t)(p->buffer - start); + if (seglen >= MAX_SEGMENT_SIZE) { + seglen = MAX_SEGMENT_SIZE - 1; + } + memcpy(segment, start, seglen); + segment[seglen] = '\0'; // Null-terminate the segment } else { - strcpy(segment, start); + strlcpy(segment, start, MAX_SEGMENT_SIZE); p->buffer = start + strlen(start); return segment; } @@ -88,15 +99,20 @@ LPCSTR parse_segment2(LPPARSER p) { while (isspace(*p->buffer)) ++p->buffer; DWORD num_quotes = 0; - for (LPSTR out = segment; *p->buffer; ++p->buffer, ++out) { + LPSTR out = segment; + LPSTR const out_end = segment + MAX_SEGMENT_SIZE - 1; + for (; *p->buffer; ++p->buffer) { if (*p->buffer == ',' && (num_quotes & 1) == 0) { ++p->buffer; break; } if (*p->buffer == '"') ++num_quotes; - *out = *p->buffer; + if (out < out_end) { + *out++ = *p->buffer; + } } + *out = '\0'; return segment; } diff --git a/games/warcraft-3/jass/jparser.c b/games/warcraft-3/jass/jparser.c index 22d0ecd7..beb7cd78 100644 --- a/games/warcraft-3/jass/jparser.c +++ b/games/warcraft-3/jass/jparser.c @@ -173,7 +173,7 @@ LPTOKEN alloc_ident_token(LPPARSER p, TOKENTYPE tt) { LPTOKEN parse_operator_token(LPPARSER p) { UINAME op = { 0 }; - strcpy(op, parse_token(p)); + strlcpy(op, parse_token(p), sizeof(op)); if (eat_token(p, "=")) { op[1] = '='; } diff --git a/games/warcraft-3/renderer/r_game.c b/games/warcraft-3/renderer/r_game.c index 0396dcc1..af102438 100644 --- a/games/warcraft-3/renderer/r_game.c +++ b/games/warcraft-3/renderer/r_game.c @@ -59,8 +59,8 @@ void R_GameLoadAssets(void) { } FOR_LOOP(team, MAX_TEAMS) { PATHSTR glowFilename, colorFilename; - sprintf(glowFilename, "ReplaceableTextures\\TeamGlow\\TeamGlow%02d.blp", team); - sprintf(colorFilename, "ReplaceableTextures\\TeamColor\\TeamColor%02d.blp", team); + snprintf(glowFilename, sizeof(glowFilename), "ReplaceableTextures\\TeamGlow\\TeamGlow%02d.blp", team); + snprintf(colorFilename, sizeof(colorFilename), "ReplaceableTextures\\TeamColor\\TeamColor%02d.blp", team); tr.texture[TEX_TEAM_GLOW + team] = R_LoadTexture(glowFilename); tr.texture[TEX_TEAM_COLOR + team] = R_LoadTexture(colorFilename); } @@ -125,8 +125,11 @@ LPMODEL R_GameLoadModel(LPCSTR modelFilename) { PATHSTR tempFileName = { 0 }; size_t stemLen = strlen(modelFilename) - 4; /* ".mdl" = 4 chars */ - strncpy(tempFileName, modelFilename, stemLen); - strcpy(tempFileName + stemLen, ".mdx"); + if (stemLen > sizeof(tempFileName) - 5) { + stemLen = sizeof(tempFileName) - 5; + } + memcpy(tempFileName, modelFilename, stemLen); + memcpy(tempFileName + stemLen, ".mdx", 5); fileSize = ri.FS_ReadFile(tempFileName, &buffer); } if (fileSize < 0) { @@ -140,8 +143,11 @@ LPMODEL R_GameLoadModel(LPCSTR modelFilename) { end--; } stemLen = (size_t)(end - modelFilename); - strncpy(tempFileName, modelFilename, stemLen); - strcpy(tempFileName + stemLen, ".mdx"); + if (stemLen > sizeof(tempFileName) - 5) { + stemLen = sizeof(tempFileName) - 5; + } + memcpy(tempFileName, modelFilename, stemLen); + memcpy(tempFileName + stemLen, ".mdx", 5); fileSize = ri.FS_ReadFile(tempFileName, &buffer); } } @@ -157,8 +163,11 @@ LPMODEL R_GameLoadModel(LPCSTR modelFilename) { PATHSTR tempFileName = { 0 }; size_t stemLen = strlen(modelFilename) - 4; - strncpy(tempFileName, modelFilename, stemLen); - strcpy(tempFileName + stemLen, ".mdx"); + if (stemLen > sizeof(tempFileName) - 5) { + stemLen = sizeof(tempFileName) - 5; + } + memcpy(tempFileName, modelFilename, stemLen); + memcpy(tempFileName + stemLen, ".mdx", 5); ri.FS_FreeFile(buffer); return R_LoadModel(tempFileName); } else { diff --git a/games/warcraft-3/renderer/w3m/r_war3map_cliffs.c b/games/warcraft-3/renderer/w3m/r_war3map_cliffs.c index b6d4a36b..bf09d19f 100644 --- a/games/warcraft-3/renderer/w3m/r_war3map_cliffs.c +++ b/games/warcraft-3/renderer/w3m/r_war3map_cliffs.c @@ -135,7 +135,7 @@ static LPCMODEL R_LoadCliffModel(cliffData_t const *data, char const *ccfg, bool } struct tCliff *cliff = ri.MemAlloc(sizeof(struct tCliff)); cliff->cliffid = cliffid; - sprintf(zBuffer, "Doodads\\Terrain\\%s\\%s%s0.mdx", dir, dir, ccfg); + snprintf(zBuffer, sizeof(zBuffer), "Doodads\\Terrain\\%s\\%s%s0.mdx", dir, dir, ccfg); cliff->model = R_LoadModel(zBuffer); ADD_TO_LIST(cliff, g_cliffs); return cliff->model; @@ -154,13 +154,13 @@ static LPCTEXTURE R_LoadCliffTexture(DWORD cliffID, char tileset, cliffData_t co entry->cliffid = cliffID; entry->tileset = tileset; - sprintf(buffer, "%s\\%c_%s.blp", data->texDir, tileset, data->texFile); + snprintf(buffer, sizeof(buffer), "%s\\%c_%s.blp", data->texDir, tileset, data->texFile); void *testbuf = NULL; if (ri.FS_ReadFile(buffer, &testbuf) >= 0) { ri.FS_FreeFile(testbuf); entry->texture = R_LoadTexture(buffer); } else { - sprintf(buffer, "%s\\%s.blp", data->texDir, data->texFile); + snprintf(buffer, sizeof(buffer), "%s\\%s.blp", data->texDir, data->texFile); entry->texture = R_LoadTexture(buffer); } diff --git a/games/warcraft-3/renderer/w3m/r_war3map_ground.c b/games/warcraft-3/renderer/w3m/r_war3map_ground.c index 42ee9107..525e7296 100644 --- a/games/warcraft-3/renderer/w3m/r_war3map_ground.c +++ b/games/warcraft-3/renderer/w3m/r_war3map_ground.c @@ -182,7 +182,7 @@ LPMAPLAYER R_BuildMapSegmentLayer(LPCWAR3MAP map, DWORD sx, DWORD sy, DWORD laye LPCSTR dir = ri.FindSheetCell(tr.sheet[SHEET_TERRAIN], groundID, "dir"); LPCSTR file = ri.FindSheetCell(tr.sheet[SHEET_TERRAIN], groundID, "file"); if (file && dir) { - sprintf(zBuffer, "%s\\%s.blp", dir, file); + snprintf(zBuffer, sizeof(zBuffer), "%s\\%s.blp", dir, file); g_groundTextures[layer] = R_LoadTexture(zBuffer); } else { return NULL; diff --git a/games/warcraft-3/sheet/sheet.c b/games/warcraft-3/sheet/sheet.c index e4c1224a..e6633267 100644 --- a/games/warcraft-3/sheet/sheet.c +++ b/games/warcraft-3/sheet/sheet.c @@ -115,7 +115,7 @@ static void SheetCacheStore(LPCSTR fileName, sheetRow_t *rows, sheetRow_t *tail) free(entry); return; } - strcpy(entry->name, key); + memcpy(entry->name, key, strlen(key) + 1); entry->rows = rows; entry->tail = tail ? tail : rows; entry->next = sheet_cache; diff --git a/games/world-of-warcraft/game/g_model.c b/games/world-of-warcraft/game/g_model.c index 7463d1e4..8a1109e7 100644 --- a/games/world-of-warcraft/game/g_model.c +++ b/games/world-of-warcraft/game/g_model.c @@ -20,7 +20,7 @@ static void ConvertMDLXAnimationName(LPANIMATION seq) { char buffer[80]; char *last_char = buffer; memset(buffer, 0, sizeof(buffer)); - strcpy(buffer, seq->name); + strlcpy(buffer, seq->name, sizeof(buffer)); for (char *ch = buffer; *ch; ch++) { if (isdigit(*ch) || *ch == '-') { while (*(++last_char)) { @@ -606,14 +606,14 @@ static BYTE *ReadModelFile(LPCSTR filename, DWORD *out_size) { size_t len = strlen(filename); if (len == 0 || len >= sizeof(path)) return NULL; - strcpy(path, filename); + memcpy(path, filename, len + 1); path[len - 1] = 'x'; data = gi.ReadFile(path, out_size); if (!data && strstr(filename, ".mdx")) { LPSTR ext; - strcpy(path, filename); + memcpy(path, filename, len + 1); ext = strstr(path, ".mdx"); - strcpy(ext, ".m2"); + memcpy(ext, ".m2", 4); data = gi.ReadFile(path, out_size); } } diff --git a/games/world-of-warcraft/renderer/r_game.c b/games/world-of-warcraft/renderer/r_game.c index 78dfd2a0..ca49240d 100644 --- a/games/world-of-warcraft/renderer/r_game.c +++ b/games/world-of-warcraft/renderer/r_game.c @@ -92,9 +92,13 @@ LPMODEL R_GameLoadModel(LPCSTR modelFilename) { if ((fileSize < 0 || !buffer) && R_GamePathHasExtension(modelFilename, ".mdx")) { PATHSTR tempFileName = { 0 }; LPSTR ext = strstr(modelFilename, ".mdx"); + size_t stemLen = (size_t)(ext - modelFilename); - strncpy(tempFileName, modelFilename, ext - modelFilename); - strcpy(tempFileName + strlen(tempFileName), ".m2"); + if (stemLen > sizeof(tempFileName) - 4) { + stemLen = sizeof(tempFileName) - 4; + } + memcpy(tempFileName, modelFilename, stemLen); + memcpy(tempFileName + stemLen, ".m2", 4); fileSize = ri.FS_ReadFile(tempFileName, &buffer); if (fileSize >= 0 && buffer) { snprintf(load_name, sizeof(load_name), "%s", tempFileName); diff --git a/server/sv_game.c b/server/sv_game.c index 89128164..d1a9e3af 100644 --- a/server/sv_game.c +++ b/server/sv_game.c @@ -96,6 +96,9 @@ void PF_Confignstring(DWORD index, LPCSTR value, DWORD len) { return; } + if (len > sizeof(sv.configstrings[index]) - 1) { + len = sizeof(sv.configstrings[index]) - 1; + } memset(sv.configstrings[index], 0, sizeof(sv.configstrings[index])); memcpy(sv.configstrings[index], value, len); @@ -116,7 +119,6 @@ void PF_Configstring(DWORD index, LPCSTR value) { } PF_Confignstring(index, value, (DWORD)(strlen(value) + 1)); - strcpy(sv.configstrings[index], value); } LPCSTR PF_GetConfigstring(DWORD index) { @@ -158,7 +160,7 @@ void PF_error(LPCSTR fmt, ...) { char msg[1024]; va_list argptr; va_start(argptr,fmt); - vsprintf(msg, fmt, argptr); + vsnprintf(msg, sizeof(msg), fmt, argptr); va_end(argptr); fprintf(stderr, "Game Error: %s\n", msg); } diff --git a/server/sv_init.c b/server/sv_init.c index b96ca0f1..5f314c41 100644 --- a/server/sv_init.c +++ b/server/sv_init.c @@ -187,7 +187,7 @@ void SV_Map(LPCSTR mapFilename) { SV_InitGame(); memset(&sv, 0, sizeof(struct server)); sv.state = ss_loading; - strcpy(sv.configstrings[CS_WORLD], mapFilename); + strlcpy(sv.configstrings[CS_WORLD], mapFilename, sizeof(sv.configstrings[CS_WORLD])); SZ_Init(&sv.multicast, sv.multicast_buf, MAX_MSGLEN); if (!ge->LoadMap(mapFilename)) { fprintf(stderr, "SV_Map: map load failed\n"); diff --git a/server/sv_lobby.c b/server/sv_lobby.c index 488c99ba..8fcd7f11 100644 --- a/server/sv_lobby.c +++ b/server/sv_lobby.c @@ -449,7 +449,7 @@ void SV_ApplyLobbySettings(LPMAPINFO info) { if (slot->name[0]) { SAFE_DELETE(player->playerName, MemFree); player->playerName = MemAlloc(strlen(slot->name) + 1); - strcpy(player->playerName, slot->name); + memcpy(player->playerName, slot->name, strlen(slot->name) + 1); } if (type == kPlayerTypeNone) { SV_LobbyClearPlayerTeams(info, slot->map_player); diff --git a/server/sv_main.c b/server/sv_main.c index 65534730..ee14fbea 100644 --- a/server/sv_main.c +++ b/server/sv_main.c @@ -120,7 +120,7 @@ int SV_ModelIndex(LPCSTR name) { LPCSTR slash; LPSTR ext; - strcpy(model_filename, name); + strlcpy(model_filename, name, sizeof(model_filename)); base = model_filename; slash = strrchr(base, '\\'); if (slash) { @@ -132,10 +132,12 @@ int SV_ModelIndex(LPCSTR name) { } ext = strrchr((LPSTR)base, '.'); if (!ext) { - ext = model_filename + strlen(model_filename); - strcpy(ext, ".mdx"); + size_t len = strlen(model_filename); + if (len + 5 <= sizeof(model_filename)) { + memcpy(model_filename + len, ".mdx", 5); + } } else if (!strcasecmp(ext, ".mdl")) { - strcpy(ext, ".mdx"); + memcpy(ext, ".mdx", 5); } int modelindex = SV_FindIndex(model_filename, CS_MODELS, MAX_MODELS, true); return modelindex; @@ -151,7 +153,7 @@ int SV_ImageIndex(LPCSTR name) { int SV_FontIndex(LPCSTR name, DWORD fontSize) { PATHSTR fontspec; - sprintf(fontspec, "%s,%d", name, fontSize); + snprintf(fontspec, sizeof(fontspec), "%s,%d", name, fontSize); return SV_FindIndex(fontspec, CS_FONTS, MAX_FONTSTYLES, true); } diff --git a/server/sv_user.c b/server/sv_user.c index bc03c669..f7785d9e 100644 --- a/server/sv_user.c +++ b/server/sv_user.c @@ -182,7 +182,7 @@ void SV_ExecuteUserCommand(LPSIZEBUF msg, LPCLIENT client) { p.tok = p.token; p.str = command; for (LPCSTR tok = ParserGetToken(&p); tok && argc < MAX_CMDARGS; tok = ParserGetToken(&p)) { - strcpy(args[argc], tok); + strlcpy(args[argc], tok, sizeof(args[argc])); argv[argc] = args[argc]; argc++; } diff --git a/tools/blp2jpg.c b/tools/blp2jpg.c index d2a9f23a..d04c7675 100644 --- a/tools/blp2jpg.c +++ b/tools/blp2jpg.c @@ -416,12 +416,10 @@ static char *output_path_for(char const *input, char const *outdir, bool from_mp path = from_mpq || outdir ? Tool_PathJoin(outdir ? outdir : "", input) : Tool_XStrdup(input); Tool_NormalizeSlashes(path, '/'); dot = strrchr(path, '.'); - if (dot) { - strcpy(dot, ".jpg"); - } else { - size_t len = strlen(path); - path = Tool_XRealloc(path, len + 5); - strcpy(path + len, ".jpg"); + { + size_t base_len = dot ? (size_t)(dot - path) : strlen(path); + path = Tool_XRealloc(path, base_len + 5); + memcpy(path + base_len, ".jpg", 5); } parent = Tool_PathParent(path); diff --git a/tools/isoextract.c b/tools/isoextract.c index 495f562b..f98ab36d 100644 --- a/tools/isoextract.c +++ b/tools/isoextract.c @@ -170,15 +170,16 @@ static void strip_iso_version(char *name) { } } -static void sanitize_name(char *name) { +static void sanitize_name(char *name, size_t name_size) { for (char *p = name; *p; p++) { unsigned char c = (unsigned char)*p; if (c < 32 || *p == '/' || *p == '\\' || *p == ':') { *p = '_'; } } - if (!name[0]) { - strcpy(name, "_"); + if (!name[0] && name_size >= 2) { + name[0] = '_'; + name[1] = 0; } } @@ -201,7 +202,7 @@ static bool decode_name(unsigned char const *in, unsigned len, bool joliet, char } strip_iso_version(out); - sanitize_name(out); + sanitize_name(out, out_size); return true; } @@ -330,7 +331,7 @@ static bool mkdir_p(char const *path) { fprintf(stderr, "isoextract: path too long: %s\n", path); return false; } - strcpy(tmp, path); + strlcpy(tmp, path, sizeof(tmp)); while (len > 1 && (tmp[len - 1] == '/' || tmp[len - 1] == '\\')) { tmp[--len] = 0; diff --git a/tools/jass.c b/tools/jass.c index 75edf9c8..f6464005 100644 --- a/tools/jass.c +++ b/tools/jass.c @@ -178,8 +178,8 @@ static int do_repl(LPJASS j) { in_block = 0; continue; } - strcat(block, line); - strcat(block, "\n"); + strlcat(block, line, sizeof(block)); + strlcat(block, "\n", sizeof(block)); } return 0; From acacfd9b4d27895616a796bae43d130801c8bf5b Mon Sep 17 00:00:00 2001 From: Daniil Ryvkin Date: Sun, 19 Jul 2026 00:18:04 +0000 Subject: [PATCH 2/2] Rename UI macros to avoid MAX_IMAGES collision, drop dead hasAlpha var, guard apple-only JPEG compositing code --- games/warcraft-3/ui/ui_fdf.c | 30 +++++++++++++++--------------- renderer/r_dds.c | 1 - tools/blp2jpg.c | 2 ++ 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/games/warcraft-3/ui/ui_fdf.c b/games/warcraft-3/ui/ui_fdf.c index 8c9f889e..9da3abd7 100644 --- a/games/warcraft-3/ui/ui_fdf.c +++ b/games/warcraft-3/ui/ui_fdf.c @@ -14,19 +14,19 @@ #endif #include "ui_local.h" -#define MAX_IMAGES 1024 -#define MAX_MODELS 256 +#define UI_MAX_TEXTURES 1024 +#define UI_MAX_MODELS 256 #define BZ_HOST_HIDDEN __attribute__((visibility("hidden"))) /* ---- Texture/model cache (UI-module specific) ----------------------------- */ -static LPCTEXTURE ui_textures[MAX_IMAGES] = { 0 }; -static PATHSTR ui_texture_names[MAX_IMAGES] = { 0 }; -static PATHSTR ui_texture_keys[MAX_IMAGES] = { 0 }; -static BOOL ui_texture_decorated[MAX_IMAGES] = { 0 }; -static LPCMODEL ui_models[MAX_MODELS] = { 0 }; -static PATHSTR ui_model_names[MAX_MODELS] = { 0 }; +static LPCTEXTURE ui_textures[UI_MAX_TEXTURES] = { 0 }; +static PATHSTR ui_texture_names[UI_MAX_TEXTURES] = { 0 }; +static PATHSTR ui_texture_keys[UI_MAX_TEXTURES] = { 0 }; +static BOOL ui_texture_decorated[UI_MAX_TEXTURES] = { 0 }; +static LPCMODEL ui_models[UI_MAX_MODELS] = { 0 }; +static PATHSTR ui_model_names[UI_MAX_MODELS] = { 0 }; BZ_HOST_HIDDEN void UI_ClearTextures(void) { memset(ui_textures, 0, sizeof(ui_textures)); @@ -63,7 +63,7 @@ BZ_HOST_HIDDEN DWORD UI_LoadTexture(LPCSTR file, BOOL decorate) { resolved = decorate ? Theme_String(file, "Default") : file; resolved = EnsureExtension(resolved, ".blp"); - FOR_LOOP(i, MAX_IMAGES) { + FOR_LOOP(i, UI_MAX_TEXTURES) { if (!ui_texture_names[i][0]) continue; if (decorate) { if (ui_texture_decorated[i] && !strcmp(ui_texture_keys[i], file)) @@ -74,7 +74,7 @@ BZ_HOST_HIDDEN DWORD UI_LoadTexture(LPCSTR file, BOOL decorate) { } index = 0; - for (DWORD i = 1; i < MAX_IMAGES; i++) { + for (DWORD i = 1; i < UI_MAX_TEXTURES; i++) { if (!ui_texture_names[i][0]) { index = i; break; } } if (!index || !uiimport.GetRenderer) return 0; @@ -89,7 +89,7 @@ BZ_HOST_HIDDEN DWORD UI_LoadTexture(LPCSTR file, BOOL decorate) { } LPCSTR UI_TextureName(DWORD index) { - if (!index || index >= MAX_IMAGES) return NULL; + if (!index || index >= UI_MAX_TEXTURES) return NULL; return ui_texture_names[index][0] ? ui_texture_names[index] : NULL; } @@ -97,7 +97,7 @@ LPCTEXTURE UI_GetTexture(DWORD index) { LPRENDERER renderer; LPCSTR resolved; - if (!index || index >= MAX_IMAGES) return NULL; + if (!index || index >= UI_MAX_TEXTURES) return NULL; if (ui_texture_decorated[index] && ui_texture_keys[index][0]) { resolved = EnsureExtension(Theme_String(ui_texture_keys[index], "Default"), ".blp"); if (strcmp(ui_texture_names[index], resolved)) { @@ -114,7 +114,7 @@ LPCTEXTURE UI_GetTexture(DWORD index) { } LPCMODEL UI_GetModel(DWORD index) { - if (!index || index >= MAX_MODELS) return NULL; + if (!index || index >= UI_MAX_MODELS) return NULL; return ui_models[index]; } @@ -126,12 +126,12 @@ BZ_HOST_HIDDEN DWORD UI_LoadModel(LPCSTR file, BOOL decorate) { if (!model || !*model) return 0; model = decorate ? Theme_String(model, "Default") : model; - FOR_LOOP(i, MAX_MODELS) { + FOR_LOOP(i, UI_MAX_MODELS) { if (ui_model_names[i][0] && !strcmp(ui_model_names[i], model)) return i; } - for (DWORD i = 1; i < MAX_MODELS; i++) { + for (DWORD i = 1; i < UI_MAX_MODELS; i++) { if (!ui_model_names[i][0]) { modelIndex = i; break; } } if (!modelIndex || !uiimport.GetRenderer) return 0; diff --git a/renderer/r_dds.c b/renderer/r_dds.c index e6710a5c..11de65dc 100644 --- a/renderer/r_dds.c +++ b/renderer/r_dds.c @@ -44,7 +44,6 @@ LPTEXTURE R_LoadTextureDDS(HANDLE data, DWORD filesize) { DWORD flags, fourcc, rgbBitCount, rMask, gMask, bMask, aMask; DDS_ParsePixelFormat(buf, &flags, &fourcc, &rgbBitCount, &rMask, &gMask, &bMask, &aMask); - BOOL const hasAlpha = (flags & 0x1) != 0; BOOL const isFourCC = (flags & 0x4) != 0; BOOL const isRGB = (flags & 0x40) != 0; diff --git a/tools/blp2jpg.c b/tools/blp2jpg.c index d04c7675..59f31100 100644 --- a/tools/blp2jpg.c +++ b/tools/blp2jpg.c @@ -432,6 +432,7 @@ static char *output_path_for(char const *input, char const *outdir, bool from_mp return path; } +#ifdef __APPLE__ static rgba8_t checker_color(uint32_t x, uint32_t y) { uint8_t v = (((x / 8) + (y / 8)) & 1) ? 72 : 32; return (rgba8_t) { v, v, v, 255 }; @@ -462,6 +463,7 @@ static rgba8_t *composite_and_scale(image_t const *image, int scale, size_t *out *out_size = sizeof(*out) * out_w * out_h; return out; } +#endif static bool write_jpeg(char const *path, image_t const *image, int scale) { #ifdef __APPLE__