[llvm] [yaml2obj] Apply output size limit to Mach-O (PR #210027)

via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 16 03:44:57 PDT 2026


https://github.com/AI-Risk-Management created https://github.com/llvm/llvm-project/pull/210027

Buffer bounded Mach-O output before committing it to the destination stream and replace attacker-sized temporary fill vectors with bounded writes. Route `--max-size` through the Mach-O emitter and cover default, disabled, and custom limits.

Assisted-by: OpenAI Codex

>From be0cfae071ba4e61cecfad485c102d51e07e43ac Mon Sep 17 00:00:00 2001
From: AI Risk Management <uozer at users.noreply.github.com>
Date: Thu, 16 Jul 2026 00:33:37 -0400
Subject: [PATCH] Apply output size limit to Mach-O

Buffer bounded Mach-O output before committing it to the destination stream and replace attacker-sized temporary fill vectors with bounded writes. Route --max-size through the Mach-O emitter and cover default, disabled, and custom limits.

Assisted-by: OpenAI Codex
---
 llvm/include/llvm/ObjectYAML/yaml2obj.h       |  2 +-
 llvm/lib/ObjectYAML/MachOEmitter.cpp          | 91 +++++++++++++++++--
 llvm/lib/ObjectYAML/yaml2obj.cpp              |  2 +-
 .../tools/yaml2obj/MachO/output-limit.yaml    | 25 +++++
 llvm/tools/yaml2obj/yaml2obj.cpp              |  2 +-
 5 files changed, 113 insertions(+), 9 deletions(-)
 create mode 100644 llvm/test/tools/yaml2obj/MachO/output-limit.yaml

diff --git a/llvm/include/llvm/ObjectYAML/yaml2obj.h b/llvm/include/llvm/ObjectYAML/yaml2obj.h
index a797155357fa4..0f4943e8782d9 100644
--- a/llvm/include/llvm/ObjectYAML/yaml2obj.h
+++ b/llvm/include/llvm/ObjectYAML/yaml2obj.h
@@ -76,7 +76,7 @@ LLVM_ABI bool yaml2goff(GOFFYAML::Object &Doc, raw_ostream &Out,
 LLVM_ABI bool yaml2elf(ELFYAML::Object &Doc, raw_ostream &Out, ErrorHandler EH,
                        uint64_t MaxSize);
 LLVM_ABI bool yaml2macho(YamlObjectFile &Doc, raw_ostream &Out,
-                         ErrorHandler EH);
+                         ErrorHandler EH, uint64_t MaxSize);
 LLVM_ABI bool yaml2minidump(MinidumpYAML::Object &Doc, raw_ostream &Out,
                             ErrorHandler EH);
 LLVM_ABI bool yaml2offload(OffloadYAML::Binary &Doc, raw_ostream &Out,
diff --git a/llvm/lib/ObjectYAML/MachOEmitter.cpp b/llvm/lib/ObjectYAML/MachOEmitter.cpp
index cf7202c7da949..ec77c6e6c3899 100644
--- a/llvm/lib/ObjectYAML/MachOEmitter.cpp
+++ b/llvm/lib/ObjectYAML/MachOEmitter.cpp
@@ -12,6 +12,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/BinaryFormat/MachO.h"
+#include "llvm/ADT/SmallVector.h"
 #include "llvm/ObjectYAML/DWARFEmitter.h"
 #include "llvm/ObjectYAML/ObjectYAML.h"
 #include "llvm/ObjectYAML/yaml2obj.h"
@@ -22,11 +23,83 @@
 #include "llvm/Support/WithColor.h"
 #include "llvm/Support/YAMLTraits.h"
 #include "llvm/Support/raw_ostream.h"
+#include <algorithm>
+#include <array>
+#include <limits>
 
 using namespace llvm;
 
 namespace {
 
+class MachOOutputBuffer : public raw_ostream {
+  raw_ostream &Out;
+  SmallVector<char, 128> Buffer;
+  uint64_t Position = 0;
+  uint64_t MaxSize;
+  bool LimitExceeded = false;
+
+  void advance(uint64_t Size) {
+    Position = Size > std::numeric_limits<uint64_t>::max() - Position
+                   ? std::numeric_limits<uint64_t>::max()
+                   : Position + Size;
+  }
+
+  bool canWrite(uint64_t Size) {
+    if (LimitExceeded)
+      return false;
+    if (MaxSize && (Position > MaxSize || Size > MaxSize - Position)) {
+      LimitExceeded = true;
+      advance(Size);
+      return false;
+    }
+    return true;
+  }
+
+  void write_impl(const char *Ptr, size_t Size) override {
+    if (!canWrite(Size))
+      return;
+    if (MaxSize)
+      Buffer.append(Ptr, Ptr + Size);
+    else
+      Out.write(Ptr, Size);
+    advance(Size);
+  }
+
+  uint64_t current_pos() const override { return Position; }
+
+public:
+  MachOOutputBuffer(raw_ostream &Out, uint64_t MaxSize)
+      : Out(Out), MaxSize(MaxSize) {
+    SetUnbuffered();
+  }
+
+  void writeZeros(uint64_t Size) {
+    if (!canWrite(Size))
+      return;
+    raw_ostream::write_zeros(Size);
+  }
+
+  void writePattern(uint64_t Size, uint32_t Data) {
+    if (!canWrite(Size))
+      return;
+    std::array<uint32_t, 1024> FillData;
+    FillData.fill(Data);
+    const char *Bytes = reinterpret_cast<const char *>(FillData.data());
+    while (Size) {
+      size_t Chunk = std::min<uint64_t>(Size, sizeof(FillData));
+      write(Bytes, Chunk);
+      Size -= Chunk;
+    }
+  }
+
+  bool exceededLimit() const { return LimitExceeded; }
+
+  void writeToOutput() {
+    if (MaxSize && !Buffer.empty())
+      Out.write(Buffer.data(), Buffer.size());
+  }
+};
+
 static const char *getLoadCommandName(uint32_t cmd) {
   switch (cmd) {
 #define HANDLE_LOAD_COMMAND(LCName, LCValue, LCStruct)                         \
@@ -239,13 +312,11 @@ size_t writeLoadCommandData<MachO::build_version_command>(
 }
 
 void ZeroFillBytes(raw_ostream &OS, size_t Size) {
-  std::vector<uint8_t> FillData(Size, 0);
-  OS.write(reinterpret_cast<char *>(FillData.data()), Size);
+  static_cast<MachOOutputBuffer &>(OS).writeZeros(Size);
 }
 
 void Fill(raw_ostream &OS, size_t Size, uint32_t Data) {
-  std::vector<uint32_t> FillData((Size / 4) + 1, Data);
-  OS.write(reinterpret_cast<char *>(FillData.data()), Size);
+  static_cast<MachOOutputBuffer &>(OS).writePattern(Size, Data);
 }
 
 void MachOWriter::ZeroToOffset(raw_ostream &OS, size_t Offset) {
@@ -775,13 +846,21 @@ void UniversalWriter::ZeroToOffset(raw_ostream &OS, size_t Offset) {
 namespace llvm {
 namespace yaml {
 
-bool yaml2macho(YamlObjectFile &Doc, raw_ostream &Out, ErrorHandler EH) {
+bool yaml2macho(YamlObjectFile &Doc, raw_ostream &Out, ErrorHandler EH,
+                uint64_t MaxSize) {
+  MachOOutputBuffer Buffer(Out, MaxSize);
   UniversalWriter Writer(Doc);
-  if (Error Err = Writer.writeMachO(Out)) {
+  if (Error Err = Writer.writeMachO(Buffer)) {
     handleAllErrors(std::move(Err),
                     [&](const ErrorInfoBase &Err) { EH(Err.message()); });
     return false;
   }
+  if (Buffer.exceededLimit()) {
+    EH("the desired output size is greater than permitted. Use the "
+       "--max-size option to change the limit");
+    return false;
+  }
+  Buffer.writeToOutput();
   return true;
 }
 
diff --git a/llvm/lib/ObjectYAML/yaml2obj.cpp b/llvm/lib/ObjectYAML/yaml2obj.cpp
index 469a6f96cdfa1..1675ecbc0d067 100644
--- a/llvm/lib/ObjectYAML/yaml2obj.cpp
+++ b/llvm/lib/ObjectYAML/yaml2obj.cpp
@@ -40,7 +40,7 @@ bool convertYAML(yaml::Input &YIn, raw_ostream &Out, ErrorHandler ErrHandler,
     if (Doc.Goff)
       return yaml2goff(*Doc.Goff, Out, ErrHandler);
     if (Doc.MachO || Doc.FatMachO)
-      return yaml2macho(Doc, Out, ErrHandler);
+      return yaml2macho(Doc, Out, ErrHandler, MaxSize);
     if (Doc.Minidump)
       return yaml2minidump(*Doc.Minidump, Out, ErrHandler);
     if (Doc.Offload)
diff --git a/llvm/test/tools/yaml2obj/MachO/output-limit.yaml b/llvm/test/tools/yaml2obj/MachO/output-limit.yaml
new file mode 100644
index 0000000000000..02670a1ccb5ac
--- /dev/null
+++ b/llvm/test/tools/yaml2obj/MachO/output-limit.yaml
@@ -0,0 +1,25 @@
+## Check the default Mach-O output limit and --max-size. This also verifies
+## that a large ZeroPadBytes value is rejected without allocating that amount.
+# RUN: yaml2obj %s -DPAD=0x9FFFD8 -DCMDSIZE=0x9FFFE0 -o /dev/null
+# RUN: not yaml2obj %s -DPAD=0xA00001 -DCMDSIZE=0xA00009 -o /dev/null 2>&1 | FileCheck %s
+# RUN: yaml2obj %s -DPAD=0xA00001 -DCMDSIZE=0xA00009 --max-size=0 -o /dev/null
+# RUN: not yaml2obj %s -DPAD=0xA00001 -DCMDSIZE=0xA00009 --max-size=0x100 -o /dev/null 2>&1 | \
+# RUN:   FileCheck %s
+
+# CHECK: error: the desired output size is greater than permitted. Use the --max-size option to change the limit
+
+--- !mach-o
+FileHeader:
+  magic:           0xFEEDFACF
+  cputype:         0x01000007
+  cpusubtype:      0x80000003
+  filetype:        0x00000002
+  ncmds:           1
+  sizeofcmds:      [[CMDSIZE]]
+  flags:           0x00000000
+  reserved:        0x00000000
+LoadCommands:
+  - cmd:             0xDEADBEEF
+    cmdsize:         [[CMDSIZE]]
+    ZeroPadBytes:    [[PAD]]
+...
diff --git a/llvm/tools/yaml2obj/yaml2obj.cpp b/llvm/tools/yaml2obj/yaml2obj.cpp
index 0cb3d5d61b126..dd8402d291714 100644
--- a/llvm/tools/yaml2obj/yaml2obj.cpp
+++ b/llvm/tools/yaml2obj/yaml2obj.cpp
@@ -52,7 +52,7 @@ cl::opt<unsigned>
 static cl::opt<uint64_t>
     MaxSize("max-size", cl::init(10 * 1024 * 1024),
             cl::desc("Sets the maximum allowed output size (0 means no limit) "
-                     "[ELF and COFF only]"),
+                     "[ELF, COFF and Mach-O only]"),
             cl::cat(Cat));
 
 cl::opt<std::string> OutputFilename("o", cl::desc("Output filename"),



More information about the llvm-commits mailing list