Conversation
…cked TileGrid put tile_width, tile_height, width and height into uint16_t fields before anything looked at them, so 65536 truncated to 0 -- which for the tile size is the "use the whole bitmap" value and for the grid size gives no tiles at all. default_tile was never bounded against the bitmap it indexes, and the tile count was multiplied in int, which overflows for a large grid and was then used as the size of an allocation. A Group's scale is a uint16_t and its constructor accepts up to 32767, but absolute_transform held it in a uint8_t and the step in an int8_t. Those are widened, the products computed in uint32_t and clamped, and the setter given the upper bound the constructor always implied. The palette bounds check admitted an index equal to the colour count, which is already one past the end. TilePaletteMapper accepted zero colours per tile, and width_in_tiles is only assigned when it is bound to a TileGrid, so a subscript before that divided by zero. An input pixel past input_color_count also indexed the mapping row unchecked. vectorio.Polygon takes its point count as a size_t but keeps twice it in a uint16_t, and counted the validation loop with a uint16_t as well.
|
Testing and diagnostic script. |
| displayio_group_t *self = native_group(self_in); | ||
|
|
||
| mp_int_t scale = mp_arg_validate_int_min(mp_obj_get_int(scale_obj), 1, MP_QSTR_scale); | ||
| mp_int_t scale = mp_arg_validate_int_range(mp_obj_get_int(scale_obj), 1, 32767, MP_QSTR_scale); |
There was a problem hiding this comment.
scale is declared a uint16_t here:
So, theoretically, up to 65535 would fit in this. Though I do think limiting it to something lower makes sense.
Going even lower than 32767 would be fine with me. I don't know whether it's worth using a smaller sized variable, but for visual practicality anything over a scale of a few dozen or maybe hundreds is going to exceed the "a few pixels are now larger than the displays we support" range.
FoamyGuy
left a comment
There was a problem hiding this comment.
Thanks for the improved error checking. A few requests and questions.
| } | ||
|
|
||
| mp_arg_validate_int_min(args[ARG_input_color_count].u_int, 1, MP_QSTR_input_color_count); | ||
|
|
There was a problem hiding this comment.
input_color_count is uint16_t (
We can validate the top end of the range also instead of just min here.
| uint16_t y = 0; | ||
| uint16_t width = common_hal_tilepalettemapper_tilepalettemapper_get_width(self); | ||
| if (width == 0) { | ||
| mp_raise_IndexError(MP_ERROR_TEXT("Tile index out of bounds")); |
There was a problem hiding this comment.
I don't think we need this check here inside of subsc()
I think the right time to check the size of the TileGrid would be when it gets bound to the TilePaletteMapper instead of any time square bracket access is used.
But I don't think we even need to do that because the width and height of TPM are set to the width and height of the TileGrid it gets bound to, and TileGrid has range enforcement on height and width added by this PR
| uint8_t scale; | ||
| int16_t dx; | ||
| int16_t dy; | ||
| uint16_t scale; |
There was a problem hiding this comment.
Why are these changed to uint8_t? In the case of scale at least, I wonder if it would make more sense to adjust the other uses of scale in displayio to be uint8_t and then do proper bounds checking on that size instead of having this move up to uint16_t
Code written by Claude Code, guided and corrected by @peterbay.
The problem
Arguments that reach a
uint16_tor auint8_tfield before anything checks them, so a value past the field's range wraps into a valid-looking one, and two indices that are used a step past the end of what they index.The changes
TileGridnarrowed four arguments before validating them.tile_widthandtile_heightwent intouint16_tfields, where 65536 truncates to 0 — which is the "use the whole bitmap" value, so it was taken as that rather than refused.widthandheightdid the same, giving a grid of zero tiles. All four are checked as parsed now, andwidthandheighthave to be at least one.default_tilewas never bounded against the bitmap. It indexes the bitmap's tiles, and any value was accepted and stored.The tile count was multiplied in
int.width * heightwith bothuint16_tpromotes tointand overflows for a large grid — 46341 by 46341 is 2147488281, pastINT_MAX— and the product was then used as the size of an allocation. It is computed inuint32_tand refused if it will not fit.A
Group's scale did not fit where the transform kept it. The scale is auint16_tand the constructor accepts up to 32767, butabsolute_transformheld it in auint8_tand the step in anint8_t. Those are widened, the products are computed inuint32_tand clamped, and the setter now has the upper bound the constructor always implied.The palette bounds check was off by one.
colorsholds exactlycolor_countentries, so an index equal to the count is already past the end;>let it through.TilePaletteMapperaccepted zero colours per tile and was subscriptable before it was bound.width_in_tilesis only set when the mapper is attached to aTileGrid, andMICROPY_GC_CONSERVATIVE_CLEARmakes it deterministically zero until then, somapper[0]divided by it. An input pixel pastinput_color_countalso indexed the mapping row unchecked.vectorio.Polygon's point count did not fit its own length field. The count is asize_tin the setter, butself->lenis auint16_tholding twice it, and the validation loop counted with auint16_tas well.Testing
Seeed XIAO nRF52840 Sense, on two builds differing only by these changes. No display is involved: every row below is an argument that should be refused at the boundary.
TileGrid(bmp, tile_width=65536)on a 16x16 bitmaptile_widthreads 16ValueError: tile_width must be 0-65535TileGrid(bmp, tile_height=65536)ValueErrorTileGrid(bmp, width=65536)widthreads 0ValueError: width must be 1-65535TileGrid(bmp, width=0)ValueErrorTileGrid(bmp, tile_width=8, tile_height=8, default_tile=99), four tilesValueError: default_tile must be 0-3TileGrid(bmp, tile_width=1, tile_height=1, width=46341, height=46341)ValueError: TileGrid length must be <= 2147483647group.scale = 40000ValueError: scale must be 1-32767TilePaletteMapper(palette, 0)ValueError: input_color_count must be >= 1Four of the changes are not in that table. The widened transform, the palette off-by-one and the mapper's input-pixel bound are all on the drawing path, which needs a display to reach. And the polygon point count needs more than 32767 points to show, which is about 130 kB of coordinates before the list of tuples that carries them — more than this board has.
No new translatable strings.