fix(draw_sw): ensure buf_h >= 1 to prevent zero-size alloc in rotated image transform (#9776)

Signed-off-by: lijianjun <lijianjun@xiaomi.com>
This commit is contained in:
lion2tomato
2026-02-27 13:11:08 +08:00
committed by GitHub
parent 60a1b796a3
commit ca18403330
2 changed files with 60 additions and 4 deletions
+4 -4
View File
@@ -412,7 +412,7 @@ static void recolor_only(lv_draw_task_t * t, const lv_draw_image_dsc_t * draw_ds
buf_stride = 1;
}
buf_h = MAX_BUF_SIZE / buf_stride;
if(buf_h > blend_h) buf_h = blend_h;
buf_h = LV_CLAMP(1, buf_h, blend_h);
tmp_buf = lv_malloc(buf_stride * buf_h);
LV_ASSERT_MALLOC(tmp_buf);
if(!tmp_buf) {
@@ -501,19 +501,19 @@ static void transform_and_recolor(lv_draw_task_t * t, const lv_draw_image_dsc_t
if(cf_final == LV_COLOR_FORMAT_RGB565A8) {
uint32_t buf_stride = blend_w * 3;
buf_h = MAX_BUF_SIZE / buf_stride;
if(buf_h > blend_h) buf_h = blend_h;
buf_h = LV_CLAMP(1, buf_h, blend_h);
transformed_buf = lv_malloc(buf_stride * buf_h);
}
else if(cf_final == LV_COLOR_FORMAT_AL88) {
uint32_t buf_stride = blend_w;
buf_h = MAX_BUF_SIZE / (buf_stride * 2);
if(buf_h > blend_h) buf_h = blend_h;
buf_h = LV_CLAMP(1, buf_h, blend_h);
transformed_buf = lv_malloc(buf_stride * buf_h * 2);
}
else {
uint32_t buf_stride = blend_w * lv_color_format_get_size(cf_final);
buf_h = MAX_BUF_SIZE / buf_stride;
if(buf_h > blend_h) buf_h = blend_h;
buf_h = LV_CLAMP(1, buf_h, blend_h);
transformed_buf = lv_malloc(buf_stride * buf_h);
}
LV_ASSERT_MALLOC(transformed_buf);
+56
View File
@@ -523,4 +523,60 @@ void test_snapshot_extreme_size_objects(void)
lv_obj_delete(small_obj);
}
/* Regression test: rotated image with large bounding box must not
* cause heap-buffer-overflow in transform_argb8888.
*
* The bug: in img_draw_core, buf_h = MAX_BUF_SIZE / buf_stride.
* MAX_BUF_SIZE depends on _lv_refr_get_disp_refreshing(). When
* the refreshing display is small but the rotated image bounding
* box is large, buf_stride > MAX_BUF_SIZE, buf_h = 0, malloc(0),
* and OOB write in transform_argb8888.
*
* We reproduce this by temporarily shrinking the default display
* to 10x10 (making MAX_BUF_SIZE tiny), placing a large rotated
* image, and taking a snapshot. The snapshot renders into a large
* off-screen layer while MAX_BUF_SIZE stays small.
* Without fix: buf_h=0 -> infinite loop in img_draw_core.
* With fix: buf_h clamped to 1 -> completes normally. */
void test_snapshot_rotated_large_bbox_no_overflow(void)
{
lv_display_t * disp = lv_display_get_default();
/* Save original resolution and shrink to 10x10.
* MAX_BUF_SIZE = 4 * 10 * 4(ARGB8888) = 160 bytes
*
* A 200x200 image rotated 45° has bounding box ~283px.
* buf_stride = 283 * 4 = 1132 >> 160 = MAX_BUF_SIZE
* buf_h = 160 / 1132 = 0 (BUG without fix) */
int32_t orig_hor = lv_display_get_horizontal_resolution(disp);
int32_t orig_ver = lv_display_get_vertical_resolution(disp);
lv_display_set_resolution(disp, 10, 10);
/* Create a 200x200 ARGB8888 draw_buf as image source */
lv_draw_buf_t * src_buf = lv_draw_buf_create(200, 200, LV_COLOR_FORMAT_ARGB8888, 0);
TEST_ASSERT_NOT_NULL(src_buf);
lv_draw_buf_clear(src_buf, NULL);
/* Place a rotated image on the current screen */
lv_obj_t * img = lv_image_create(lv_screen_active());
lv_image_set_src(img, src_buf);
lv_image_set_rotation(img, 450); /* 45 degrees */
lv_obj_center(img);
lv_obj_update_layout(img);
/* Take a snapshot. Internally snapshot sets this display as
* refreshing (small MAX_BUF_SIZE), but renders into a layer
* sized to the object's bounding box (~283x283).
* Without fix: buf_h=0 -> infinite loop.
* With fix: buf_h clamped to 1 -> safe. */
lv_draw_buf_t * snapshot = lv_snapshot_take(img, LV_COLOR_FORMAT_ARGB8888);
TEST_ASSERT_NOT_NULL(snapshot);
/* Cleanup and restore original resolution */
lv_draw_buf_destroy(snapshot);
lv_obj_delete(img);
lv_draw_buf_destroy(src_buf);
lv_display_set_resolution(disp, orig_hor, orig_ver);
}
#endif