Skip to content

Commit be4666a

Browse files
committed
Validate Mach-O headers before full reads
- Avoid loading large invalid files before `MachOFile` and `FatFile` can reject malformed headers. - Keep successful parsing on one handle so serialisation and mutation continue to use complete file data. - Preserve compressed Mach-O handling when decompression needs the full payload.
1 parent 1f005f1 commit be4666a

4 files changed

Lines changed: 40 additions & 2 deletions

File tree

lib/macho/fat_file.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,12 @@ def initialize(filename, **opts)
9696

9797
@filename = filename
9898
@options = opts
99-
@raw_data = File.binread(@filename)
99+
File.open(@filename, "rb") do |file|
100+
@raw_data = file.read(Headers::FatHeader.bytesize)
101+
@raw_data ||= ""
102+
populate_fat_header
103+
@raw_data << file.read
104+
end
100105
populate_fields
101106
end
102107

lib/macho/macho_file.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,12 @@ def initialize(filename, **opts)
6060

6161
@filename = filename
6262
@options = opts
63-
@raw_data = File.binread(@filename)
63+
File.open(@filename, "rb") do |file|
64+
@raw_data = file.read(Headers::MachHeader.bytesize)
65+
@raw_data ||= ""
66+
populate_mach_header if !opts.fetch(:decompress, false) || !Utils.compressed_magic?(@raw_data.unpack1("N"))
67+
@raw_data << file.read
68+
end
6469
populate_fields
6570
end
6671

test/test_fat.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,20 @@ def test_java_classfile
3636
end
3737
end
3838

39+
def test_invalid_header_is_rejected_before_full_read
40+
tempfile_with_data("invalid_header", [MachO::Headers::FAT_MAGIC, 31].pack("N2") + ("x" * 1024)) do |invalid_header|
41+
assert_raises MachO::JavaClassFileError do
42+
Class.new(MachO::FatFile) do
43+
def populate_fat_header
44+
raise "read entire file" if serialize.bytesize > MachO::Headers::FatHeader.bytesize
45+
46+
super
47+
end
48+
end.new(invalid_header.path)
49+
end
50+
end
51+
end
52+
3953
def test_zero_arch_file
4054
assert_raises MachO::ZeroArchitectureError do
4155
MachO::FatFile.new("test/bin/llvm/macho-invalid-fat-header")

test/test_macho.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,20 @@ def test_truncated_file
2727
end
2828
end
2929

30+
def test_invalid_header_is_rejected_before_full_read
31+
tempfile_with_data("invalid_header", [MachO::Headers::MH_MAGIC, 0, 0, MachO::Headers::MH_EXECUTE, 0, 0, 0].pack("N7") + ("x" * 1024)) do |invalid_header|
32+
assert_raises MachO::CPUTypeError do
33+
Class.new(MachO::MachOFile) do
34+
def populate_mach_header
35+
raise "read entire file" if serialize.bytesize > MachO::Headers::MachHeader.bytesize
36+
37+
super
38+
end
39+
end.new(invalid_header.path)
40+
end
41+
end
42+
end
43+
3044
def test_load_commands
3145
filenames = SINGLE_ARCHES.map { |a| fixture(a, "hello.bin") }
3246

0 commit comments

Comments
 (0)