Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions lib/macho/exceptions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,14 @@ def initialize(num)
end
end

# Raised when a load command has an invalid size.
class LoadCommandSizeError < NotAMachOError
# @param size [Integer] the invalid size
def initialize(size)
super("Invalid Mach-O load command size: #{size}")
end
end

# Raised when a load command can't be created manually.
class LoadCommandNotCreatableError < MachOError
# @param cmd_sym [Symbol] the uncreatable load command's symbol
Expand Down
2 changes: 2 additions & 0 deletions lib/macho/load_commands.rb
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@ def initialize(lc, lc_str)
view = lc.view

if view
raise LCStrMalformedError, lc if lc_str < lc.class.bytesize || lc_str >= lc.cmdsize

lc_str_abs = view.offset + lc_str
lc_end = view.offset + lc.cmdsize - 1
raw_string = view.raw_data.slice(lc_str_abs..lc_end)
Expand Down
12 changes: 12 additions & 0 deletions lib/macho/macho_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -579,17 +579,27 @@ def check_filetype(filetype)

# All load commands in the file.
# @return [Array<LoadCommands::LoadCommand>] an array of load commands
# @raise [TruncatedFileError] if the declared load command data is incomplete
# @raise [LoadCommandError] if an unknown load command is encountered
# @raise [LoadCommandSizeError] if a load command's size is invalid
# @api private
def populate_load_commands
permissive = options.fetch(:permissive, false)
offset = header.class.bytesize
load_commands_end = offset + sizeofcmds
raise TruncatedFileError if load_commands_end > @raw_data.bytesize

load_commands = []
@load_commands_by_type = Hash.new { |h, k| h[k] = [] }

header.ncmds.times do
raise TruncatedFileError if offset + LoadCommands::LoadCommand.bytesize > load_commands_end

fmt = Utils.specialize_format("L=", endianness)
cmd = @raw_data.slice(offset, 4).unpack1(fmt)
cmdsize = @raw_data.slice(offset + 4, 4).unpack1(fmt)
raise LoadCommandSizeError, cmdsize if cmdsize % 4 != 0 || offset + cmdsize > load_commands_end

cmd_sym = LoadCommands::LOAD_COMMANDS[cmd]

raise LoadCommandError, cmd unless cmd_sym || permissive
Expand All @@ -602,6 +612,8 @@ def populate_load_commands
LoadCommands::LoadCommand
end

raise LoadCommandSizeError, cmdsize if cmdsize < klass.bytesize

view = MachOView.new(self, @raw_data, endianness, offset)
command = klass.new_from_bin(view)

Expand Down
8 changes: 8 additions & 0 deletions test/bin/yaml2obj/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
YAMLS := $(wildcard *.yaml)
MACHOS := $(YAMLS:.yaml=.macho)

.PHONY: all
all: $(MACHOS)

%.macho: %.yaml
yaml2obj $< -o $@
Binary file added test/bin/yaml2obj/big-endian.macho
Binary file not shown.
11 changes: 11 additions & 0 deletions test/bin/yaml2obj/big-endian.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--- !mach-o
IsLittleEndian: false
FileHeader:
magic: 0xFEEDFACE
cputype: 0x00000007
cpusubtype: 0x00000003
filetype: 0x00000001
ncmds: 0
sizeofcmds: 0
flags: 0x00000000
...
Binary file added test/bin/yaml2obj/build-version.macho
Binary file not shown.
22 changes: 22 additions & 0 deletions test/bin/yaml2obj/build-version.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--- !mach-o
IsLittleEndian: true
FileHeader:
magic: 0xFEEDFACF
cputype: 0x01000007
cpusubtype: 0x00000003
filetype: 0x00000002
ncmds: 1
sizeofcmds: 32
flags: 0x00000000
reserved: 0x00000000
LoadCommands:
- cmd: LC_BUILD_VERSION
cmdsize: 32
platform: 2
minos: 0x00080000
sdk: 0x00090000
ntools: 1
Tools:
- tool: 1
version: 0
...
Binary file added test/bin/yaml2obj/invalid-lcstr-offset.macho
Binary file not shown.
18 changes: 18 additions & 0 deletions test/bin/yaml2obj/invalid-lcstr-offset.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--- !mach-o
IsLittleEndian: true
FileHeader:
magic: 0xFEEDFACF
cputype: 0x01000007
cpusubtype: 0x00000003
filetype: 0x00000002
ncmds: 1
sizeofcmds: 24
flags: 0x00000000
reserved: 0x00000000
LoadCommands:
- cmd: LC_LOAD_DYLINKER
cmdsize: 24
name: 0
Content: test
ZeroPadBytes: 8
...
Binary file added test/bin/yaml2obj/load-cmdsize-too-large.macho
Binary file not shown.
15 changes: 15 additions & 0 deletions test/bin/yaml2obj/load-cmdsize-too-large.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--- !mach-o
IsLittleEndian: true
FileHeader:
magic: 0xFEEDFACF
cputype: 0x01000007
cpusubtype: 0x00000003
filetype: 0x00000001
ncmds: 1
sizeofcmds: 8
flags: 0x00002000
reserved: 0x00000000
LoadCommands:
- cmd: 0xDEADBEEF
cmdsize: 16
...
Binary file added test/bin/yaml2obj/load-cmdsize-too-small.macho
Binary file not shown.
24 changes: 24 additions & 0 deletions test/bin/yaml2obj/load-cmdsize-too-small.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--- !mach-o
IsLittleEndian: true
FileHeader:
magic: 0xFEEDFACF
cputype: 0x01000007
cpusubtype: 0x00000003
filetype: 0x00000001
ncmds: 1
sizeofcmds: 56
flags: 0x00002000
reserved: 0x00000000
LoadCommands:
- cmd: LC_SEGMENT_64
cmdsize: 56
segname: '__TEXT'
vmaddr: 0x1000
vmsize: 0x10
fileoff: 0
filesize: 0
maxprot: 7
initprot: 5
nsects: 0
flags: 0
...
Binary file added test/bin/yaml2obj/minimal.macho
Binary file not shown.
12 changes: 12 additions & 0 deletions test/bin/yaml2obj/minimal.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--- !mach-o
IsLittleEndian: true
FileHeader:
magic: 0xFEEDFACF
cputype: 0x0100000C
cpusubtype: 0x00000000
filetype: 0x00000001
ncmds: 0
sizeofcmds: 0
flags: 0x00000000
reserved: 0x00000000
...
Binary file added test/bin/yaml2obj/rpath.macho
Binary file not shown.
18 changes: 18 additions & 0 deletions test/bin/yaml2obj/rpath.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--- !mach-o
IsLittleEndian: true
FileHeader:
magic: 0xFEEDFACF
cputype: 0x01000007
cpusubtype: 0x00000003
filetype: 0x00000002
ncmds: 1
sizeofcmds: 24
flags: 0x00000000
reserved: 0x00000000
LoadCommands:
- cmd: LC_RPATH
cmdsize: 24
path: 12
Content: /usr/lib
ZeroPadBytes: 3
...
Binary file not shown.
12 changes: 12 additions & 0 deletions test/bin/yaml2obj/truncated-load-command-region.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--- !mach-o
IsLittleEndian: true
FileHeader:
magic: 0xFEEDFACF
cputype: 0x01000007
cpusubtype: 0x00000003
filetype: 0x00000001
ncmds: 0
sizeofcmds: 8
flags: 0x00002000
reserved: 0x00000000
...
Binary file added test/bin/yaml2obj/truncated-load-commands.macho
Binary file not shown.
12 changes: 12 additions & 0 deletions test/bin/yaml2obj/truncated-load-commands.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--- !mach-o
IsLittleEndian: true
FileHeader:
magic: 0xFEEDFACF
cputype: 0x01000007
cpusubtype: 0x00000003
filetype: 0x00000001
ncmds: 1
sizeofcmds: 0
flags: 0x00002000
reserved: 0x00000000
...
Binary file added test/bin/yaml2obj/unaligned-cmdsize.macho
Binary file not shown.
17 changes: 17 additions & 0 deletions test/bin/yaml2obj/unaligned-cmdsize.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--- !mach-o
IsLittleEndian: true
FileHeader:
magic: 0xFEEDFACF
cputype: 0x01000007
cpusubtype: 0x00000003
filetype: 0x00000002
ncmds: 1
sizeofcmds: 25
flags: 0x00000000
reserved: 0x00000000
LoadCommands:
- cmd: LC_UUID
cmdsize: 25
uuid: 00112233-4455-6677-8899-AABBCCDDEEFF
ZeroPadBytes: 1
...
65 changes: 65 additions & 0 deletions test/test_macho.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,71 @@ def test_truncated_file
end
end

def test_load_command_with_size_smaller_than_its_structure
assert_raises MachO::LoadCommandSizeError do
MachO::MachOFile.new(fixture(:yaml2obj, "load-cmdsize-too-small.macho"))
end
end

def test_load_command_with_size_beyond_its_region
assert_raises MachO::LoadCommandSizeError do
MachO::MachOFile.new(fixture(:yaml2obj, "load-cmdsize-too-large.macho"))
end
end

def test_declared_load_commands_are_truncated
assert_raises MachO::TruncatedFileError do
MachO::MachOFile.new(fixture(:yaml2obj, "truncated-load-commands.macho"))
end
end

def test_declared_load_command_region_is_truncated
assert_raises MachO::TruncatedFileError do
MachO::MachOFile.new(fixture(:yaml2obj, "truncated-load-command-region.macho"))
end
end

def test_load_command_with_unaligned_size
assert_raises MachO::LoadCommandSizeError do
MachO::MachOFile.new(fixture(:yaml2obj, "unaligned-cmdsize.macho"))
end
end

def test_load_command_with_invalid_string_offset
command = MachO::MachOFile.new(fixture(:yaml2obj, "invalid-lcstr-offset.macho"))[:LC_LOAD_DYLINKER].first

assert_raises MachO::LCStrMalformedError do
command.name
end
end

def test_minimal_macho
file = MachO::MachOFile.new(fixture(:yaml2obj, "minimal.macho"))

assert file.object?
assert_equal :arm64, file.cputype
end

def test_big_endian_macho
file = MachO::MachOFile.new(fixture(:yaml2obj, "big-endian.macho"))

assert_equal :big, file.endianness
assert file.magic32?
assert_equal :i386, file.cputype
end

def test_build_version_command
command = MachO::MachOFile.new(fixture(:yaml2obj, "build-version.macho"))[:LC_BUILD_VERSION].first

assert_equal "8.0.0", command.minos_string
assert_equal "9.0.0", command.sdk_string
assert_equal 1, command.tool_entries.tools.size
end

def test_rpath_command
assert_equal ["/usr/lib"], MachO::MachOFile.new(fixture(:yaml2obj, "rpath.macho")).rpaths
end

def test_load_commands
filenames = SINGLE_ARCHES.map { |a| fixture(a, "hello.bin") }

Expand Down
Loading