[llvm] [Remarks] Fix use-after-free of block scalar values in the YAML remark parser (PR #228072)
Henrich Lauko via llvm-commits
llvm-commits at lists.llvm.org
Thu Oct 1 06:18:28 PDT 2026
https://github.com/xlauko created https://github.com/llvm/llvm-project/pull/228072
`YAMLRemarkParser::parseStr` returned `BlockScalarNode::getValue()` as is. That value lives in the YAML document's node allocator, and `next()` advances the document iterator before returning the remark, which frees it. So every argument written as a block scalar (`|`), such as LowerMatrixIntrinsics remarks, pointed at freed memory by the time the caller saw it. This affects the C API, `RemarkLinker`, MLIR remark import, `llvm-remarkutil` and `llvm-opt-report`.
Copy block scalar values into an allocator owned by the parser. The new test reads a block value after parsing the next remark. Under ASan it fails without the fix with a heap-use-after-free in `YAMLRemarkParser::next()`.
Split out of #227654.
Assisted-by: Claude Code (Claude Fable 5.1).
>From 55c61f6544b20097999f6aba885fff4a44c743f4 Mon Sep 17 00:00:00 2001
From: Henrich Lauko <hlauko at nvidia.com>
Date: Thu, 1 Oct 2026 13:17:58 +0000
Subject: [PATCH] [Remarks] Fix use-after-free of block scalar values in the
YAML remark parser
YAMLRemarkParser::parseStr returned BlockScalarNode::getValue() as is.
The YAML parser copies a block scalar's value into the document's node
allocator, and next() advances the document iterator before returning
the remark, which frees that allocator. So every argument value written
as a block scalar pointed at freed memory by the time the caller saw
it.
Copy block scalar values into an allocator owned by the parser.
Assisted-by: Claude
---
llvm/lib/Remarks/YAMLRemarkParser.cpp | 4 +-
llvm/lib/Remarks/YAMLRemarkParser.h | 4 ++
.../Remarks/YAMLRemarksParsingTest.cpp | 37 +++++++++++++++++++
3 files changed, 44 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/Remarks/YAMLRemarkParser.cpp b/llvm/lib/Remarks/YAMLRemarkParser.cpp
index 33f659eaaa49b..405575b3123de 100644
--- a/llvm/lib/Remarks/YAMLRemarkParser.cpp
+++ b/llvm/lib/Remarks/YAMLRemarkParser.cpp
@@ -273,7 +273,9 @@ Expected<StringRef> YAMLRemarkParser::parseStr(yaml::KeyValueNode &Node) {
ValueBlock = dyn_cast_if_present<yaml::BlockScalarNode>(Node.getValue());
if (!ValueBlock)
return error("expected a value of scalar type.", Node);
- Result = ValueBlock->getValue();
+ // The block value lives in the YAML document, which next() frees before
+ // returning the remark.
+ Result = ValueBlock->getValue().copy(Alloc);
} else
Result = Value->getRawValue();
diff --git a/llvm/lib/Remarks/YAMLRemarkParser.h b/llvm/lib/Remarks/YAMLRemarkParser.h
index 9a30e9e295cb2..e53adf1bd16e8 100644
--- a/llvm/lib/Remarks/YAMLRemarkParser.h
+++ b/llvm/lib/Remarks/YAMLRemarkParser.h
@@ -15,6 +15,7 @@
#include "llvm/Remarks/Remark.h"
#include "llvm/Remarks/RemarkParser.h"
+#include "llvm/Support/Allocator.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/SourceMgr.h"
@@ -58,6 +59,9 @@ struct YAMLRemarkParser : public RemarkParser {
/// If we parse remark metadata in separate mode, we need to open a new file
/// and parse that.
std::unique_ptr<MemoryBuffer> SeparateBuf;
+ /// Storage for block scalar values, which live in the YAML document that
+ /// next() frees.
+ BumpPtrAllocator Alloc;
YAMLRemarkParser(StringRef Buf);
diff --git a/llvm/unittests/Remarks/YAMLRemarksParsingTest.cpp b/llvm/unittests/Remarks/YAMLRemarksParsingTest.cpp
index 824813aa5af7c..95ca63fc979f9 100644
--- a/llvm/unittests/Remarks/YAMLRemarksParsingTest.cpp
+++ b/llvm/unittests/Remarks/YAMLRemarksParsingTest.cpp
@@ -475,6 +475,43 @@ TEST(YAMLRemarks, Contents) {
EXPECT_TRUE(errorToBool(std::move(E))); // Check for parsing errors.
}
+TEST(YAMLRemarks, ContentsBlockScalar) {
+ StringRef Buf = "--- !Missed\n"
+ "Pass: pass\n"
+ "Name: name\n"
+ "Function: func\n"
+ "Args:\n"
+ " - String: |\n"
+ " abc\n"
+ " def\n"
+ "--- !Missed\n"
+ "Pass: pass\n"
+ "Name: name\n"
+ "Function: func\n"
+ "Args:\n"
+ " - String: |\n"
+ " xxxxxxxxxx\n"
+ " xxxxxxxxxx\n"
+ "\n";
+
+ Expected<std::unique_ptr<remarks::RemarkParser>> MaybeParser =
+ remarks::createRemarkParser(remarks::Format::YAML, Buf);
+ EXPECT_FALSE(errorToBool(MaybeParser.takeError()));
+ EXPECT_TRUE(*MaybeParser != nullptr);
+
+ remarks::RemarkParser &Parser = **MaybeParser;
+ Expected<std::unique_ptr<remarks::Remark>> MaybeRemark = Parser.next();
+ EXPECT_FALSE(errorToBool(MaybeRemark.takeError()));
+ EXPECT_TRUE(*MaybeRemark != nullptr);
+ // The value must outlive the YAML document it was parsed from.
+ Expected<std::unique_ptr<remarks::Remark>> MaybeNext = Parser.next();
+ EXPECT_FALSE(errorToBool(MaybeNext.takeError()));
+
+ const remarks::Remark &Remark = **MaybeRemark;
+ ASSERT_EQ(Remark.Args.size(), 1U);
+ EXPECT_EQ(checkStr(Remark.Args[0].Val, 8), "abc\ndef\n");
+}
+
static inline StringRef checkStr(LLVMRemarkStringRef Str,
unsigned ExpectedLen) {
const char *StrData = LLVMRemarkStringGetData(Str);
More information about the llvm-commits
mailing list