From 1fba7b3ac505eb349e86cbbdef3eebf6c550f493 Mon Sep 17 00:00:00 2001 From: Simon Marchi Date: Mon, 8 May 2023 10:41:36 -0400 Subject: [PATCH] gdb: fix use-after-free in check_longjmp_breakpoint_for_call_dummy Commit 7a8de0c33019 ("Remove ALL_BREAKPOINTS_SAFE") introduced a use-after-free in the breakpoints iterations (see below for full ASan report). This makes gdb.base/stale-infcall.exp fail when GDB is build with ASan. check_longjmp_breakpoint_for_call_dummy iterates on all breakpoints, possibly deleting the current breakpoint as well as related breakpoints. The problem arises when a breakpoint in the B->related_breakpoint chain is also B->next. In that case, deleting that related breakpoint frees the breakpoint that all_breakpoints_safe has saved. The old code worked around that by manually changing B_TMP, which was the next breakpoint saved by the "safe iterator": while (b->related_breakpoint != b) { if (b_tmp == b->related_breakpoint) b_tmp = b->related_breakpoint->next; delete_breakpoint (b->related_breakpoint); } (Note that this seemed to assume that b->related_breakpoint->next was the same as b->next->next, not sure this is guaranteed.) The new code kept the B_TMP variable, but it's not useful in that context. We can't go change the next breakpoint as saved by the safe iterator, like we did before. I suggest fixing that by saving the breakpoints to delete in a map and deleting them all at the end. Here's the full ASan report: (gdb) PASS: gdb.base/stale-infcall.exp: continue to breakpoint: break-run1 print infcall () ================================================================= ==47472==ERROR: AddressSanitizer: heap-use-after-free on address 0x611000034980 at pc 0x563f7012c7bc bp 0x7ffdf3804d70 sp 0x7ffdf3804d60 READ of size 8 at 0x611000034980 thread T0 #0 0x563f7012c7bb in next_iterator::operator++() /home/smarchi/src/binutils-gdb/gdb/../gdbsupport/next-iterator.h:66 #1 0x563f702ce8c0 in basic_safe_iterator >::operator++() /home/smarchi/src/binutils-gdb/gdb/../gdbsupport/safe-iterator.h:84 #2 0x563f7021522a in check_longjmp_breakpoint_for_call_dummy(thread_info*) /home/smarchi/src/binutils-gdb/gdb/breakpoint.c:7611 #3 0x563f714567b1 in process_event_stop_test /home/smarchi/src/binutils-gdb/gdb/infrun.c:6881 #4 0x563f71454e07 in handle_signal_stop /home/smarchi/src/binutils-gdb/gdb/infrun.c:6769 #5 0x563f7144b680 in handle_inferior_event /home/smarchi/src/binutils-gdb/gdb/infrun.c:6023 #6 0x563f71436165 in fetch_inferior_event() /home/smarchi/src/binutils-gdb/gdb/infrun.c:4387 #7 0x563f7136ff51 in inferior_event_handler(inferior_event_type) /home/smarchi/src/binutils-gdb/gdb/inf-loop.c:42 #8 0x563f7168038d in handle_target_event /home/smarchi/src/binutils-gdb/gdb/linux-nat.c:4219 #9 0x563f72fccb6d in handle_file_event /home/smarchi/src/binutils-gdb/gdbsupport/event-loop.cc:573 #10 0x563f72fcd503 in gdb_wait_for_event /home/smarchi/src/binutils-gdb/gdbsupport/event-loop.cc:694 #11 0x563f72fcaf2b in gdb_do_one_event(int) /home/smarchi/src/binutils-gdb/gdbsupport/event-loop.cc:217 #12 0x563f7262b9bb in wait_sync_command_done() /home/smarchi/src/binutils-gdb/gdb/top.c:426 #13 0x563f7137a7c3 in run_inferior_call /home/smarchi/src/binutils-gdb/gdb/infcall.c:650 #14 0x563f71381295 in call_function_by_hand_dummy(value*, type*, gdb::array_view, void (*)(void*, int), void*) /home/smarchi/src/binutils-gdb/gdb/infcall.c:1332 #15 0x563f7137c0e2 in call_function_by_hand(value*, type*, gdb::array_view) /home/smarchi/src/binutils-gdb/gdb/infcall.c:780 #16 0x563f70fe5960 in evaluate_subexp_do_call(expression*, noside, value*, gdb::array_view, char const*, type*) /home/smarchi/src/binutils-gdb/gdb/eval.c:649 #17 0x563f70fe6617 in expr::operation::evaluate_funcall(type*, expression*, noside, char const*, std::__debug::vector >, std::allocator > > > const&) /home/smarchi/src/binutils-gdb/gdb/eval.c:677 #18 0x563f6fd19668 in expr::operation::evaluate_funcall(type*, expression*, noside, std::__debug::vector >, std::allocator > > > const&) /home/smarchi/src/binutils-gdb/gdb/expression.h:136 #19 0x563f70fe6bba in expr::var_value_operation::evaluate_funcall(type*, expression*, noside, std::__debug::vector >, std::allocator > > > const&) /home/smarchi/src/binutils-gdb/gdb/eval.c:689 #20 0x563f704b71dc in expr::funcall_operation::evaluate(type*, expression*, noside) /home/smarchi/src/binutils-gdb/gdb/expop.h:2219 #21 0x563f70fe0f02 in expression::evaluate(type*, noside) /home/smarchi/src/binutils-gdb/gdb/eval.c:110 #22 0x563f71b1373e in process_print_command_args /home/smarchi/src/binutils-gdb/gdb/printcmd.c:1319 #23 0x563f71b1391b in print_command_1 /home/smarchi/src/binutils-gdb/gdb/printcmd.c:1332 #24 0x563f71b147ec in print_command /home/smarchi/src/binutils-gdb/gdb/printcmd.c:1465 #25 0x563f706029b8 in do_simple_func /home/smarchi/src/binutils-gdb/gdb/cli/cli-decode.c:95 #26 0x563f7061972a in cmd_func(cmd_list_element*, char const*, int) /home/smarchi/src/binutils-gdb/gdb/cli/cli-decode.c:2735 #27 0x563f7262d0ef in execute_command(char const*, int) /home/smarchi/src/binutils-gdb/gdb/top.c:572 #28 0x563f7100ed9c in command_handler(char const*) /home/smarchi/src/binutils-gdb/gdb/event-top.c:543 #29 0x563f7101014b in command_line_handler(std::unique_ptr >&&) /home/smarchi/src/binutils-gdb/gdb/event-top.c:779 #30 0x563f72777942 in tui_command_line_handler /home/smarchi/src/binutils-gdb/gdb/tui/tui-interp.c:104 #31 0x563f7100d059 in gdb_rl_callback_handler /home/smarchi/src/binutils-gdb/gdb/event-top.c:250 #32 0x7f5a80418246 in rl_callback_read_char (/usr/lib/libreadline.so.8+0x3b246) (BuildId: 092e91fc4361b0ef94561e3ae03a75f69398acbb) #33 0x563f7100ca06 in gdb_rl_callback_read_char_wrapper_noexcept /home/smarchi/src/binutils-gdb/gdb/event-top.c:192 #34 0x563f7100cc5e in gdb_rl_callback_read_char_wrapper /home/smarchi/src/binutils-gdb/gdb/event-top.c:225 #35 0x563f728c70db in stdin_event_handler /home/smarchi/src/binutils-gdb/gdb/ui.c:155 #36 0x563f72fccb6d in handle_file_event /home/smarchi/src/binutils-gdb/gdbsupport/event-loop.cc:573 #37 0x563f72fcd503 in gdb_wait_for_event /home/smarchi/src/binutils-gdb/gdbsupport/event-loop.cc:694 #38 0x563f72fcb15c in gdb_do_one_event(int) /home/smarchi/src/binutils-gdb/gdbsupport/event-loop.cc:264 #39 0x563f7177ec1c in start_event_loop /home/smarchi/src/binutils-gdb/gdb/main.c:412 #40 0x563f7177f12e in captured_command_loop /home/smarchi/src/binutils-gdb/gdb/main.c:476 #41 0x563f717846e4 in captured_main /home/smarchi/src/binutils-gdb/gdb/main.c:1320 #42 0x563f71784821 in gdb_main(captured_main_args*) /home/smarchi/src/binutils-gdb/gdb/main.c:1339 #43 0x563f6fcedfbd in main /home/smarchi/src/binutils-gdb/gdb/gdb.c:32 #44 0x7f5a7e43984f (/usr/lib/libc.so.6+0x2384f) (BuildId: 2f005a79cd1a8e385972f5a102f16adba414d75e) #45 0x7f5a7e439909 in __libc_start_main (/usr/lib/libc.so.6+0x23909) (BuildId: 2f005a79cd1a8e385972f5a102f16adba414d75e) #46 0x563f6fcedd84 in _start (/home/smarchi/build/binutils-gdb/gdb/gdb+0xafb0d84) (BuildId: 50bd32e6e9d5e84543e9897b8faca34858ca3995) 0x611000034980 is located 0 bytes inside of 208-byte region [0x611000034980,0x611000034a50) freed by thread T0 here: #0 0x7f5a7fce312a in operator delete(void*, unsigned long) /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_new_delete.cpp:164 #1 0x563f702bd1fa in momentary_breakpoint::~momentary_breakpoint() /home/smarchi/src/binutils-gdb/gdb/breakpoint.c:304 #2 0x563f702771c5 in delete_breakpoint(breakpoint*) /home/smarchi/src/binutils-gdb/gdb/breakpoint.c:12404 #3 0x563f702150a7 in check_longjmp_breakpoint_for_call_dummy(thread_info*) /home/smarchi/src/binutils-gdb/gdb/breakpoint.c:7673 #4 0x563f714567b1 in process_event_stop_test /home/smarchi/src/binutils-gdb/gdb/infrun.c:6881 #5 0x563f71454e07 in handle_signal_stop /home/smarchi/src/binutils-gdb/gdb/infrun.c:6769 #6 0x563f7144b680 in handle_inferior_event /home/smarchi/src/binutils-gdb/gdb/infrun.c:6023 #7 0x563f71436165 in fetch_inferior_event() /home/smarchi/src/binutils-gdb/gdb/infrun.c:4387 #8 0x563f7136ff51 in inferior_event_handler(inferior_event_type) /home/smarchi/src/binutils-gdb/gdb/inf-loop.c:42 #9 0x563f7168038d in handle_target_event /home/smarchi/src/binutils-gdb/gdb/linux-nat.c:4219 #10 0x563f72fccb6d in handle_file_event /home/smarchi/src/binutils-gdb/gdbsupport/event-loop.cc:573 #11 0x563f72fcd503 in gdb_wait_for_event /home/smarchi/src/binutils-gdb/gdbsupport/event-loop.cc:694 #12 0x563f72fcaf2b in gdb_do_one_event(int) /home/smarchi/src/binutils-gdb/gdbsupport/event-loop.cc:217 #13 0x563f7262b9bb in wait_sync_command_done() /home/smarchi/src/binutils-gdb/gdb/top.c:426 #14 0x563f7137a7c3 in run_inferior_call /home/smarchi/src/binutils-gdb/gdb/infcall.c:650 #15 0x563f71381295 in call_function_by_hand_dummy(value*, type*, gdb::array_view, void (*)(void*, int), void*) /home/smarchi/src/binutils-gdb/gdb/infcall.c:1332 #16 0x563f7137c0e2 in call_function_by_hand(value*, type*, gdb::array_view) /home/smarchi/src/binutils-gdb/gdb/infcall.c:780 #17 0x563f70fe5960 in evaluate_subexp_do_call(expression*, noside, value*, gdb::array_view, char const*, type*) /home/smarchi/src/binutils-gdb/gdb/eval.c:649 #18 0x563f70fe6617 in expr::operation::evaluate_funcall(type*, expression*, noside, char const*, std::__debug::vector >, std::allocator > > > const&) /home/smarchi/src/binutils-gdb/gdb/eval.c:677 #19 0x563f6fd19668 in expr::operation::evaluate_funcall(type*, expression*, noside, std::__debug::vector >, std::allocator > > > const&) /home/smarchi/src/binutils-gdb/gdb/expression.h:136 #20 0x563f70fe6bba in expr::var_value_operation::evaluate_funcall(type*, expression*, noside, std::__debug::vector >, std::allocator > > > const&) /home/smarchi/src/binutils-gdb/gdb/eval.c:689 #21 0x563f704b71dc in expr::funcall_operation::evaluate(type*, expression*, noside) /home/smarchi/src/binutils-gdb/gdb/expop.h:2219 #22 0x563f70fe0f02 in expression::evaluate(type*, noside) /home/smarchi/src/binutils-gdb/gdb/eval.c:110 #23 0x563f71b1373e in process_print_command_args /home/smarchi/src/binutils-gdb/gdb/printcmd.c:1319 #24 0x563f71b1391b in print_command_1 /home/smarchi/src/binutils-gdb/gdb/printcmd.c:1332 #25 0x563f71b147ec in print_command /home/smarchi/src/binutils-gdb/gdb/printcmd.c:1465 #26 0x563f706029b8 in do_simple_func /home/smarchi/src/binutils-gdb/gdb/cli/cli-decode.c:95 #27 0x563f7061972a in cmd_func(cmd_list_element*, char const*, int) /home/smarchi/src/binutils-gdb/gdb/cli/cli-decode.c:2735 #28 0x563f7262d0ef in execute_command(char const*, int) /home/smarchi/src/binutils-gdb/gdb/top.c:572 #29 0x563f7100ed9c in command_handler(char const*) /home/smarchi/src/binutils-gdb/gdb/event-top.c:543 previously allocated by thread T0 here: #0 0x7f5a7fce2012 in operator new(unsigned long) /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_new_delete.cpp:95 #1 0x563f7029a9a3 in new_momentary_breakpoint /home/smarchi/src/binutils-gdb/gdb/breakpoint.c:8129 #2 0x563f702212f6 in momentary_breakpoint_from_master /home/smarchi/src/binutils-gdb/gdb/breakpoint.c:8169 #3 0x563f70212db1 in set_longjmp_breakpoint_for_call_dummy() /home/smarchi/src/binutils-gdb/gdb/breakpoint.c:7582 #4 0x563f713804db in call_function_by_hand_dummy(value*, type*, gdb::array_view, void (*)(void*, int), void*) /home/smarchi/src/binutils-gdb/gdb/infcall.c:1260 #5 0x563f7137c0e2 in call_function_by_hand(value*, type*, gdb::array_view) /home/smarchi/src/binutils-gdb/gdb/infcall.c:780 #6 0x563f70fe5960 in evaluate_subexp_do_call(expression*, noside, value*, gdb::array_view, char const*, type*) /home/smarchi/src/binutils-gdb/gdb/eval.c:649 #7 0x563f70fe6617 in expr::operation::evaluate_funcall(type*, expression*, noside, char const*, std::__debug::vector >, std::allocator > > > const&) /home/smarchi/src/binutils-gdb/gdb/eval.c:677 #8 0x563f6fd19668 in expr::operation::evaluate_funcall(type*, expression*, noside, std::__debug::vector >, std::allocator > > > const&) /home/smarchi/src/binutils-gdb/gdb/expression.h:136 #9 0x563f70fe6bba in expr::var_value_operation::evaluate_funcall(type*, expression*, noside, std::__debug::vector >, std::allocator > > > const&) /home/smarchi/src/binutils-gdb/gdb/eval.c:689 #10 0x563f704b71dc in expr::funcall_operation::evaluate(type*, expression*, noside) /home/smarchi/src/binutils-gdb/gdb/expop.h:2219 #11 0x563f70fe0f02 in expression::evaluate(type*, noside) /home/smarchi/src/binutils-gdb/gdb/eval.c:110 #12 0x563f71b1373e in process_print_command_args /home/smarchi/src/binutils-gdb/gdb/printcmd.c:1319 #13 0x563f71b1391b in print_command_1 /home/smarchi/src/binutils-gdb/gdb/printcmd.c:1332 #14 0x563f71b147ec in print_command /home/smarchi/src/binutils-gdb/gdb/printcmd.c:1465 #15 0x563f706029b8 in do_simple_func /home/smarchi/src/binutils-gdb/gdb/cli/cli-decode.c:95 #16 0x563f7061972a in cmd_func(cmd_list_element*, char const*, int) /home/smarchi/src/binutils-gdb/gdb/cli/cli-decode.c:2735 #17 0x563f7262d0ef in execute_command(char const*, int) /home/smarchi/src/binutils-gdb/gdb/top.c:572 #18 0x563f7100ed9c in command_handler(char const*) /home/smarchi/src/binutils-gdb/gdb/event-top.c:543 #19 0x563f7101014b in command_line_handler(std::unique_ptr >&&) /home/smarchi/src/binutils-gdb/gdb/event-top.c:779 #20 0x563f72777942 in tui_command_line_handler /home/smarchi/src/binutils-gdb/gdb/tui/tui-interp.c:104 #21 0x563f7100d059 in gdb_rl_callback_handler /home/smarchi/src/binutils-gdb/gdb/event-top.c:250 #22 0x7f5a80418246 in rl_callback_read_char (/usr/lib/libreadline.so.8+0x3b246) (BuildId: 092e91fc4361b0ef94561e3ae03a75f69398acbb) Change-Id: Id00c17ab677f847fbf4efdf0f4038373668d3d88 Approved-By: Tom Tromey --- gdb/breakpoint.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c index 1cc9e84c11f..dcb00bffdbe 100644 --- a/gdb/breakpoint.c +++ b/gdb/breakpoint.c @@ -69,6 +69,7 @@ #include "tid-parse.h" #include "cli/cli-style.h" #include "cli/cli-decode.h" +#include /* readline include files */ #include "readline/tilde.h" @@ -7608,9 +7609,13 @@ set_longjmp_breakpoint_for_call_dummy (void) void check_longjmp_breakpoint_for_call_dummy (struct thread_info *tp) { - for (struct breakpoint *b : all_breakpoints_safe ()) + /* We would need to delete breakpoints other than the current one while + iterating, so all_breakpoints_safe is not sufficient to make that safe. + Save all breakpoints to delete in that set and delete them at the end. */ + std::unordered_set to_delete; + + for (struct breakpoint *b : all_breakpoints ()) { - struct breakpoint *b_tmp = b->next; if (b->type == bp_longjmp_call_dummy && b->thread == tp->global_num) { struct breakpoint *dummy_b = b->related_breakpoint; @@ -7666,15 +7671,17 @@ check_longjmp_breakpoint_for_call_dummy (struct thread_info *tp) dummy_frame_discard (dummy_b->frame_id, tp); - while (b->related_breakpoint != b) - { - if (b_tmp == b->related_breakpoint) - b_tmp = b->related_breakpoint->next; - delete_breakpoint (b->related_breakpoint); - } - delete_breakpoint (b); + for (breakpoint *related_breakpoint = b->related_breakpoint; + related_breakpoint != b; + related_breakpoint = related_breakpoint->related_breakpoint) + to_delete.insert (b->related_breakpoint); + + to_delete.insert (b); } } + + for (breakpoint *b : to_delete) + delete_breakpoint (b); } void