Bound every array a data file can index
Closes Defects items 16 and 17 and Known-and-still-open item 6. All three let an asset file, or a caller's argument, write past a fixed array. akgl_sprite_load_json took its frame count straight from the document and wrote that many entries into a 16-byte frameids -- through a uint32_t * cast of a uint8_t *, so each write touched four bytes and the overrun reached four bytes past the array, into the rest of akgl_Sprite and then the next pool slot. The count is checked first now, each id is read into an int and narrowed deliberately, and a frame number too large for a uint8_t is refused rather than truncated into an index for a different tile. The tilemap loader had the same shape twice: objects[j] with no check against AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER and tilesets[i] with none against AKGL_TILEMAP_MAX_TILESETS. akgl_tilemap_load_layers already bounded its own loop, so the pattern was in the same file. The object one is the reachable half -- 128 objects is not a large object layer. akgl_string_initialize zeroed sizeof(akgl_String) starting at `data`, which begins after the refcount in front of it, so it ran four bytes past the end of the object and onto the *next* slot's refcount -- the field the allocator reads to decide whether a slot is free. Same file, same class, fixed with it: akgl_string_copy accepted a count larger than the buffers, reading past one pool slot and writing past another, which the header documented as behaviour. Every case has a test that fails against the old code, with five new fixtures. Exactly-the-maximum is asserted alongside one-past in each, so the bound cannot be fixed by making the limit off by one. 25/25 pass, memcheck clean, reindent --check clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -229,6 +229,81 @@ akerr_ErrorContext *test_akgl_tilemap_compute_tileset_offsets(void)
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The loader must refuse a map that would overrun its fixed tables.
|
||||
*
|
||||
* Two loops indexed straight from the document. `curlayer->objects[j]` had no
|
||||
* check against AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER, and `dest->tilesets[i]`
|
||||
* none against AKGL_TILEMAP_MAX_TILESETS. The object one is the reachable one:
|
||||
* 128 objects is not a large object layer, and akgl_TilemapObject is big, so
|
||||
* the 129th wrote well past the end of the layer.
|
||||
*
|
||||
* akgl_tilemap_load_layers already bounded its own loop and raised
|
||||
* AKERR_OUTOFBOUNDS, so the shape to copy was in the same file.
|
||||
*/
|
||||
akerr_ErrorContext *test_akgl_tilemap_load_bounds_fixed_tables(void)
|
||||
{
|
||||
akgl_String *pathstr = NULL;
|
||||
PREPARE_ERROR(errctx);
|
||||
json_t *doc = NULL;
|
||||
json_error_t errdata;
|
||||
|
||||
ATTEMPT {
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
CATCH(errctx, akgl_heap_next_string(&pathstr));
|
||||
|
||||
// Exactly the maximum must still load.
|
||||
memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s",
|
||||
SDL_GetBasePath(), "assets/snippets/test_tilemap_max_objects.json");
|
||||
doc = json_load_file((char *)&pathstr->data, 0, &errdata);
|
||||
FAIL_ZERO_BREAK(errctx, doc, AKERR_NULLPOINTER, "max-objects fixture: %s", (char *)&errdata.text);
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets");
|
||||
TEST_EXPECT_OK(errctx,
|
||||
akgl_tilemap_load_layer_objects(akgl_gamemap, doc, 0, pathstr),
|
||||
"loading an object layer with exactly the maximum objects");
|
||||
json_decref(doc);
|
||||
doc = NULL;
|
||||
|
||||
// One more must be refused rather than written.
|
||||
memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s",
|
||||
SDL_GetBasePath(), "assets/snippets/test_tilemap_too_many_objects.json");
|
||||
doc = json_load_file((char *)&pathstr->data, 0, &errdata);
|
||||
FAIL_ZERO_BREAK(errctx, doc, AKERR_NULLPOINTER, "too-many-objects fixture: %s", (char *)&errdata.text);
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets");
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
|
||||
akgl_tilemap_load_layer_objects(akgl_gamemap, doc, 0, pathstr),
|
||||
"loading an object layer with one object too many");
|
||||
json_decref(doc);
|
||||
doc = NULL;
|
||||
|
||||
// And the tileset table.
|
||||
memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s",
|
||||
SDL_GetBasePath(), "assets/snippets/test_tilemap_too_many_tilesets.json");
|
||||
doc = json_load_file((char *)&pathstr->data, 0, &errdata);
|
||||
FAIL_ZERO_BREAK(errctx, doc, AKERR_NULLPOINTER, "too-many-tilesets fixture: %s", (char *)&errdata.text);
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets");
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
|
||||
akgl_tilemap_load_tilesets(akgl_gamemap, doc, pathstr),
|
||||
"loading a map with one tileset too many");
|
||||
TEST_ASSERT(errctx, akgl_gamemap->numtilesets <= AKGL_TILEMAP_MAX_TILESETS,
|
||||
"numtilesets reached %d, past the %d the table holds",
|
||||
akgl_gamemap->numtilesets, AKGL_TILEMAP_MAX_TILESETS);
|
||||
} CLEANUP {
|
||||
if ( doc != NULL ) {
|
||||
json_decref(doc);
|
||||
}
|
||||
if ( pathstr != NULL ) {
|
||||
IGNORE(akgl_heap_release_string(pathstr));
|
||||
}
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *test_akgl_tilemap_load_layer_objects(void)
|
||||
{
|
||||
akgl_String *pathstr;
|
||||
@@ -538,6 +613,7 @@ int main(void)
|
||||
CATCH(errctx, test_akgl_tilemap_load_layer_tile());
|
||||
CATCH(errctx, test_akgl_tilemap_load_layers());
|
||||
CATCH(errctx, test_akgl_tilemap_load_tilesets());
|
||||
CATCH(errctx, test_akgl_tilemap_load_bounds_fixed_tables());
|
||||
//CATCH(errctx, test_akgl_tilemap_load());
|
||||
//CATCH(errctx, test_akgl_tilemap_draw_tileset());
|
||||
//CATCH(errctx, test_akgl_tilemap_draw());
|
||||
|
||||
Reference in New Issue
Block a user