Skip to content

Commit 295a3f7

Browse files
authored
[yaml2obj][MachO] Fix crash from integer underflow with invalid cmdsize (#165924)
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.
1 parent f2ed002 commit 295a3f7

File tree

2 files changed

+89
-3
lines changed

2 files changed

+89
-3
lines changed

llvm/lib/ObjectYAML/MachOEmitter.cpp

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,26 @@
1919
#include "llvm/Support/Error.h"
2020
#include "llvm/Support/FormatVariadic.h"
2121
#include "llvm/Support/LEB128.h"
22-
#include "llvm/Support/YAMLTraits.h"
2322
#include "llvm/Support/SystemZ/zOSSupport.h"
23+
#include "llvm/Support/WithColor.h"
24+
#include "llvm/Support/YAMLTraits.h"
2425
#include "llvm/Support/raw_ostream.h"
2526

2627
using namespace llvm;
2728

2829
namespace {
2930

31+
static const char *getLoadCommandName(uint32_t cmd) {
32+
switch (cmd) {
33+
#define HANDLE_LOAD_COMMAND(LCName, LCValue, LCStruct) \
34+
case MachO::LCName: \
35+
return #LCName;
36+
#include "llvm/BinaryFormat/MachO.def"
37+
default:
38+
return nullptr;
39+
}
40+
}
41+
3042
class MachOWriter {
3143
public:
3244
MachOWriter(MachOYAML::Object &Obj) : Obj(Obj), fileStart(0) {
@@ -244,7 +256,8 @@ void MachOWriter::ZeroToOffset(raw_ostream &OS, size_t Offset) {
244256
}
245257

246258
void MachOWriter::writeLoadCommands(raw_ostream &OS) {
247-
for (auto &LC : Obj.LoadCommands) {
259+
for (size_t i = 0; i < Obj.LoadCommands.size(); ++i) {
260+
auto &LC = Obj.LoadCommands[i];
248261
size_t BytesWritten = 0;
249262
llvm::MachO::macho_load_command Data = LC.Data;
250263

@@ -285,7 +298,25 @@ void MachOWriter::writeLoadCommands(raw_ostream &OS) {
285298

286299
// Fill remaining bytes with 0. This will only get hit in partially
287300
// specified test cases.
288-
auto BytesRemaining = LC.Data.load_command_data.cmdsize - BytesWritten;
301+
// Prevent integer underflow if BytesWritten exceeds cmdsize.
302+
if (BytesWritten > LC.Data.load_command_data.cmdsize) {
303+
std::string Name;
304+
const char *NameCStr = getLoadCommandName(LC.Data.load_command_data.cmd);
305+
if (NameCStr)
306+
Name = NameCStr;
307+
else
308+
Name = ("(0x" + Twine::utohexstr(LC.Data.load_command_data.cmd) + ")")
309+
.str();
310+
311+
WithColor::warning() << "load command " << i << " " << Name
312+
<< " cmdsize too small ("
313+
<< LC.Data.load_command_data.cmdsize
314+
<< " bytes) for actual size (" << BytesWritten
315+
<< " bytes)\n";
316+
}
317+
auto BytesRemaining = (BytesWritten < LC.Data.load_command_data.cmdsize)
318+
? LC.Data.load_command_data.cmdsize - BytesWritten
319+
: 0;
289320
if (BytesRemaining > 0) {
290321
ZeroFillBytes(OS, BytesRemaining);
291322
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
## Test that yaml2obj handles load commands with cmdsize smaller than the
2+
## actual structure size without crashing (due to integer underflow).
3+
4+
## Test with a known load command (LC_SEGMENT_64).
5+
# RUN: yaml2obj %s --docnum=1 -o %t1 2>&1 | FileCheck %s --check-prefix=WARNING-KNOWN
6+
# RUN: not llvm-readobj --file-headers %t1 2>&1 | FileCheck %s --check-prefix=MALFORMED
7+
8+
# WARNING-KNOWN: warning: load command 0 LC_SEGMENT_64 cmdsize too small (56 bytes) for actual size (72 bytes)
9+
10+
# MALFORMED: error: {{.*}}: truncated or malformed object (load command 0 LC_SEGMENT_64 cmdsize too small)
11+
12+
--- !mach-o
13+
FileHeader:
14+
magic: 0xFEEDFACF
15+
cputype: 0x01000007
16+
cpusubtype: 0x00000003
17+
filetype: 0x00000001
18+
ncmds: 1
19+
sizeofcmds: 56
20+
flags: 0x00002000
21+
reserved: 0x00000000
22+
LoadCommands:
23+
- cmd: LC_SEGMENT_64
24+
cmdsize: 56 ## Should be 72 for LC_SEGMENT_64
25+
segname: '__TEXT'
26+
vmaddr: 0x1000
27+
vmsize: 0x10
28+
fileoff: 0
29+
filesize: 0
30+
maxprot: 7
31+
initprot: 5
32+
nsects: 0
33+
flags: 0
34+
...
35+
36+
## Test with an unknown load command value.
37+
# RUN: yaml2obj %s --docnum=2 -o %t2 2>&1 | FileCheck %s --check-prefix=WARNING-UNKNOWN
38+
39+
# WARNING-UNKNOWN: warning: load command 0 (0xdeadbeef) cmdsize too small (8 bytes) for actual size (20 bytes)
40+
41+
--- !mach-o
42+
FileHeader:
43+
magic: 0xFEEDFACF
44+
cputype: 0x01000007
45+
cpusubtype: 0x00000003
46+
filetype: 0x00000001
47+
ncmds: 1
48+
sizeofcmds: 20
49+
flags: 0x00002000
50+
reserved: 0x00000000
51+
LoadCommands:
52+
- cmd: 0xDEADBEEF
53+
cmdsize: 8
54+
PayloadBytes: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C]
55+
...

0 commit comments

Comments
 (0)