[2/7] gdb: add prepare_reinflate/reinflate around print_frame_args in info_frame_command_core
Message ID | 20221107155310.2590069-2-simon.marchi@polymtl.ca |
---|---|
State | Committed |
Commit | 3dc9dde26d1c279e888d1fd0361f720e5a3721f3 |
Headers |
Return-Path: <gdb-patches-bounces+patchwork=sourceware.org@sourceware.org> X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 35F6238582A7 for <patchwork@sourceware.org>; Mon, 7 Nov 2022 15:53:53 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 35F6238582A7 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1667836433; bh=ovMC7nXYgx9U6aTcrzArMAMZ6wu1yxryaqscgKXFaqE=; h=To:Cc:Subject:Date:In-Reply-To:References:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From:Reply-To:From; b=U5Q0vhK9cDaaL6HlWC84+bgoph05ejbFz6RlTXdcC5TQ0RF8n6H65VNQnLfSr9lWz k6bxqUjlwe0KILbIlDUH4JjTV8y7pf69ofBMfEt+VXZk8/mdC4/W0NDUp5rEAJnXvD 0PQIWHMIxnlU3W9QqSg08e3nxU3jZUHOJbos2i0Q= X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from smtp.polymtl.ca (smtp.polymtl.ca [132.207.4.11]) by sourceware.org (Postfix) with ESMTPS id 69EAF385841B for <gdb-patches@sourceware.org>; Mon, 7 Nov 2022 15:53:17 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org 69EAF385841B Received: from simark.ca (simark.ca [158.69.221.121]) (authenticated bits=0) by smtp.polymtl.ca (8.14.7/8.14.7) with ESMTP id 2A7FrBLV019843 (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Mon, 7 Nov 2022 10:53:16 -0500 DKIM-Filter: OpenDKIM Filter v2.11.0 smtp.polymtl.ca 2A7FrBLV019843 Received: from simark.localdomain (unknown [217.28.27.60]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPSA id 9E8C81E11F; Mon, 7 Nov 2022 10:53:11 -0500 (EST) To: gdb-patches@sourceware.org Cc: Simon Marchi <simon.marchi@polymtl.ca> Subject: [PATCH 2/7] gdb: add prepare_reinflate/reinflate around print_frame_args in info_frame_command_core Date: Mon, 7 Nov 2022 10:53:05 -0500 Message-Id: <20221107155310.2590069-2-simon.marchi@polymtl.ca> X-Mailer: git-send-email 2.38.1 In-Reply-To: <20221107155310.2590069-1-simon.marchi@polymtl.ca> References: <20221107155310.2590069-1-simon.marchi@polymtl.ca> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Poly-FromMTA: (simark.ca [158.69.221.121]) at Mon, 7 Nov 2022 15:53:11 +0000 X-Spam-Status: No, score=-3189.7 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, SPF_HELO_PASS, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gdb-patches mailing list <gdb-patches.sourceware.org> List-Unsubscribe: <https://sourceware.org/mailman/options/gdb-patches>, <mailto:gdb-patches-request@sourceware.org?subject=unsubscribe> List-Archive: <https://sourceware.org/pipermail/gdb-patches/> List-Post: <mailto:gdb-patches@sourceware.org> List-Help: <mailto:gdb-patches-request@sourceware.org?subject=help> List-Subscribe: <https://sourceware.org/mailman/listinfo/gdb-patches>, <mailto:gdb-patches-request@sourceware.org?subject=subscribe> From: Simon Marchi via Gdb-patches <gdb-patches@sourceware.org> Reply-To: Simon Marchi <simon.marchi@polymtl.ca> Errors-To: gdb-patches-bounces+patchwork=sourceware.org@sourceware.org Sender: "Gdb-patches" <gdb-patches-bounces+patchwork=sourceware.org@sourceware.org> |
Series |
[1/7] gdb: clear other.m_cached_id in frame_info_ptr's move ctor
|
|
Commit Message
Simon Marchi
Nov. 7, 2022, 3:53 p.m. UTC
I noticed this crash: $ ./gdb --data-directory=data-directory -nx -q \ testsuite/outputs/gdb.python/pretty-print-call-by-hand/pretty-print-call-by-hand \ -x testsuite/outputs/gdb.python/pretty-print-call-by-hand/pretty-print-call-by-hand.py \ -ex "b g" -ex r (gdb) info frame Stack level 0, frame at 0x7fffffffdd80: rip = 0x555555555160 in g (/home/simark/src/binutils-gdb/gdb/testsuite/gdb.python/pretty-print-call-by-hand.c:41); saved rip = 0x5555555551a3 called by frame at 0x7fffffffdda0 source language c. Arglist at 0x7fffffffdd70, args: mt=mytype is 0x555555556004 "hello world", depth=10 Fatal signal: Segmentation fault This is another case of frame_info being invalidated under a function's feet. The stack trace when the frame_info get invalidated looks like: ... many frames to pretty print the arg, that eventually invalidate the frame_infos ... #35 0x00005568d0a8ab24 in print_frame_arg (fp_opts=..., arg=0x7ffc3216bcb0) at /home/simark/src/binutils-gdb/gdb/stack.c:489 #36 0x00005568d0a8cc75 in print_frame_args (fp_opts=..., func=0x621000233210, frame=..., num=-1, stream=0x60b000000300) at /home/simark/src/binutils-gdb/gdb/stack.c:898 #37 0x00005568d0a9536d in info_frame_command_core (fi=..., selected_frame_p=true) at /home/simark/src/binutils-gdb/gdb/stack.c:1682 print_frame_args knows that print_frame_arg can invalidate frame_info objects, and therefore calls prepare_reinflate/reinflate. However, info_frame_command_core has a separate frame_info_ptr instance (it is passed by value / copy). So info_frame_command_core needs to know that print_frame_args can invalidate frame_info objects, and therefore needs to prepare_reinflate/reinflate as well. Add those calls, and enhance the gdb.python/pretty-print-call-by-hand.exp test to test that command. Change-Id: I9edaae06d62e97ffdb30938d364437737238a960 --- gdb/stack.c | 4 ++++ gdb/testsuite/gdb.python/pretty-print-call-by-hand.exp | 8 ++++++++ 2 files changed, 12 insertions(+)
Comments
On 07/11/2022 16:53, Simon Marchi via Gdb-patches wrote: > I noticed this crash: > > $ ./gdb --data-directory=data-directory -nx -q \ > testsuite/outputs/gdb.python/pretty-print-call-by-hand/pretty-print-call-by-hand \ > -x testsuite/outputs/gdb.python/pretty-print-call-by-hand/pretty-print-call-by-hand.py \ > -ex "b g" -ex r > (gdb) info frame > Stack level 0, frame at 0x7fffffffdd80: > rip = 0x555555555160 in g > (/home/simark/src/binutils-gdb/gdb/testsuite/gdb.python/pretty-print-call-by-hand.c:41); saved rip = 0x5555555551a3 > called by frame at 0x7fffffffdda0 > source language c. > Arglist at 0x7fffffffdd70, args: mt=mytype is 0x555555556004 "hello world", > depth=10 > > Fatal signal: Segmentation fault > > This is another case of frame_info being invalidated under a function's > feet. The stack trace when the frame_info get invalidated looks like: > > ... many frames to pretty print the arg, that eventually invalidate the frame_infos ... > #35 0x00005568d0a8ab24 in print_frame_arg (fp_opts=..., arg=0x7ffc3216bcb0) at /home/simark/src/binutils-gdb/gdb/stack.c:489 > #36 0x00005568d0a8cc75 in print_frame_args (fp_opts=..., func=0x621000233210, frame=..., num=-1, stream=0x60b000000300) > at /home/simark/src/binutils-gdb/gdb/stack.c:898 > #37 0x00005568d0a9536d in info_frame_command_core (fi=..., selected_frame_p=true) at /home/simark/src/binutils-gdb/gdb/stack.c:1682 > > print_frame_args knows that print_frame_arg can invalidate frame_info > objects, and therefore calls prepare_reinflate/reinflate. However, > info_frame_command_core has a separate frame_info_ptr instance (it is > passed by value / copy). So info_frame_command_core needs to know that > print_frame_args can invalidate frame_info objects, and therefore needs > to prepare_reinflate/reinflate as well. Add those calls, and enhance > the gdb.python/pretty-print-call-by-hand.exp test to test that command. Can confirm that the crash is reproducible and that the patch fixes the problem. Sorry for missing it the first time. Makes me wonder if I also missed this in print_frame... Either way: Reviewed-By: Bruno Larsen <blarsen@redhat.com>
On 11/8/22 04:32, Bruno Larsen wrote: > On 07/11/2022 16:53, Simon Marchi via Gdb-patches wrote: >> I noticed this crash: >> >> $ ./gdb --data-directory=data-directory -nx -q \ >> testsuite/outputs/gdb.python/pretty-print-call-by-hand/pretty-print-call-by-hand \ >> -x testsuite/outputs/gdb.python/pretty-print-call-by-hand/pretty-print-call-by-hand.py \ >> -ex "b g" -ex r >> (gdb) info frame >> Stack level 0, frame at 0x7fffffffdd80: >> rip = 0x555555555160 in g >> (/home/simark/src/binutils-gdb/gdb/testsuite/gdb.python/pretty-print-call-by-hand.c:41); saved rip = 0x5555555551a3 >> called by frame at 0x7fffffffdda0 >> source language c. >> Arglist at 0x7fffffffdd70, args: mt=mytype is 0x555555556004 "hello world", >> depth=10 >> >> Fatal signal: Segmentation fault >> >> This is another case of frame_info being invalidated under a function's >> feet. The stack trace when the frame_info get invalidated looks like: >> >> ... many frames to pretty print the arg, that eventually invalidate the frame_infos ... >> #35 0x00005568d0a8ab24 in print_frame_arg (fp_opts=..., arg=0x7ffc3216bcb0) at /home/simark/src/binutils-gdb/gdb/stack.c:489 >> #36 0x00005568d0a8cc75 in print_frame_args (fp_opts=..., func=0x621000233210, frame=..., num=-1, stream=0x60b000000300) >> at /home/simark/src/binutils-gdb/gdb/stack.c:898 >> #37 0x00005568d0a9536d in info_frame_command_core (fi=..., selected_frame_p=true) at /home/simark/src/binutils-gdb/gdb/stack.c:1682 >> >> print_frame_args knows that print_frame_arg can invalidate frame_info >> objects, and therefore calls prepare_reinflate/reinflate. However, >> info_frame_command_core has a separate frame_info_ptr instance (it is >> passed by value / copy). So info_frame_command_core needs to know that >> print_frame_args can invalidate frame_info objects, and therefore needs >> to prepare_reinflate/reinflate as well. Add those calls, and enhance >> the gdb.python/pretty-print-call-by-hand.exp test to test that command. > > Can confirm that the crash is reproducible and that the patch fixes the problem. Sorry for missing it the first time. Makes me wonder if I also missed this in print_frame... Either way: I checked print_frame, it could use prepare_reinflate/reinflate. But the only way for the frame variable to be re-used after the print_frame_args call is if: (funname == NULL || sal.symtab == NULL) If that expression is true, then it's likely that we don't have debug info for the function, so it's not likely that some pretty printer was used. But this shows how the current frame_info_ptr is error-prone: you have two know which functions can, deep down their call tree, reinit the frame cache. And all their callers that have a frame_info_ptr object, recursively, must explicitly do prepare_reinflate / reinflate to protect themselves against their frame_info object being invalidated. It's very easy to forget some spots. I'm currently working on making frame_info_ptr work automatically, meaning it would grab the wrapped frame id automatically on construction, and reinflate the frame automatically if needed in operator-> or operator*. The thing that currently throws a wrench in all that is the "frame view" command, which allows you to create and select frames out of thin air. And these frames are currently not reinflatable. And that prevents using my version of frame_info_ptr in code paths that can process that kind of frame, like print_frame_args. It defeats the purpose of frame_info_ptr, because those are the places that would benefit from that the most. So I'm currently checking if I can improve "frame view" and the support for those user-created frames just enough to make frame_info_ptr support them. > Reviewed-By: Bruno Larsen <blarsen@redhat.com> Thanks, Simon
>>>>> "Simon" == Simon Marchi via Gdb-patches <gdb-patches@sourceware.org> writes:
Simon> But this shows how the current frame_info_ptr is error-prone: you have
Simon> two know which functions can, deep down their call tree, reinit the
Simon> frame cache. And all their callers that have a frame_info_ptr object,
Simon> recursively, must explicitly do prepare_reinflate / reinflate to protect
Simon> themselves against their frame_info object being invalidated. It's very
Simon> easy to forget some spots.
Yeah. This problem already existed, and the rationale behind
frame_info_ptr wasn't to fix it, but rather to expose it when it happens
-- by crashing rather than allowing a UAF.
Simon> I'm currently working on making frame_info_ptr work automatically,
Simon> meaning it would grab the wrapped frame id automatically on
Simon> construction, and reinflate the frame automatically if needed
We tried this a bit, but the problem we hit was that computing the
frame id require unwinding a bit, and since the code generally uses
frame_info_ptr everywhere, gdb would end up unwinding everything.
If this can be overcome then that would be great.
Tom
On 11/8/22 14:39, Tom Tromey wrote: >>>>>> "Simon" == Simon Marchi via Gdb-patches <gdb-patches@sourceware.org> writes: > > Simon> But this shows how the current frame_info_ptr is error-prone: you have > Simon> two know which functions can, deep down their call tree, reinit the > Simon> frame cache. And all their callers that have a frame_info_ptr object, > Simon> recursively, must explicitly do prepare_reinflate / reinflate to protect > Simon> themselves against their frame_info object being invalidated. It's very > Simon> easy to forget some spots. > > Yeah. This problem already existed, and the rationale behind > frame_info_ptr wasn't to fix it, but rather to expose it when it happens > -- by crashing rather than allowing a UAF. > > Simon> I'm currently working on making frame_info_ptr work automatically, > Simon> meaning it would grab the wrapped frame id automatically on > Simon> construction, and reinflate the frame automatically if needed > > We tried this a bit, but the problem we hit was that computing the > frame id require unwinding a bit, and since the code generally uses > frame_info_ptr everywhere, gdb would end up unwinding everything. With the final patch of this series ("gdb: add special handling for frame level 0 in frame_info_ptr"), frame_info_ptr won't compute frame 0's frame id in order to prepare_reinflate or reinflate, so won't cause extra unwinding. For other frames, the frame id is already computed when the frame is created, so frame_info_ptr won't cause any more unwinding that already happens by creating the frame_info objects. With my prototype that grabs the frame id in frame_info_ptr's constructor (except for frame 0), it requires that for the frame id to be computed already. This means that the code paths involved in computing the frame id wouldn't use frame_info_ptr anymore. So, for instance, I would revert frame_this_id_ftype to take a plain `frame_info *` (and that leads to pretty much the whole struct frame_unwind API to go back to `frame_info *`. The point being that as long as a frame doesn't have a computed id, it is not reinflatable anyway. And this is not really the kind of spot that is at risk of using a stale frame_info. However, if we really want to keep using frame_info_ptr everywhere (even in things like frame_this_id_ftype, just to be extra safe), then perhaps we can have two classes. One that would provide the protection against stale frame_info objects, which could be used everywhere, and one that would do that + automatic reinflation. Simon
diff --git a/gdb/stack.c b/gdb/stack.c index 653251c200b4..4e2342c2a8d9 100644 --- a/gdb/stack.c +++ b/gdb/stack.c @@ -1679,8 +1679,12 @@ info_frame_command_core (frame_info_ptr fi, bool selected_frame_p) else gdb_printf (" %d args: ", numargs); } + + fi.prepare_reinflate (); print_frame_args (user_frame_print_options, func, fi, numargs, gdb_stdout); + fi.reinflate (); + gdb_puts ("\n"); } } diff --git a/gdb/testsuite/gdb.python/pretty-print-call-by-hand.exp b/gdb/testsuite/gdb.python/pretty-print-call-by-hand.exp index 0aeb2218f911..eb3fc9e35faf 100644 --- a/gdb/testsuite/gdb.python/pretty-print-call-by-hand.exp +++ b/gdb/testsuite/gdb.python/pretty-print-call-by-hand.exp @@ -98,6 +98,14 @@ with_test_prefix "frame print" { "backtrace test" } } + +# Test the "info frame" command +with_test_prefix "info frame" { + if { [start_test "TAG: first frame"] == 0 } { + gdb_test "info frame" "mytype is $hex \"hello world\".*" + } +} + # Testing the down command. with_test_prefix "frame movement down" { if { [start_test "TAG: first frame"] == 0 } {