Skip to content

Commit 2be4278

Browse files
JoeMattclaude
andcommitted
perf: address Copilot review on test_benchmark --load-state
Three concerns flagged by automated review on PR #128: * Validate fseek/ftell/malloc/fread when reading the state file (negative size, NULL malloc, short read) instead of trusting them. * Bounds-check RASTATE chunk_size against the buffer end before treating any bytes past the chunk header as payload, so a truncated/corrupt container can't push retro_unserialize past the allocation. * Route every state-load failure through a single cleanup label that closes the FILE, frees the buffer, and tears down retro_load_game / retro_init / dlopen before returning, matching the cleanup in the normal exit path. No functional change for valid state files. Smoke-tested with make benchmark (no --load-state) -- still reports FPS as before. Co-Authored-By: Claude Opus 4.7 <[email protected]>
1 parent 79a65d0 commit 2be4278

1 file changed

Lines changed: 91 additions & 48 deletions

File tree

test/tools/test_benchmark.c

Lines changed: 91 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -372,66 +372,109 @@ int main(int argc, char **argv)
372372
* RASTATE format. */
373373
if (state_load_path)
374374
{
375-
FILE *stf = fopen(state_load_path, "rb");
375+
FILE *stf = NULL;
376+
long st_total = 0;
377+
uint8_t *st_buf = NULL;
378+
const uint8_t *payload = NULL;
379+
size_t payload_size = 0;
380+
size_t expected;
381+
const char *state_err = NULL;
382+
383+
stf = fopen(state_load_path, "rb");
376384
if (!stf)
377385
{
378-
fprintf(stderr, "ERROR: cannot open state file: %s\n", state_load_path);
379-
return 1;
386+
state_err = "cannot open state file";
387+
goto state_fail;
380388
}
389+
if (fseek(stf, 0, SEEK_END) != 0)
381390
{
382-
long st_total;
383-
uint8_t *st_buf;
384-
const uint8_t *payload;
385-
size_t payload_size;
386-
size_t expected;
387-
fseek(stf, 0, SEEK_END);
388-
st_total = ftell(stf);
389-
fseek(stf, 0, SEEK_SET);
390-
st_buf = (uint8_t *)malloc(st_total);
391-
if (fread(st_buf, 1, st_total, stf) != (size_t)st_total)
392-
{
393-
fprintf(stderr, "ERROR: short read on state file\n");
394-
free(st_buf); fclose(stf); return 1;
395-
}
396-
fclose(stf);
397-
payload = st_buf;
398-
payload_size = (size_t)st_total;
399-
/* RASTATE container? "RASTATE" + 1 version byte, then chunks. */
400-
if (st_total >= 16 && memcmp(st_buf, "RASTATE", 7) == 0)
391+
state_err = "fseek to end failed";
392+
goto state_fail;
393+
}
394+
st_total = ftell(stf);
395+
if (st_total <= 0)
396+
{
397+
state_err = "ftell failed or empty state file";
398+
goto state_fail;
399+
}
400+
if (fseek(stf, 0, SEEK_SET) != 0)
401+
{
402+
state_err = "fseek to start failed";
403+
goto state_fail;
404+
}
405+
st_buf = (uint8_t *)malloc((size_t)st_total);
406+
if (!st_buf)
407+
{
408+
state_err = "malloc failed for state buffer";
409+
goto state_fail;
410+
}
411+
if (fread(st_buf, 1, (size_t)st_total, stf) != (size_t)st_total)
412+
{
413+
state_err = "short read on state file";
414+
goto state_fail;
415+
}
416+
fclose(stf);
417+
stf = NULL;
418+
payload = st_buf;
419+
payload_size = (size_t)st_total;
420+
/* RASTATE container? "RASTATE" + 1 version byte, then chunks. */
421+
if (st_total >= 16 && memcmp(st_buf, "RASTATE", 7) == 0)
422+
{
423+
const uint8_t *p = st_buf + 8; /* past magic+version */
424+
const uint8_t *end = st_buf + st_total;
425+
int found = 0;
426+
/* Each chunk: 4-byte type + 4-byte LE size + payload. */
427+
while (p + 8 <= end)
401428
{
402-
const uint8_t *p = st_buf + 8; /* past magic+version */
403-
const uint8_t *end = st_buf + st_total;
404-
int found = 0;
405-
while (p + 8 <= end)
429+
uint32_t chunk_size = (uint32_t)p[4] | ((uint32_t)p[5] << 8)
430+
| ((uint32_t)p[6] << 16) | ((uint32_t)p[7] << 24);
431+
/* Bounds-check the declared chunk size against the buffer. */
432+
if (chunk_size > (uint32_t)(end - (p + 8)))
406433
{
407-
uint32_t chunk_size = (uint32_t)p[4] | ((uint32_t)p[5] << 8)
408-
| ((uint32_t)p[6] << 16) | ((uint32_t)p[7] << 24);
409-
if (memcmp(p, "MEM ", 4) == 0)
410-
{
411-
payload = p + 8;
412-
payload_size = chunk_size;
413-
found = 1;
414-
break;
415-
}
416-
p += 8 + chunk_size;
434+
state_err = "RASTATE chunk size overruns buffer";
435+
goto state_fail;
417436
}
418-
if (!found)
437+
if (memcmp(p, "MEM ", 4) == 0)
419438
{
420-
fprintf(stderr, "ERROR: no MEM chunk in RASTATE file\n");
421-
free(st_buf); return 1;
439+
payload = p + 8;
440+
payload_size = chunk_size;
441+
found = 1;
442+
break;
422443
}
423-
fprintf(stderr, "--- RASTATE: extracted MEM chunk (%zu bytes) ---\n", payload_size);
444+
p += 8 + chunk_size;
424445
}
425-
expected = pretro_serialize_size();
426-
fprintf(stderr, "--- State payload: %zu bytes (core expects %zu) ---\n",
427-
payload_size, expected);
428-
if (!pretro_unserialize(payload, payload_size))
446+
if (!found)
429447
{
430-
fprintf(stderr, "ERROR: retro_unserialize failed\n");
431-
free(st_buf); return 1;
448+
state_err = "no MEM chunk in RASTATE file";
449+
goto state_fail;
432450
}
433-
fprintf(stderr, "--- State loaded from %s ---\n", state_load_path);
434-
free(st_buf);
451+
fprintf(stderr, "--- RASTATE: extracted MEM chunk (%zu bytes) ---\n", payload_size);
452+
}
453+
expected = pretro_serialize_size();
454+
fprintf(stderr, "--- State payload: %zu bytes (core expects %zu) ---\n",
455+
payload_size, expected);
456+
if (!pretro_unserialize(payload, payload_size))
457+
{
458+
state_err = "retro_unserialize failed";
459+
goto state_fail;
460+
}
461+
fprintf(stderr, "--- State loaded from %s ---\n", state_load_path);
462+
free(st_buf);
463+
st_buf = NULL;
464+
465+
if (0)
466+
{
467+
state_fail:
468+
fprintf(stderr, "ERROR: %s: %s\n",
469+
state_err ? state_err : "state load error",
470+
state_load_path);
471+
if (stf) fclose(stf);
472+
if (st_buf) free(st_buf);
473+
pretro_unload_game();
474+
pretro_deinit();
475+
free((void *)info.data);
476+
dlclose(handle);
477+
return 1;
435478
}
436479
}
437480

0 commit comments

Comments
 (0)