[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