From 6a0c5f97a043c81411d6468c968af1c084470268 Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Mon, 20 Apr 2026 18:26:18 +0200 Subject: [PATCH 1/3] screen: Fix sb.pos_num sb.pos, sb.pos_num and sb.count must stay coherent. Fix the few corner case where sb.pos_num can be greater than sb.count Which can cause a heap overvlow in set_selection like this: ==1236==ERROR: AddressSanitizer: heap-use-after-free on address 0x7cad23ff0058 at pc 0x7f4d26389bb0 bp 0x7ffee6280cc0 sp 0x7ffee6280cb0 READ of size 8 at 0x7cad23ff0058 thread T0 #0 0x7f4d26389baf in selection_set ../libtsm/src/tsm/tsm-selection.c:78 #1 0x7f4d263a1754 in tsm_screen_selection_start ../libtsm/src/tsm/tsm-selection.c:242 #2 0x5559ccbbdfb2 in start_selection Signed-off-by: Jocelyn Falempe --- src/shared/shl_dlist.h | 3 +++ src/tsm/tsm-screen.c | 14 ++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/shared/shl_dlist.h b/src/shared/shl_dlist.h index 53b29f9..f21ec50 100644 --- a/src/shared/shl_dlist.h +++ b/src/shared/shl_dlist.h @@ -105,6 +105,9 @@ static inline bool shl_dlist_empty(struct shl_dlist *head) #define shl_dlist_next(iter, head, member) \ ((iter)->member.next == (head) ? NULL : shl_dlist_entry((iter)->member.next, typeof(*iter), list)) +#define shl_dlist_prev(iter, head, member) \ + ((iter)->member.prev == (head) ? NULL : shl_dlist_entry((iter)->member.prev, typeof(*iter), list)) + #define shl_dlist_for_each(iter, head) for (iter = (head)->next; iter != (head); iter = iter->next) #define shl_dlist_for_each_but_one(iter, start, head) \ diff --git a/src/tsm/tsm-screen.c b/src/tsm/tsm-screen.c index cafbfc1..3247242 100644 --- a/src/tsm/tsm-screen.c +++ b/src/tsm/tsm-screen.c @@ -222,7 +222,9 @@ static void link_to_scrollback(struct tsm_screen *con, struct line *line) /* Only consider sb.max > 1, so there is always another line in sb. */ if (con->sb.pos == tmp) { con->sb.pos = shl_dlist_first(&con->sb.list, struct line, list); - ++con->sb.pos_num; + con->sb.pos_num = 0; + } else { + con->sb.pos_num--; } clear_selection_on_line(con, tmp); line_free(tmp); @@ -855,6 +857,8 @@ void tsm_screen_clear_sb(struct tsm_screen *con) SHL_EXPORT void tsm_screen_sb_up(struct tsm_screen *con, unsigned int num) { + struct line *prev; + if (!con || !num) return; @@ -870,8 +874,14 @@ void tsm_screen_sb_up(struct tsm_screen *con, unsigned int num) if (con->sb.pos_num == 0) return; - con->sb.pos = shl_dlist_last(&con->sb.pos->list, struct line, list); + prev = shl_dlist_prev(con->sb.pos, &con->sb.list, list); + if (!prev) { + llog_error(con, "prev is NULL, con->sb.pos_num: %d con->sb.count: %d", + con->sb.pos_num, con->sb.count); + return; + } --con->sb.pos_num; + con->sb.pos = prev; } else { con->sb.pos = shl_dlist_last(&con->sb.list, struct line, list); con->sb.pos_num = con->sb.count - 1; From 978a76907902a405b5399d51113015d0ecafb517 Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Mon, 20 Apr 2026 18:26:37 +0200 Subject: [PATCH 2/3] test/selection: check sb.pos_num Check that sb.pos_num and sb.pos stays coherent during the robustness test. Signed-off-by: Jocelyn Falempe --- test/test_selection.c | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/test/test_selection.c b/test/test_selection.c index 65ebfc1..cbc38c7 100644 --- a/test/test_selection.c +++ b/test/test_selection.c @@ -798,6 +798,26 @@ START_TEST(test_screen_copy_lines_sb_scrolled_cut_off) } END_TEST +static void check_sb_pos(struct tsm_screen *screen) +{ + struct line *line = shl_dlist_first(&screen->sb.list, struct line, list); + int count = 0; + + if (!screen->sb.pos) { + ck_assert_int_eq(screen->sb.pos_num, screen->sb.count); + return; + } + + ck_assert_int_le(screen->sb.pos_num, screen->sb.count); + + while (line && line != screen->sb.pos) { + count++; + line = shl_dlist_next(line, &screen->sb.list, list); + } + + ck_assert_int_eq(count, screen->sb.pos_num); +} + static void write_random_string(struct tsm_screen *screen, int count) { char str[201]; @@ -834,14 +854,18 @@ START_TEST(test_screen_robustness) write_random_string(screen, 600); + check_sb_pos(screen); + for (i = 0; i < 300; i++) { size_x = 1 + rand() % 100; size_y = 1 + rand() % 100; r = tsm_screen_resize(screen, size_x, size_y); ck_assert_int_eq(r, 0); - - tsm_screen_scroll_up(screen, rand() % sb_size); - tsm_screen_scroll_down(screen, rand() % sb_size); + check_sb_pos(screen); + tsm_screen_sb_up(screen, rand() % sb_size); + check_sb_pos(screen); + tsm_screen_sb_down(screen, rand() % sb_size); + check_sb_pos(screen); tsm_screen_selection_start(screen, rand() % size_x, rand() % size_y); tsm_screen_selection_target(screen, rand() % size_x, rand() % size_y); for (j = 0; j < 50; j++) { @@ -851,8 +875,10 @@ START_TEST(test_screen_robustness) free(str); str = NULL; } - tsm_screen_scroll_up(screen, rand() % sb_size); - tsm_screen_scroll_down(screen, rand() % sb_size); + tsm_screen_sb_up(screen, sb_size ); + check_sb_pos(screen); + tsm_screen_sb_down(screen, rand() % sb_size); + check_sb_pos(screen); } write_random_string(screen, rand() % 100); } From c0ea258651869fc406950966e3d8ff42cda197d6 Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Mon, 20 Apr 2026 20:17:51 +0200 Subject: [PATCH 3/3] test/screen: add resize in the sb test Add screen resize in the scrollback count and pos_num test Signed-off-by: Jocelyn Falempe --- test/test_screen.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/test_screen.c b/test/test_screen.c index f1ac141..9811807 100644 --- a/test/test_screen.c +++ b/test/test_screen.c @@ -257,6 +257,34 @@ START_TEST(test_screen_sb_get_line_pos) ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 3); ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 3); + tsm_screen_newline(screen); + ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 4); + ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 4); + + tsm_screen_newline(screen); + ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 5); + ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 5); + + tsm_screen_sb_up(screen, 2); + ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 5); + ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 3); + + tsm_screen_newline(screen); + ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 5); + ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 2); + + r = tsm_screen_resize(screen, 5, 3); + ck_assert_int_eq(r, 0); + + ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 5); + ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 0); + + r = tsm_screen_resize(screen, 5, 5); + ck_assert_int_eq(r, 0); + + ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 3); + ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 0); + tsm_screen_unref(screen); screen = NULL; }