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>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
This commit is contained in:
2026-08-01 00:24:35 -04:00
parent 3a016a99f6
commit b022b62e1b
14 changed files with 3763 additions and 39 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,225 @@
{
"tilesets": [
{
"columns": 48,
"firstgid": 1,
"image": "assets/World_A1.png",
"imageheight": 576,
"imagewidth": 768,
"margin": 0,
"name": "World_A1_0",
"spacing": 0,
"tilecount": 1728,
"tileheight": 16,
"tilewidth": 16
},
{
"columns": 48,
"firstgid": 1729,
"image": "assets/World_A1.png",
"imageheight": 576,
"imagewidth": 768,
"margin": 0,
"name": "World_A1_1",
"spacing": 0,
"tilecount": 1728,
"tileheight": 16,
"tilewidth": 16
},
{
"columns": 48,
"firstgid": 3457,
"image": "assets/World_A1.png",
"imageheight": 576,
"imagewidth": 768,
"margin": 0,
"name": "World_A1_2",
"spacing": 0,
"tilecount": 1728,
"tileheight": 16,
"tilewidth": 16
},
{
"columns": 48,
"firstgid": 5185,
"image": "assets/World_A1.png",
"imageheight": 576,
"imagewidth": 768,
"margin": 0,
"name": "World_A1_3",
"spacing": 0,
"tilecount": 1728,
"tileheight": 16,
"tilewidth": 16
},
{
"columns": 48,
"firstgid": 6913,
"image": "assets/World_A1.png",
"imageheight": 576,
"imagewidth": 768,
"margin": 0,
"name": "World_A1_4",
"spacing": 0,
"tilecount": 1728,
"tileheight": 16,
"tilewidth": 16
},
{
"columns": 48,
"firstgid": 8641,
"image": "assets/World_A1.png",
"imageheight": 576,
"imagewidth": 768,
"margin": 0,
"name": "World_A1_5",
"spacing": 0,
"tilecount": 1728,
"tileheight": 16,
"tilewidth": 16
},
{
"columns": 48,
"firstgid": 10369,
"image": "assets/World_A1.png",
"imageheight": 576,
"imagewidth": 768,
"margin": 0,
"name": "World_A1_6",
"spacing": 0,
"tilecount": 1728,
"tileheight": 16,
"tilewidth": 16
},
{
"columns": 48,
"firstgid": 12097,
"image": "assets/World_A1.png",
"imageheight": 576,
"imagewidth": 768,
"margin": 0,
"name": "World_A1_7",
"spacing": 0,
"tilecount": 1728,
"tileheight": 16,
"tilewidth": 16
},
{
"columns": 48,
"firstgid": 13825,
"image": "assets/World_A1.png",
"imageheight": 576,
"imagewidth": 768,
"margin": 0,
"name": "World_A1_8",
"spacing": 0,
"tilecount": 1728,
"tileheight": 16,
"tilewidth": 16
},
{
"columns": 48,
"firstgid": 15553,
"image": "assets/World_A1.png",
"imageheight": 576,
"imagewidth": 768,
"margin": 0,
"name": "World_A1_9",
"spacing": 0,
"tilecount": 1728,
"tileheight": 16,
"tilewidth": 16
},
{
"columns": 48,
"firstgid": 17281,
"image": "assets/World_A1.png",
"imageheight": 576,
"imagewidth": 768,
"margin": 0,
"name": "World_A1_10",
"spacing": 0,
"tilecount": 1728,
"tileheight": 16,
"tilewidth": 16
},
{
"columns": 48,
"firstgid": 19009,
"image": "assets/World_A1.png",
"imageheight": 576,
"imagewidth": 768,
"margin": 0,
"name": "World_A1_11",
"spacing": 0,
"tilecount": 1728,
"tileheight": 16,
"tilewidth": 16
},
{
"columns": 48,
"firstgid": 20737,
"image": "assets/World_A1.png",
"imageheight": 576,
"imagewidth": 768,
"margin": 0,
"name": "World_A1_12",
"spacing": 0,
"tilecount": 1728,
"tileheight": 16,
"tilewidth": 16
},
{
"columns": 48,
"firstgid": 22465,
"image": "assets/World_A1.png",
"imageheight": 576,
"imagewidth": 768,
"margin": 0,
"name": "World_A1_13",
"spacing": 0,
"tilecount": 1728,
"tileheight": 16,
"tilewidth": 16
},
{
"columns": 48,
"firstgid": 24193,
"image": "assets/World_A1.png",
"imageheight": 576,
"imagewidth": 768,
"margin": 0,
"name": "World_A1_14",
"spacing": 0,
"tilecount": 1728,
"tileheight": 16,
"tilewidth": 16
},
{
"columns": 48,
"firstgid": 25921,
"image": "assets/World_A1.png",
"imageheight": 576,
"imagewidth": 768,
"margin": 0,
"name": "World_A1_15",
"spacing": 0,
"tilecount": 1728,
"tileheight": 16,
"tilewidth": 16
},
{
"columns": 48,
"firstgid": 27649,
"image": "assets/World_A1.png",
"imageheight": 576,
"imagewidth": 768,
"margin": 0,
"name": "World_A1_16",
"spacing": 0,
"tilecount": 1728,
"tileheight": 16,
"tilewidth": 16
}
]
}

View File

@@ -0,0 +1,31 @@
{
"spritesheet": {
"filename": "spritesheet.png",
"frame_width": 48,
"frame_height": 48
},
"name": "testsprite_maxframes",
"width": 48,
"height": 48,
"speed": 100,
"loop": true,
"loopReverse": true,
"frames": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15
]
}

View File

@@ -0,0 +1,32 @@
{
"spritesheet": {
"filename": "spritesheet.png",
"frame_width": 48,
"frame_height": 48
},
"name": "testsprite_toomanyframes",
"width": 48,
"height": 48,
"speed": 100,
"loop": true,
"loopReverse": true,
"frames": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16
]
}

View File

@@ -0,0 +1,17 @@
{
"spritesheet": {
"filename": "spritesheet.png",
"frame_width": 48,
"frame_height": 48
},
"name": "testsprite_widecount",
"width": 48,
"height": 48,
"speed": 100,
"loop": true,
"loopReverse": true,
"frames": [
0,
256
]
}