https://github.com/qiyao created
https://github.com/llvm/llvm-project/pull/219217
`target.process.memory-cache-line-size` is an unbounded `UInt64`, so
`settings set` accepts 0, and `MemoryCache` keeps the value in a
`uint32_t`, so it also accepts any multiple of 2^32, which truncates to
0. Every consumer then takes a remainder by 0.
`Process::ReadCStringFromMemory` divides on the first iteration of its
loop, before it touches inferior memory, so any address reproduces it.
On an x86_64 host that is a `SIGFPE` and lldb dies; on AArch64 `udiv` by
zero yields 0, so `addr % 0` evaluates to `addr` and the subtraction
underflows to a huge chunk size, and the bug hides.
```
$ lldb -b \
-o 'settings set target.process.memory-cache-line-size 0' \
-o 'target create --core linux-x86_64.core'
Floating point exception: 8
```
Under UBSan on any host, with
`lldb/test/API/tools/lldb-dap/coreFile/linux-x86_64.core`:
```
lldb/source/Target/Process.cpp:2388:40: runtime error: division by zero
#0 lldb_private::Process::ReadCStringFromMemory
#2 ProcessElfCore::GetMainExecutableModuleSpec
#3 ProcessElfCore::DoLoadCore
#4 lldb_private::Process::LoadCore
```
The truncating case crashes the same way while `settings show` reports a
value that is not 0, so nothing in the UI hints at the cause:
```
(lldb) settings set target.process.memory-cache-line-size 4294967296
(lldb) settings show target.process.memory-cache-line-size
target.process.memory-cache-line-size (unsigned) = 4294967296
```
The cache uses 0. 4294967297 truncates to 1, so the cache uses a
one-byte line.
Bound the property once instead of at each use, from 1 to `UINT32_MAX`,
the way `Debugger` bounds `term-width` and `term-height`. Rejecting
beats clamping, because `settings show` reads the stored value back and
would otherwise report a number the cache does not use:
```
(lldb) settings set target.process.memory-cache-line-size 4294967296
error: 4294967296 is out of range, valid values must be between 1 and
4294967295.
```
`OptionValueProperties::CreateLocalCopy` deep-copies the option values,
so the bounds also apply to each process's own collection, not just the
global one.
>From 2b56aaa4da9faef83c0dcf3ad5b6ccd9c41076a2 Mon Sep 17 00:00:00 2001
From: Yao Qi <[email protected]>
Date: Mon, 24 Aug 2026 23:20:20 +0100
Subject: [PATCH] [lldb] Reject an unusable memory-cache-line-size
`target.process.memory-cache-line-size` is an unbounded `UInt64`, so
`settings set` accepts 0, and `MemoryCache` keeps the value in a
`uint32_t`, so it also accepts any multiple of 2^32, which truncates to
0. Every consumer then takes a remainder by 0.
`Process::ReadCStringFromMemory` divides on the first iteration of its
loop, before it touches inferior memory, so any address reproduces it.
On an x86_64 host that is a `SIGFPE` and lldb dies; on AArch64 `udiv` by
zero yields 0, so `addr % 0` evaluates to `addr` and the subtraction
underflows to a huge chunk size, and the bug hides.
```
$ lldb -b \
-o 'settings set target.process.memory-cache-line-size 0' \
-o 'target create --core linux-x86_64.core'
Floating point exception: 8
```
Under UBSan on any host, with
`lldb/test/API/tools/lldb-dap/coreFile/linux-x86_64.core`:
```
lldb/source/Target/Process.cpp:2388:40: runtime error: division by zero
#0 lldb_private::Process::ReadCStringFromMemory
#2 ProcessElfCore::GetMainExecutableModuleSpec
#3 ProcessElfCore::DoLoadCore
#4 lldb_private::Process::LoadCore
```
The truncating case crashes the same way while `settings show` reports a
value that is not 0, so nothing in the UI hints at the cause:
```
(lldb) settings set target.process.memory-cache-line-size 4294967296
(lldb) settings show target.process.memory-cache-line-size
target.process.memory-cache-line-size (unsigned) = 4294967296
```
The cache uses 0. 4294967297 truncates to 1, so the cache uses a
one-byte line.
Bound the property once instead of at each use, from 1 to `UINT32_MAX`,
the way `Debugger` bounds `term-width` and `term-height`. Rejecting
beats clamping, because `settings show` reads the stored value back and
would otherwise report a number the cache does not use:
```
(lldb) settings set target.process.memory-cache-line-size 4294967296
error: 4294967296 is out of range, valid values must be between 1 and
4294967295.
```
`OptionValueProperties::CreateLocalCopy` deep-copies the option values,
so the bounds also apply to each process's own collection, not just the
global one.
---
lldb/source/Target/Process.cpp | 8 ++++++
lldb/unittests/Target/MemoryTest.cpp | 39 ++++++++++++++++++++++++++++
2 files changed, 47 insertions(+)
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index fdc56e1c310eb..033638f680934 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -38,6 +38,7 @@
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Interpreter/OptionArgParser.h"
#include "lldb/Interpreter/OptionValueProperties.h"
+#include "lldb/Interpreter/OptionValueUInt64.h"
#include "lldb/Symbol/Function.h"
#include "lldb/Symbol/Symbol.h"
#include "lldb/Target/ABI.h"
@@ -173,6 +174,13 @@ ProcessProperties::ProcessProperties(lldb_private::Process
*process)
// Global process properties, set them up one time
m_collection_sp =
std::make_shared<ProcessOptionValueProperties>("process");
m_collection_sp->Initialize(g_process_properties_def);
+ // MemoryCache divides by the cache line size and holds it in a uint32_t,
so
+ // reject a value it could not use.
+ OptionValueUInt64 *line_size =
+ m_collection_sp->GetPropertyAtIndexAsOptionValueUInt64(
+ ePropertyMemCacheLineSize);
+ line_size->SetMinimumValue(1);
+ line_size->SetMaximumValue(UINT32_MAX);
m_collection_sp->AppendProperty(
"thread", "Settings specific to threads.", true,
Thread::GetGlobalProperties().GetValueProperties());
diff --git a/lldb/unittests/Target/MemoryTest.cpp
b/lldb/unittests/Target/MemoryTest.cpp
index 73f17ca4ce122..97402c4cb6e03 100644
--- a/lldb/unittests/Target/MemoryTest.cpp
+++ b/lldb/unittests/Target/MemoryTest.cpp
@@ -445,6 +445,45 @@ TEST_F(MemoryTest, TestReadStopsAtAnInvalidRange) {
EXPECT_TRUE(inside_error.Fail());
}
+TEST_F(MemoryTest, TestUnusableCacheLineSize) {
+ ArchSpec arch("arm64-apple-macosx");
+
+ Platform::SetHostPlatform(PlatformRemoteMacOSX::CreateInstance(true, &arch));
+
+ DebuggerSP debugger_sp = Debugger::CreateInstance();
+ ASSERT_TRUE(debugger_sp);
+
+ // A Process copies the global properties when it is constructed, so the
+ // setting must be in place before CreateProcess, and put back afterwards.
+ struct SettingGuard {
+ ~SettingGuard() {
+ Process::GetGlobalProperties().SetPropertyValue(
+ nullptr, eVarSetOperationClear, "memory-cache-line-size", "");
+ }
+ } restore_setting;
+
+ auto set_line_size = [](const char *setting) {
+ return Process::GetGlobalProperties().SetPropertyValue(
+ nullptr, eVarSetOperationAssign, "memory-cache-line-size", setting);
+ };
+
+ // A usable setting must take effect, or the checks below prove nothing.
+ ASSERT_TRUE(set_line_size("256").Success());
+ TargetSP target_sp = CreateTarget(debugger_sp, arch);
+ DummyProcess *process =
+ static_cast<DummyProcess *>(CreateProcess(target_sp).get());
+ EXPECT_EQ(process->GetMemoryCacheLineSize(), 256u);
+
+ for (const char *setting : {"0", "4294967296"}) {
+ SCOPED_TRACE(setting);
+ EXPECT_TRUE(set_line_size(setting).Fail());
+ // Refused, so the last usable value is still in effect.
+ EXPECT_EQ(process->GetMemoryCacheLineSize(), 256u);
+ TargetSP later_target_sp = CreateTarget(debugger_sp, arch);
+ EXPECT_EQ(CreateProcess(later_target_sp)->GetMemoryCacheLineSize(), 256u);
+ }
+}
+
TEST_F(MemoryTest, TestReadInteger) {
ArchSpec arch("x86_64-apple-macosx-");
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits