[llvm] [yaml2obj][MachO] Fix crash from integer underflow with invalid cmdsize (PR #165924)

Ryan Mansfield via llvm-commits llvm-commits at lists.llvm.org
Mon Nov 3 06:49:09 PST 2025


https://github.com/rjmansfield updated https://github.com/llvm/llvm-project/pull/165924

>From 1857805719a0c32263f4356c3902c63d32db15c8 Mon Sep 17 00:00:00 2001
From: Ryan Mansfield <ryan_mansfield at apple.com>
Date: Fri, 31 Oct 2025 16:21:11 -0400
Subject: [PATCH 1/2] [yaml2obj][MachO] Fix crash from integer underflow with
 invalid cmdsize

yaml2obj would crash when processing Mach-O load commands with cmdsize
smaller than the actual structure size e.g. LC_SEGMENT_64 with
cmdsize=56 instead of 72. The crash occurred due to integer underflow
when calculating padding: cmdsize - BytesWritten wraps to a large value
when negative, causing a massive allocation attempt.
---
 llvm/lib/ObjectYAML/MachOEmitter.cpp          | 10 +++++-
 .../MachO/segment-cmdsize-too-small.yaml      | 33 +++++++++++++++++++
 2 files changed, 42 insertions(+), 1 deletion(-)
 create mode 100644 llvm/test/ObjectYAML/MachO/segment-cmdsize-too-small.yaml

diff --git a/llvm/lib/ObjectYAML/MachOEmitter.cpp b/llvm/lib/ObjectYAML/MachOEmitter.cpp
index 35d442e8e3437..a3555b211ab53 100644
--- a/llvm/lib/ObjectYAML/MachOEmitter.cpp
+++ b/llvm/lib/ObjectYAML/MachOEmitter.cpp
@@ -285,7 +285,15 @@ void MachOWriter::writeLoadCommands(raw_ostream &OS) {
 
     // Fill remaining bytes with 0. This will only get hit in partially
     // specified test cases.
-    auto BytesRemaining = LC.Data.load_command_data.cmdsize - BytesWritten;
+    // Prevent integer underflow if BytesWritten exceeds cmdsize.
+    if (BytesWritten > LC.Data.load_command_data.cmdsize) {
+      errs() << "warning: load command " << LC.Data.load_command_data.cmd
+             << " cmdsize too small (" << LC.Data.load_command_data.cmdsize
+             << " bytes) for actual size (" << BytesWritten << " bytes)\n";
+    }
+    auto BytesRemaining = (BytesWritten < LC.Data.load_command_data.cmdsize)
+                              ? LC.Data.load_command_data.cmdsize - BytesWritten
+                              : 0;
     if (BytesRemaining > 0) {
       ZeroFillBytes(OS, BytesRemaining);
     }
diff --git a/llvm/test/ObjectYAML/MachO/segment-cmdsize-too-small.yaml b/llvm/test/ObjectYAML/MachO/segment-cmdsize-too-small.yaml
new file mode 100644
index 0000000000000..250a0631a5a9c
--- /dev/null
+++ b/llvm/test/ObjectYAML/MachO/segment-cmdsize-too-small.yaml
@@ -0,0 +1,33 @@
+## Test that yaml2obj handles cmdsize that is too small for LC_SEGMENT_64
+## without crashing (due to integer underflow).
+
+# RUN: yaml2obj %s -o %t 2>&1 | FileCheck %s --check-prefix=WARNING
+# RUN: not llvm-readobj --file-headers %t 2>&1 | FileCheck %s --check-prefix=MALFORMED
+
+# WARNING: warning: load command 25 cmdsize too small (56 bytes) for actual size (72 bytes)
+
+# MALFORMED: error: {{.*}}: truncated or malformed object (load command 0 LC_SEGMENT_64 cmdsize too small)
+
+--- !mach-o
+FileHeader:
+  magic:           0xFEEDFACF
+  cputype:         0x01000007
+  cpusubtype:      0x00000003
+  filetype:        0x00000001
+  ncmds:           1
+  sizeofcmds:      56
+  flags:           0x00002000
+  reserved:        0x00000000
+LoadCommands:
+  - cmd:             LC_SEGMENT_64
+    cmdsize:         56          ## Should be 72 for LC_SEGMENT_64
+    segname:         '__TEXT'
+    vmaddr:          0x1000
+    vmsize:          0x10
+    fileoff:         0
+    filesize:        0
+    maxprot:         7
+    initprot:        5
+    nsects:          0
+    flags:           0
+...

>From 356799ba47334c8c73e3c48d21f20e17a3f0d503 Mon Sep 17 00:00:00 2001
From: Ryan Mansfield <ryan_mansfield at apple.com>
Date: Mon, 3 Nov 2025 09:34:33 -0500
Subject: [PATCH 2/2] Address reviewer feedback.

 - Add getLoadCommandName() helper to map cmd values to names
 - Change to indexed loop to show load command index for disambiguation
 - Update warning format to match llvm-readobj style:
    Known commands: "load command 0 LC_SEGMENT_64 cmdsize too small"
    Unknown commands: "load command 0 (0xdeadbeef) cmdsize too small"
 - Use WithColor::warning()
---
 llvm/lib/ObjectYAML/MachOEmitter.cpp          | 32 ++++++++++++++++---
 ...small.yaml => load-cmdsize-too-small.yaml} |  6 ++--
 2 files changed, 30 insertions(+), 8 deletions(-)
 rename llvm/test/ObjectYAML/MachO/{segment-cmdsize-too-small.yaml => load-cmdsize-too-small.yaml} (77%)

diff --git a/llvm/lib/ObjectYAML/MachOEmitter.cpp b/llvm/lib/ObjectYAML/MachOEmitter.cpp
index a3555b211ab53..0fd2c588eaead 100644
--- a/llvm/lib/ObjectYAML/MachOEmitter.cpp
+++ b/llvm/lib/ObjectYAML/MachOEmitter.cpp
@@ -19,14 +19,26 @@
 #include "llvm/Support/Error.h"
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/LEB128.h"
-#include "llvm/Support/YAMLTraits.h"
 #include "llvm/Support/SystemZ/zOSSupport.h"
+#include "llvm/Support/WithColor.h"
+#include "llvm/Support/YAMLTraits.h"
 #include "llvm/Support/raw_ostream.h"
 
 using namespace llvm;
 
 namespace {
 
+static const char *getLoadCommandName(uint32_t cmd) {
+  switch (cmd) {
+#define HANDLE_LOAD_COMMAND(LCName, LCValue, LCStruct)                         \
+  case MachO::LCName:                                                          \
+    return #LCName;
+#include "llvm/BinaryFormat/MachO.def"
+  default:
+    return nullptr;
+  }
+}
+
 class MachOWriter {
 public:
   MachOWriter(MachOYAML::Object &Obj) : Obj(Obj), fileStart(0) {
@@ -244,7 +256,8 @@ void MachOWriter::ZeroToOffset(raw_ostream &OS, size_t Offset) {
 }
 
 void MachOWriter::writeLoadCommands(raw_ostream &OS) {
-  for (auto &LC : Obj.LoadCommands) {
+  for (size_t i = 0; i < Obj.LoadCommands.size(); ++i) {
+    auto &LC = Obj.LoadCommands[i];
     size_t BytesWritten = 0;
     llvm::MachO::macho_load_command Data = LC.Data;
 
@@ -287,9 +300,18 @@ void MachOWriter::writeLoadCommands(raw_ostream &OS) {
     // specified test cases.
     // Prevent integer underflow if BytesWritten exceeds cmdsize.
     if (BytesWritten > LC.Data.load_command_data.cmdsize) {
-      errs() << "warning: load command " << LC.Data.load_command_data.cmd
-             << " cmdsize too small (" << LC.Data.load_command_data.cmdsize
-             << " bytes) for actual size (" << BytesWritten << " bytes)\n";
+      const char *name = getLoadCommandName(LC.Data.load_command_data.cmd);
+      if (name)
+        WithColor::warning()
+            << "load command " << i << " " << name << " cmdsize too small ("
+            << LC.Data.load_command_data.cmdsize << " bytes) for actual size ("
+            << BytesWritten << " bytes)\n";
+      else
+        WithColor::warning()
+            << "load command " << i << " (0x"
+            << Twine::utohexstr(LC.Data.load_command_data.cmd)
+            << ") cmdsize too small (" << LC.Data.load_command_data.cmdsize
+            << " bytes) for actual size (" << BytesWritten << " bytes)\n";
     }
     auto BytesRemaining = (BytesWritten < LC.Data.load_command_data.cmdsize)
                               ? LC.Data.load_command_data.cmdsize - BytesWritten
diff --git a/llvm/test/ObjectYAML/MachO/segment-cmdsize-too-small.yaml b/llvm/test/ObjectYAML/MachO/load-cmdsize-too-small.yaml
similarity index 77%
rename from llvm/test/ObjectYAML/MachO/segment-cmdsize-too-small.yaml
rename to llvm/test/ObjectYAML/MachO/load-cmdsize-too-small.yaml
index 250a0631a5a9c..ef18a959b12ec 100644
--- a/llvm/test/ObjectYAML/MachO/segment-cmdsize-too-small.yaml
+++ b/llvm/test/ObjectYAML/MachO/load-cmdsize-too-small.yaml
@@ -1,10 +1,10 @@
-## Test that yaml2obj handles cmdsize that is too small for LC_SEGMENT_64
-## without crashing (due to integer underflow).
+## Test that yaml2obj handles load commands with cmdsize smaller than the
+## actual structure size without crashing (due to integer underflow).
 
 # RUN: yaml2obj %s -o %t 2>&1 | FileCheck %s --check-prefix=WARNING
 # RUN: not llvm-readobj --file-headers %t 2>&1 | FileCheck %s --check-prefix=MALFORMED
 
-# WARNING: warning: load command 25 cmdsize too small (56 bytes) for actual size (72 bytes)
+# WARNING: warning: load command 0 LC_SEGMENT_64 cmdsize too small (56 bytes) for actual size (72 bytes)
 
 # MALFORMED: error: {{.*}}: truncated or malformed object (load command 0 LC_SEGMENT_64 cmdsize too small)
 



More information about the llvm-commits mailing list