https://github.com/qiyao updated https://github.com/llvm/llvm-project/pull/213055
>From a950c8cb0ddad82702475aa89049bf8b8669e900 Mon Sep 17 00:00:00 2001 From: Yao Qi <[email protected]> Date: Sat, 20 Jun 2026 16:39:25 +0100 Subject: [PATCH 1/3] [lldb][DIL] Validate bitfield extraction ranges The DIL bitfield extraction operator `base[high:low]` creates a synthetic bitfield child without validating the requested range. Three malformed ranges reach the data layer and either return nonsense or crash. Reproduced with a 32-bit `int value` and DIL enabled: ``` (lldb) settings set target.experimental.use-DIL true (lldb) frame variable 'value[-1:0]' (int:2) value[-1:0] = 2 ``` A negative index is accepted and produces a meaningless child. `first_index`/`last_index` are signed `int64_t`, but `GetSyntheticBitFieldChild` takes `uint32_t`, so `-1` silently wraps to a huge unsigned offset. ``` (lldb) frame variable 'value[0:64]' Assertion failed: (bitfield_bit_size <= 64), function GetMaxU64Bitfield, file DataExtractor.cpp, line 580. ``` A width greater than 64 bits aborts. `DataExtractor::GetMaxU64Bitfield` only supports up to 64 bits: it asserts in an assertions build and otherwise performs an out-of-bounds shift. A 32-bit `value` with range `[0:64]` is 65 bits, enough to trip it. ``` (lldb) frame variable 'value[100:50]' (int:51) value[100:50] = 0 ``` A high index past the base object's storage returns a garbage child in a normal build. Under UBSan the read/format path shifts by an oversized amount derived from the offset: ``` (lldb) frame variable 'value[100:50]' DataExtractor.cpp:591:12: runtime error: shift exponent 234 is too large for 64-bit type 'uint64_t' ``` Reject all three in the DIL evaluator before the synthetic child is created: a negative `first_index`/`last_index`, a normalized width greater than 64 bits, and a high index at or beyond the base object's bit size (queried with `GetCompilerType().GetBitSize`). Each returns a `DILDiagnosticError` with a clear message. Valid in-range extractions are unaffected. Adds the three malformed ranges to the DIL bitfield extraction API test (`TestFrameVarDILBitFieldExtraction`). Without the fix the test fails on the first case (`value[-1:0]` is expected to error but succeeds); the `[0:64]` case additionally asserts and the `[100:50]` case is a UBSan shift-out-of-bounds. --- lldb/source/ValueObject/DILEval.cpp | 38 +++++++++++++++++++ .../TestFrameVarDILBitFieldExtraction.py | 34 +++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp index 4c5ac96dccf74..7e443419bee2d 100644 --- a/lldb/source/ValueObject/DILEval.cpp +++ b/lldb/source/ValueObject/DILEval.cpp @@ -1277,14 +1277,52 @@ Interpreter::Visit(const BitFieldExtractionNode &node) { return llvm::make_error<DILDiagnosticError>( m_expr, "could not get the index as an integer", node.GetLocation()); + if (first_index < 0 || last_index < 0) { + std::string message = + llvm::formatv("bitfield range {0}:{1} is not valid (negative index)", + first_index, last_index); + return llvm::make_error<DILDiagnosticError>(m_expr, message, + node.GetLocation()); + } + // if the format given is [high-low], swap range if (first_index > last_index) std::swap(first_index, last_index); + // The underlying DataExtractor bitfield accessors only support extracting up + // to 64 bits at once (GetMaxU64Bitfield asserts bitfield_bit_size <= 64 and + // would otherwise perform an out-of-bounds shift). Reject oversized ranges + // here instead of crashing deep in the data layer. + if (last_index - first_index + 1 > 64) { + std::string message = + llvm::formatv("bitfield range {0}:{1} is not valid (more than 64 bits)", + first_index, last_index); + return llvm::make_error<DILDiagnosticError>(m_expr, message, + node.GetLocation()); + } + auto base_or_err = EvaluateAndDereference(node.GetBase()); if (!base_or_err) return base_or_err; lldb::ValueObjectSP base = *base_or_err; + + // The bitfield range must lie within the storage of the base object. A + // bit offset/size that extends past the base's bit size leads to an + // out-of-bounds shift when the value is later read or formatted (e.g. in + // DataExtractor::GetMaxU64Bitfield via DumpDataExtractor). + llvm::Expected<uint64_t> base_bit_size = + base->GetCompilerType().GetBitSize(&m_stack_frame); + if (!base_bit_size) + return base_bit_size.takeError(); + if (static_cast<uint64_t>(last_index) >= *base_bit_size) { + std::string message = llvm::formatv( + "bitfield range {0}:{1} is not valid for \"({2}) {3}\"", first_index, + last_index, base->GetTypeName().AsCString("<invalid type>"), + base->GetName().GetStringRef()); + return llvm::make_error<DILDiagnosticError>(m_expr, message, + node.GetLocation()); + } + lldb::ValueObjectSP child_valobj_sp = base->GetSyntheticBitFieldChild(first_index, last_index, true); if (!child_valobj_sp) { diff --git a/lldb/test/API/commands/frame/var-dil/basics/BitFieldExtraction/TestFrameVarDILBitFieldExtraction.py b/lldb/test/API/commands/frame/var-dil/basics/BitFieldExtraction/TestFrameVarDILBitFieldExtraction.py index 026658c0cd25f..11dce1e494ead 100644 --- a/lldb/test/API/commands/frame/var-dil/basics/BitFieldExtraction/TestFrameVarDILBitFieldExtraction.py +++ b/lldb/test/API/commands/frame/var-dil/basics/BitFieldExtraction/TestFrameVarDILBitFieldExtraction.py @@ -66,3 +66,37 @@ def test_bitfield_extraction(self): error=True, substrs=["bit index is not an integer"], ) + + # A negative bit index must be rejected with a clear error instead of + # silently wrapping to a huge uint32_t at the GetSyntheticBitFieldChild + # call site. + self.expect( + "frame var 'value[-1:0]'", + error=True, + substrs=["bitfield range -1:0 is not valid (negative index)"], + ) + self.expect( + "frame var 'value[0:-1]'", + error=True, + substrs=["bitfield range 0:-1 is not valid (negative index)"], + ) + + # A bitfield wider than 64 bits must be rejected. The underlying + # DataExtractor::GetMaxU64Bitfield only supports up to 64 bits + # (it asserts and otherwise performs an out-of-bounds shift). + self.expect( + "frame var 'value[0:64]'", + error=True, + substrs=["bitfield range 0:64 is not valid (more than 64 bits)"], + ) + + # A bitfield whose high index is past the base object's storage must be + # rejected. Otherwise reading/formatting the synthetic child performs an + # out-of-bounds shift in DataExtractor::GetMaxU64Bitfield. 'value' is a + # 32-bit int, so bit index 50 is out of range. The range is normalized + # (high:low swapped) before the message is built. + self.expect( + "frame var 'value[100:50]'", + error=True, + substrs=["bitfield range 50:100 is not valid"], + ) >From 4dc78c1650ca4fc9a9ee5a05dc33cb06a76aca92 Mon Sep 17 00:00:00 2001 From: Yao Qi <[email protected]> Date: Thu, 30 Jul 2026 17:02:17 +0100 Subject: [PATCH 2/3] Simplify comments --- lldb/source/ValueObject/DILEval.cpp | 17 ++++++++-------- .../TestFrameVarDILBitFieldExtraction.py | 20 +++++++++---------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp index 7e443419bee2d..d2108cdca11a4 100644 --- a/lldb/source/ValueObject/DILEval.cpp +++ b/lldb/source/ValueObject/DILEval.cpp @@ -1277,6 +1277,9 @@ Interpreter::Visit(const BitFieldExtractionNode &node) { return llvm::make_error<DILDiagnosticError>( m_expr, "could not get the index as an integer", node.GetLocation()); + // Reject negative indices before the swap below, so the diagnostic reports + // the range as the user wrote it. A negative index would also wrap to a huge + // offset in the uint32_t GetSyntheticBitFieldChild call below. if (first_index < 0 || last_index < 0) { std::string message = llvm::formatv("bitfield range {0}:{1} is not valid (negative index)", @@ -1289,10 +1292,9 @@ Interpreter::Visit(const BitFieldExtractionNode &node) { if (first_index > last_index) std::swap(first_index, last_index); - // The underlying DataExtractor bitfield accessors only support extracting up - // to 64 bits at once (GetMaxU64Bitfield asserts bitfield_bit_size <= 64 and - // would otherwise perform an out-of-bounds shift). Reject oversized ranges - // here instead of crashing deep in the data layer. + // GetMaxU64Bitfield in the data layer only supports up to 64 bits (it asserts + // bitfield_bit_size <= 64 and otherwise shifts out of bounds), so reject a + // wider range here. if (last_index - first_index + 1 > 64) { std::string message = llvm::formatv("bitfield range {0}:{1} is not valid (more than 64 bits)", @@ -1306,10 +1308,9 @@ Interpreter::Visit(const BitFieldExtractionNode &node) { return base_or_err; lldb::ValueObjectSP base = *base_or_err; - // The bitfield range must lie within the storage of the base object. A - // bit offset/size that extends past the base's bit size leads to an - // out-of-bounds shift when the value is later read or formatted (e.g. in - // DataExtractor::GetMaxU64Bitfield via DumpDataExtractor). + // The high index must lie within the base object's storage; a bit index past + // its bit size shifts out of bounds when the child is later read or formatted + // (GetMaxU64Bitfield). llvm::Expected<uint64_t> base_bit_size = base->GetCompilerType().GetBitSize(&m_stack_frame); if (!base_bit_size) diff --git a/lldb/test/API/commands/frame/var-dil/basics/BitFieldExtraction/TestFrameVarDILBitFieldExtraction.py b/lldb/test/API/commands/frame/var-dil/basics/BitFieldExtraction/TestFrameVarDILBitFieldExtraction.py index 11dce1e494ead..088b753172e09 100644 --- a/lldb/test/API/commands/frame/var-dil/basics/BitFieldExtraction/TestFrameVarDILBitFieldExtraction.py +++ b/lldb/test/API/commands/frame/var-dil/basics/BitFieldExtraction/TestFrameVarDILBitFieldExtraction.py @@ -67,9 +67,8 @@ def test_bitfield_extraction(self): substrs=["bit index is not an integer"], ) - # A negative bit index must be rejected with a clear error instead of - # silently wrapping to a huge uint32_t at the GetSyntheticBitFieldChild - # call site. + # A negative bit index must be rejected instead of wrapping to a huge + # uint32_t at the GetSyntheticBitFieldChild call site. self.expect( "frame var 'value[-1:0]'", error=True, @@ -81,20 +80,19 @@ def test_bitfield_extraction(self): substrs=["bitfield range 0:-1 is not valid (negative index)"], ) - # A bitfield wider than 64 bits must be rejected. The underlying - # DataExtractor::GetMaxU64Bitfield only supports up to 64 bits - # (it asserts and otherwise performs an out-of-bounds shift). + # A range wider than 64 bits must be rejected: DataExtractor's + # GetMaxU64Bitfield only supports up to 64 bits (it asserts and + # otherwise shifts out of bounds). self.expect( "frame var 'value[0:64]'", error=True, substrs=["bitfield range 0:64 is not valid (more than 64 bits)"], ) - # A bitfield whose high index is past the base object's storage must be - # rejected. Otherwise reading/formatting the synthetic child performs an - # out-of-bounds shift in DataExtractor::GetMaxU64Bitfield. 'value' is a - # 32-bit int, so bit index 50 is out of range. The range is normalized - # (high:low swapped) before the message is built. + # A high index past the base object's storage must be rejected; + # otherwise reading the synthetic child shifts out of bounds in + # GetMaxU64Bitfield. 'value' is a 32-bit int, so bit 50 is out of range. + # The range is normalized (high:low swapped) before the message. self.expect( "frame var 'value[100:50]'", error=True, >From 7f34548bbe630a0ebdd6c987d54ce008c19b6094 Mon Sep 17 00:00:00 2001 From: Yao Qi <[email protected]> Date: Mon, 3 Aug 2026 16:05:45 +0100 Subject: [PATCH 3/3] fixup: avoid +1 in signed arithmetic --- lldb/source/ValueObject/DILEval.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp index d2108cdca11a4..b2e76fd317b7f 100644 --- a/lldb/source/ValueObject/DILEval.cpp +++ b/lldb/source/ValueObject/DILEval.cpp @@ -1295,7 +1295,7 @@ Interpreter::Visit(const BitFieldExtractionNode &node) { // GetMaxU64Bitfield in the data layer only supports up to 64 bits (it asserts // bitfield_bit_size <= 64 and otherwise shifts out of bounds), so reject a // wider range here. - if (last_index - first_index + 1 > 64) { + if (last_index - first_index >= 64) { std::string message = llvm::formatv("bitfield range {0}:{1} is not valid (more than 64 bits)", first_index, last_index); _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
