diff --git a/src/draw/sw/lv_draw_sw_img.c b/src/draw/sw/lv_draw_sw_img.c index b401a08853..047e85cd28 100644 --- a/src/draw/sw/lv_draw_sw_img.c +++ b/src/draw/sw/lv_draw_sw_img.c @@ -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); diff --git a/tests/src/test_cases/test_snapshot.c b/tests/src/test_cases/test_snapshot.c index 3ad55213bf..af9da6504f 100644 --- a/tests/src/test_cases/test_snapshot.c +++ b/tests/src/test_cases/test_snapshot.c @@ -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