From fe1de40f09c3b19c45b6d894e0b74e7430e4e0d3 Mon Sep 17 00:00:00 2001 From: Caleb Xu Date: Fri, 4 Sep 2026 12:35:13 -0400 Subject: [PATCH] Validate segment nsects against cmdsize and file bounds - Check length against available file data and that section table fits cmdsize - Raise LoadCommandSizeError on invalid nsects values Signed-off-by: Caleb Xu Assisted-by: OpenCode (Nemotron 3 Ultra) --- lib/macho/load_commands.rb | 3 +++ test/test_macho.rb | 30 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/lib/macho/load_commands.rb b/lib/macho/load_commands.rb index 7a4f0c411..a56d2c60b 100644 --- a/lib/macho/load_commands.rb +++ b/lib/macho/load_commands.rb @@ -472,6 +472,9 @@ def sections offset = view.offset + self.class.bytesize length = nsects * klass.bytesize + available = view.raw_data.bytesize - offset + raise LoadCommandSizeError, cmdsize if length > available || self.class.bytesize + length > cmdsize + bins = view.raw_data[offset, length] bins.unpack("a#{klass.bytesize}" * nsects).map do |bin| klass.new_from_bin(view.endianness, bin) diff --git a/test/test_macho.rb b/test/test_macho.rb index f54758687..11f10ee5e 100644 --- a/test/test_macho.rb +++ b/test/test_macho.rb @@ -65,6 +65,14 @@ def test_load_command_with_invalid_string_offset end end + def test_segment_with_too_many_sections + assert_segment_sections_exceed_cmdsize(:i386, :LC_SEGMENT) + end + + def test_segment_64_with_too_many_sections + assert_segment_sections_exceed_cmdsize(:x86_64, :LC_SEGMENT_64) + end + def test_minimal_macho file = MachO::MachOFile.new(fixture(:yaml2obj, "minimal.macho")) @@ -803,4 +811,26 @@ def test_to_h assert_kind_of Integer, lc_hsh["structure"]["bytesize"] end end + + private + + def assert_segment_sections_exceed_cmdsize(arch, command_type) + bin = File.binread(fixture(arch, "hello.bin")) + file = MachO::MachOFile.new_from_bin(bin) + segment = file[command_type].first + section_class = if segment.is_a?(MachO::LoadCommands::SegmentCommand64) + MachO::Sections::Section64 + else + MachO::Sections::Section + end + nsects = ((segment.cmdsize - segment.class.bytesize) / section_class.bytesize) + 1 + nsects_offset = segment.view.offset + segment.class.bytesize - 8 + bin[nsects_offset, 4] = [nsects].pack(file.endianness == :little ? "L<" : "L>") + + segment = MachO::MachOFile.new_from_bin(bin)[command_type].first + + assert_raises MachO::LoadCommandSizeError do + segment.sections + end + end end