Skip to content

Commit 3a3d1bf

Browse files
[lldb][AArch64] Handle core file tag segments missing tag data (#145338)
In the same way that memory regions may be known from a core file but not readable, tag segments can also have no content. For example: ``` $ readelf --segments core <...> Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align <...> LOAD 0x0000000000002000 0x0000ffff93899000 0x0000000000000000 0x0000000000000000 0x0000000000001000 RW 0x1000 <...> LOPROC+0x2 0x0000000000008000 0x0000ffff93899000 0x0000000000000000 0x0000000000000000 0x0000000000001000 0x0 ``` This happens if you have a restricted coredump filter or size limit. The area of virtual memory this segment covers is 0x1000, or 4096 bytes aka one tagged page. It's FileSiz would normally be 0x80. Tags are packed 2 per byte and granules are 16 bytes. 4096 / 16 / 2 = 128 or 0x80. But here it has no data, and in theory a corrupt file might have some data but not all. This triggered an assert in UnpackTagsFromCoreFileSegment and crashed lldb. To fix this I have made UnpackTagsFromCoreFileSegment return an expected and returned an error in this case instead of asserting. This will be seen by the user, as shown in the added API test.
1 parent 97b8cec commit 3a3d1bf

File tree

7 files changed

+86
-19
lines changed

7 files changed

+86
-19
lines changed

lldb/include/lldb/Target/MemoryTagManager.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,15 @@ class MemoryTagManager {
122122
//
123123
// 'reader' will always be a wrapper around a CoreFile in real use
124124
// but allows testing without having to mock a CoreFile.
125+
//
126+
// This call will fail in the case that the core file segment does not contain
127+
// enough data to read all the tags.
125128
typedef std::function<size_t(lldb::offset_t, size_t, void *)> CoreReaderFn;
126-
std::vector<lldb::addr_t> virtual UnpackTagsFromCoreFileSegment(
127-
CoreReaderFn reader, lldb::addr_t tag_segment_virtual_address,
128-
lldb::addr_t tag_segment_data_address, lldb::addr_t addr,
129-
size_t len) const = 0;
129+
llvm::
130+
Expected<std::vector<lldb::addr_t>> virtual UnpackTagsFromCoreFileSegment(
131+
CoreReaderFn reader, lldb::addr_t tag_segment_virtual_address,
132+
lldb::addr_t tag_segment_data_address, lldb::addr_t addr,
133+
size_t len) const = 0;
130134

131135
// Pack uncompressed tags into their storage format (e.g. for gdb QMemTags).
132136
// Checks that each tag is within the expected value range.

lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ MemoryTagManagerAArch64MTE::UnpackTagsData(const std::vector<uint8_t> &tags,
247247
return unpacked;
248248
}
249249

250-
std::vector<lldb::addr_t>
250+
llvm::Expected<std::vector<lldb::addr_t>>
251251
MemoryTagManagerAArch64MTE::UnpackTagsFromCoreFileSegment(
252252
CoreReaderFn reader, lldb::addr_t tag_segment_virtual_address,
253253
lldb::addr_t tag_segment_data_address, lldb::addr_t addr,
@@ -290,8 +290,12 @@ MemoryTagManagerAArch64MTE::UnpackTagsFromCoreFileSegment(
290290
const size_t bytes_copied =
291291
reader(tag_segment_data_address + file_offset_in_bytes, tag_bytes_to_read,
292292
tag_data.data());
293-
UNUSED_IF_ASSERT_DISABLED(bytes_copied);
294-
assert(bytes_copied == tag_bytes_to_read);
293+
if (bytes_copied != tag_bytes_to_read) {
294+
return llvm::createStringError(
295+
llvm::inconvertibleErrorCode(),
296+
"Could not read tags from core file segment. Segment "
297+
"is missing some or all tag data.");
298+
}
295299

296300
std::vector<lldb::addr_t> tags;
297301
tags.reserve(2 * tag_data.size());

lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class MemoryTagManagerAArch64MTE : public MemoryTagManager {
4444
UnpackTagsData(const std::vector<uint8_t> &tags,
4545
size_t granules = 0) const override;
4646

47-
std::vector<lldb::addr_t>
47+
llvm::Expected<std::vector<lldb::addr_t>>
4848
UnpackTagsFromCoreFileSegment(CoreReaderFn reader,
4949
lldb::addr_t tag_segment_virtual_address,
5050
lldb::addr_t tag_segment_data_address,

lldb/test/API/linux/aarch64/mte_core_file/TestAArch64LinuxMTEMemoryTagCoreFile.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,3 +248,26 @@ def test_mte_ctrl_register(self):
248248
"TCF: 0 = TCF_NONE, 1 = TCF_SYNC, 2 = TCF_ASYNC, 3 = TCF_ASYMM"
249249
],
250250
)
251+
252+
@skipIfLLVMTargetMissing("AArch64")
253+
def test_mte_no_tags(self):
254+
"""Test that we handle there being a tag segment but that segment does
255+
not contain any tag data. This can happen when the core is dumped
256+
with a restrictive limit or filter."""
257+
self.runCmd("target create --core core.mte.notags")
258+
259+
mte_buf_addr = 0xFFFFA4AF3000
260+
261+
# We can see which memory was tagged.
262+
self.expect(
263+
f"memory region {mte_buf_addr}", substrs=["memory tagging: enabled"]
264+
)
265+
266+
# We cannot read those tags.
267+
self.expect(
268+
f"memory tag read {mte_buf_addr}",
269+
substrs=[
270+
"Could not read tags from core file segment. Segment is missing some or all tag data."
271+
],
272+
error=True,
273+
)
Binary file not shown.

lldb/test/API/linux/aarch64/mte_core_file/main.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@
55
//
66
// Compile with:
77
// <gcc or clang> -march=armv8.5-a+memtag -g main.c -o a.out.mte
8+
// (use a.out.mte to generate core.mte.notags and core.mte)
89
// <gcc or clang> -march=armv8.5-a+memtag -g main.c -DNO_MTE -o a.out.nomte
910
//
10-
// /proc/self/coredump_filter was set to 2 when the core files were made.
11+
// Set /proc/self/coredump_filter to the following values when generating the
12+
// core files:
13+
// * core.mte - 3
14+
// * core.mte.notags - 2
15+
// * core.nomte - 3
1116

1217
#include <arm_acle.h>
1318
#include <asm/mman.h>

lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -87,63 +87,94 @@ TEST(MemoryTagManagerAArch64MTETest, UnpackTagsFromCoreFileSegment) {
8787
std::vector<uint8_t> tags_data;
8888
MemoryTagManager::CoreReaderFn reader =
8989
[&tags_data](lldb::offset_t offset, size_t length, void *dst) {
90+
if ((offset + length) >= tags_data.size())
91+
length = tags_data.size() - offset;
92+
9093
std::memcpy(dst, tags_data.data() + offset, length);
9194
return length;
9295
};
9396

9497
// Zero length is ok.
95-
std::vector<lldb::addr_t> tags =
98+
llvm::Expected<std::vector<lldb::addr_t>> tags =
9699
manager.UnpackTagsFromCoreFileSegment(reader, 0, 0, 0, 0);
97-
ASSERT_EQ(tags.size(), (size_t)0);
100+
ASSERT_THAT_EXPECTED(tags, llvm::Succeeded());
101+
ASSERT_EQ(tags->size(), (size_t)0);
98102

99103
// In the simplest case we read 2 tags which are in the same byte.
100104
tags_data.push_back(0x21);
101105
// The least significant bits are the first tag in memory.
102106
std::vector<lldb::addr_t> expected{1, 2};
103107
tags = manager.UnpackTagsFromCoreFileSegment(reader, 0, 0, 0, 32);
104-
ASSERT_THAT(expected, testing::ContainerEq(tags));
108+
ASSERT_THAT_EXPECTED(tags, llvm::Succeeded());
109+
ASSERT_THAT(expected, testing::ContainerEq(*tags));
105110

106111
// If we read just one then it will have to trim off the second one.
107112
expected = std::vector<lldb::addr_t>{1};
108113
tags = manager.UnpackTagsFromCoreFileSegment(reader, 0, 0, 0, 16);
109-
ASSERT_THAT(expected, testing::ContainerEq(tags));
114+
ASSERT_THAT_EXPECTED(tags, llvm::Succeeded());
115+
ASSERT_THAT(expected, testing::ContainerEq(*tags));
110116

111117
// If we read the second tag only then the first one must be trimmed.
112118
expected = std::vector<lldb::addr_t>{2};
113119
tags = manager.UnpackTagsFromCoreFileSegment(reader, 0, 0, 16, 16);
114-
ASSERT_THAT(expected, testing::ContainerEq(tags));
120+
ASSERT_THAT_EXPECTED(tags, llvm::Succeeded());
121+
ASSERT_THAT(expected, testing::ContainerEq(*tags));
115122

116123
// This trimming logic applies if you read a larger set of tags.
117124
tags_data = std::vector<uint8_t>{0x21, 0x43, 0x65, 0x87};
118125

119126
// Trailing tag should be trimmed.
120127
expected = std::vector<lldb::addr_t>{1, 2, 3};
121128
tags = manager.UnpackTagsFromCoreFileSegment(reader, 0, 0, 0, 48);
122-
ASSERT_THAT(expected, testing::ContainerEq(tags));
129+
ASSERT_THAT_EXPECTED(tags, llvm::Succeeded());
130+
ASSERT_THAT(expected, testing::ContainerEq(*tags));
123131

124132
// Leading tag should be trimmed.
125133
expected = std::vector<lldb::addr_t>{2, 3, 4};
126134
tags = manager.UnpackTagsFromCoreFileSegment(reader, 0, 0, 16, 48);
127-
ASSERT_THAT(expected, testing::ContainerEq(tags));
135+
ASSERT_THAT_EXPECTED(tags, llvm::Succeeded());
136+
ASSERT_THAT(expected, testing::ContainerEq(*tags));
128137

129138
// Leading and trailing trimmmed.
130139
expected = std::vector<lldb::addr_t>{2, 3, 4, 5};
131140
tags = manager.UnpackTagsFromCoreFileSegment(reader, 0, 0, 16, 64);
132-
ASSERT_THAT(expected, testing::ContainerEq(tags));
141+
ASSERT_THAT_EXPECTED(tags, llvm::Succeeded());
142+
ASSERT_THAT(expected, testing::ContainerEq(*tags));
133143

134144
// The address given is an offset into the whole file so the address requested
135145
// from the reader should be beyond that.
136146
tags_data = std::vector<uint8_t>{0xFF, 0xFF, 0x21, 0x43, 0x65, 0x87};
137147
expected = std::vector<lldb::addr_t>{1, 2};
138148
tags = manager.UnpackTagsFromCoreFileSegment(reader, 0, 2, 0, 32);
139-
ASSERT_THAT(expected, testing::ContainerEq(tags));
149+
ASSERT_THAT_EXPECTED(tags, llvm::Succeeded());
150+
ASSERT_THAT(expected, testing::ContainerEq(*tags));
140151

141152
// addr is a virtual address that we expect to be >= the tag segment's
142153
// starting virtual address. So again an offset must be made from the
143154
// difference.
144155
expected = std::vector<lldb::addr_t>{3, 4};
145156
tags = manager.UnpackTagsFromCoreFileSegment(reader, 32, 2, 64, 32);
146-
ASSERT_THAT(expected, testing::ContainerEq(tags));
157+
ASSERT_THAT_EXPECTED(tags, llvm::Succeeded());
158+
ASSERT_THAT(expected, testing::ContainerEq(*tags));
159+
160+
// Error when there is not enough data to decode tags.
161+
162+
// Read 1 tag from an offset just outside the segment's data.
163+
tags_data = {0xAB};
164+
tags = manager.UnpackTagsFromCoreFileSegment(reader, 0, 0, 32, 16);
165+
const char *expected_err = "Could not read tags from core file segment. "
166+
"Segment is missing some or all tag data.";
167+
EXPECT_THAT_EXPECTED(tags, llvm::FailedWithMessage(expected_err));
168+
169+
// First 2 tags come from the segment, second 2 cannot be read.
170+
tags_data.push_back(0xCD);
171+
tags = manager.UnpackTagsFromCoreFileSegment(reader, 0, 0, 32, 64);
172+
EXPECT_THAT_EXPECTED(tags, llvm::FailedWithMessage(expected_err));
173+
174+
// Segment is completely empty.
175+
tags_data.clear();
176+
tags = manager.UnpackTagsFromCoreFileSegment(reader, 0, 0, 0, 16);
177+
EXPECT_THAT_EXPECTED(tags, llvm::FailedWithMessage(expected_err));
147178
}
148179

149180
TEST(MemoryTagManagerAArch64MTETest, GetLogicalTag) {

0 commit comments

Comments
 (0)