[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