[llvm] [yaml2obj][MachO] Fix crash from integer underflow with invalid cmdsize (PR #165924)
Ryan Mansfield via llvm-commits
llvm-commits at lists.llvm.org
Tue Nov 4 05:04:45 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/5] [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/5] 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)
>From e2fc33872b77c07453c40cb5308a762a4a3b9162 Mon Sep 17 00:00:00 2001
From: Ryan Mansfield <ryan_mansfield at apple.com>
Date: Tue, 4 Nov 2025 07:24:20 -0500
Subject: [PATCH 3/5] Address reviewer feedback. - Refactor warning message -
Add testcase for unknown load command
---
llvm/lib/ObjectYAML/MachOEmitter.cpp | 22 +++++++--------
.../MachO/load-cmdsize-too-small.yaml | 28 +++++++++++++++++--
2 files changed, 36 insertions(+), 14 deletions(-)
diff --git a/llvm/lib/ObjectYAML/MachOEmitter.cpp b/llvm/lib/ObjectYAML/MachOEmitter.cpp
index 0fd2c588eaead..63ff8729450ca 100644
--- a/llvm/lib/ObjectYAML/MachOEmitter.cpp
+++ b/llvm/lib/ObjectYAML/MachOEmitter.cpp
@@ -300,18 +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) {
- 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";
+ std::string Name;
+ const char *NameCStr = getLoadCommandName(LC.Data.load_command_data.cmd);
+ if (NameCStr)
+ Name = NameCStr;
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";
+ Name = "(0x" + Twine::utohexstr(LC.Data.load_command_data.cmd).str() + ")";
+
+ WithColor::warning() << "load command " << i << " " << Name
+ << " 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/load-cmdsize-too-small.yaml b/llvm/test/ObjectYAML/MachO/load-cmdsize-too-small.yaml
index ef18a959b12ec..ec460a6bc618a 100644
--- a/llvm/test/ObjectYAML/MachO/load-cmdsize-too-small.yaml
+++ b/llvm/test/ObjectYAML/MachO/load-cmdsize-too-small.yaml
@@ -1,13 +1,18 @@
## 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
+# RUN: yaml2obj %s --docnum=1 -o %t1 2>&1 | FileCheck %s --check-prefix=WARNING-KNOWN
+# RUN: not llvm-readobj --file-headers %t1 2>&1 | FileCheck %s --check-prefix=MALFORMED
-# WARNING: warning: load command 0 LC_SEGMENT_64 cmdsize too small (56 bytes) for actual size (72 bytes)
+# RUN: yaml2obj %s --docnum=2 -o %t2 2>&1 | FileCheck %s --check-prefix=WARNING-UNKNOWN
+
+# WARNING-KNOWN: 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)
+# WARNING-UNKNOWN: warning: load command 0 (0xdeadbeef) cmdsize too small (8 bytes) for actual size (20 bytes)
+
+## Test with a known load command (LC_SEGMENT_64)
--- !mach-o
FileHeader:
magic: 0xFEEDFACF
@@ -31,3 +36,20 @@ LoadCommands:
nsects: 0
flags: 0
...
+
+## Test with an unknown load command value
+--- !mach-o
+FileHeader:
+ magic: 0xFEEDFACF
+ cputype: 0x01000007
+ cpusubtype: 0x00000003
+ filetype: 0x00000001
+ ncmds: 1
+ sizeofcmds: 20
+ flags: 0x00002000
+ reserved: 0x00000000
+LoadCommands:
+ - cmd: 0xDEADBEEF
+ cmdsize: 8
+ PayloadBytes: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C]
+...
>From d2f1eee485e18fcac1e75e2f4f41a12982a4bcc6 Mon Sep 17 00:00:00 2001
From: Ryan Mansfield <ryan_mansfield at apple.com>
Date: Tue, 4 Nov 2025 07:41:55 -0500
Subject: [PATCH 4/5] Fix formatting.
---
llvm/lib/ObjectYAML/MachOEmitter.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/ObjectYAML/MachOEmitter.cpp b/llvm/lib/ObjectYAML/MachOEmitter.cpp
index 63ff8729450ca..9367e6b17383c 100644
--- a/llvm/lib/ObjectYAML/MachOEmitter.cpp
+++ b/llvm/lib/ObjectYAML/MachOEmitter.cpp
@@ -305,7 +305,8 @@ void MachOWriter::writeLoadCommands(raw_ostream &OS) {
if (NameCStr)
Name = NameCStr;
else
- Name = "(0x" + Twine::utohexstr(LC.Data.load_command_data.cmd).str() + ")";
+ Name =
+ "(0x" + Twine::utohexstr(LC.Data.load_command_data.cmd).str() + ")";
WithColor::warning() << "load command " << i << " " << Name
<< " cmdsize too small ("
>From 5c1704953366fc2127b71e317c9a4bb6bb2bb618 Mon Sep 17 00:00:00 2001
From: Ryan Mansfield <ryan_mansfield at apple.com>
Date: Tue, 4 Nov 2025 08:00:30 -0500
Subject: [PATCH 5/5] Address reviewer feedback. - Do .str() around the full
string concatenation - Reorganize test
---
llvm/lib/ObjectYAML/MachOEmitter.cpp | 4 ++--
.../ObjectYAML/MachO/load-cmdsize-too-small.yaml | 12 +++++-------
2 files changed, 7 insertions(+), 9 deletions(-)
diff --git a/llvm/lib/ObjectYAML/MachOEmitter.cpp b/llvm/lib/ObjectYAML/MachOEmitter.cpp
index 9367e6b17383c..46c91811d0a67 100644
--- a/llvm/lib/ObjectYAML/MachOEmitter.cpp
+++ b/llvm/lib/ObjectYAML/MachOEmitter.cpp
@@ -305,8 +305,8 @@ void MachOWriter::writeLoadCommands(raw_ostream &OS) {
if (NameCStr)
Name = NameCStr;
else
- Name =
- "(0x" + Twine::utohexstr(LC.Data.load_command_data.cmd).str() + ")";
+ Name = ("(0x" + Twine::utohexstr(LC.Data.load_command_data.cmd) + ")")
+ .str();
WithColor::warning() << "load command " << i << " " << Name
<< " cmdsize too small ("
diff --git a/llvm/test/ObjectYAML/MachO/load-cmdsize-too-small.yaml b/llvm/test/ObjectYAML/MachO/load-cmdsize-too-small.yaml
index ec460a6bc618a..fbb1ceb52343e 100644
--- a/llvm/test/ObjectYAML/MachO/load-cmdsize-too-small.yaml
+++ b/llvm/test/ObjectYAML/MachO/load-cmdsize-too-small.yaml
@@ -1,18 +1,13 @@
## Test that yaml2obj handles load commands with cmdsize smaller than the
## actual structure size without crashing (due to integer underflow).
+## Test with a known load command (LC_SEGMENT_64).
# RUN: yaml2obj %s --docnum=1 -o %t1 2>&1 | FileCheck %s --check-prefix=WARNING-KNOWN
# RUN: not llvm-readobj --file-headers %t1 2>&1 | FileCheck %s --check-prefix=MALFORMED
-# RUN: yaml2obj %s --docnum=2 -o %t2 2>&1 | FileCheck %s --check-prefix=WARNING-UNKNOWN
-
# WARNING-KNOWN: 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)
-
-# WARNING-UNKNOWN: warning: load command 0 (0xdeadbeef) cmdsize too small (8 bytes) for actual size (20 bytes)
-
-## Test with a known load command (LC_SEGMENT_64)
--- !mach-o
FileHeader:
magic: 0xFEEDFACF
@@ -37,7 +32,10 @@ LoadCommands:
flags: 0
...
-## Test with an unknown load command value
+## Test with an unknown load command value.
+# RUN: yaml2obj %s --docnum=2 -o %t2 2>&1 | FileCheck %s --check-prefix=WARNING-UNKNOWN
+
+# WARNING-UNKNOWN: warning: load command 0 (0xdeadbeef) cmdsize too small (8 bytes) for actual size (20 bytes)
--- !mach-o
FileHeader:
magic: 0xFEEDFACF
More information about the llvm-commits
mailing list