[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