brew vendor-gems: commit updates.

This commit is contained in:
BrewTestBot 2022-01-12 18:08:59 +00:00
parent 6a109f77c6
commit 847b271481
No known key found for this signature in database
GPG Key ID: 82D7D104050B0F0F
12 changed files with 316 additions and 37 deletions

View File

@ -91,7 +91,7 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-performance-1
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rails-2.13.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rspec-2.7.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-sorbet-0.6.5/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-macho-2.5.1/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-macho-3.0.0/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/simplecov-html-0.12.3/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/simplecov_json_formatter-0.1.3/lib"
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/simplecov-0.21.2/lib"

View File

@ -16,7 +16,7 @@ require_relative "macho/tools"
# The primary namespace for ruby-macho.
module MachO
# release version
VERSION = "2.5.1"
VERSION = "3.0.0"
# Opens the given filename as a MachOFile or FatFile, depending on its magic.
# @param filename [String] the file being opened

View File

@ -84,7 +84,7 @@ module MachO
# @param cpusubtype [Integer] the CPU sub-type of the unknown pair
def initialize(cputype, cpusubtype)
super "Unrecognized CPU sub-type: 0x%08<cpusubtype>x" \
" (for CPU type: 0x%08<cputype>x" % { :cputype => cputype, :cpusubtype => cpusubtype }
" (for CPU type: 0x%08<cputype>x" % { :cputype => cputype, :cpusubtype => cpusubtype }
end
end
@ -120,7 +120,7 @@ module MachO
# @param actual_arity [Integer] the number of arguments received
def initialize(cmd_sym, expected_arity, actual_arity)
super "Expected #{expected_arity} arguments for #{cmd_sym} creation," \
" got #{actual_arity}"
" got #{actual_arity}"
end
end
@ -137,7 +137,7 @@ module MachO
# @param lc [MachO::LoadCommand] the load command containing the string
def initialize(lc)
super "Load command #{lc.type} at offset #{lc.view.offset} contains a" \
" malformed string"
" malformed string"
end
end
@ -154,8 +154,8 @@ module MachO
# @param filename [String] the filename
def initialize(filename)
super "Updated load commands do not fit in the header of " \
"#{filename}. #{filename} needs to be relinked, possibly with " \
"-headerpad or -headerpad_max_install_names"
"#{filename}. #{filename} needs to be relinked, possibly with " \
"-headerpad or -headerpad_max_install_names"
end
end
@ -207,4 +207,14 @@ module MachO
" Consider merging with `fat64: true`"
end
end
# Raised when attempting to parse a compressed Mach-O without explicitly
# requesting decompression.
class CompressedMachOError < MachOError
end
# Raised when attempting to decompress a compressed Mach-O without adequate
# dependencies, or on other decompression errors.
class DecompressionError < MachOError
end
end

View File

@ -55,7 +55,7 @@ module MachO
machos.each do |macho|
macho_offset = Utils.round(offset, 2**macho.segment_alignment)
raise FatArchOffsetOverflowError, macho_offset if !fat64 && macho_offset > (2**32 - 1)
raise FatArchOffsetOverflowError, macho_offset if !fat64 && macho_offset > ((2**32) - 1)
macho_pads[macho] = Utils.padding_for(offset, 2**macho.segment_alignment)
@ -96,7 +96,7 @@ module MachO
@filename = filename
@options = opts
@raw_data = File.open(@filename, "rb", &:read)
@raw_data = File.binread(@filename)
populate_fields
end
@ -238,6 +238,8 @@ module MachO
# @param options [Hash]
# @option options [Boolean] :strict (true) if true, fail if one slice fails.
# if false, fail only if all slices fail.
# @option options [Boolean] :uniq (false) for each slice: if true, change
# each rpath simultaneously.
# @return [void]
# @see MachOFile#change_rpath
def change_rpath(old_path, new_path, options = {})
@ -268,6 +270,9 @@ module MachO
# @param options [Hash]
# @option options [Boolean] :strict (true) if true, fail if one slice fails.
# if false, fail only if all slices fail.
# @option options [Boolean] :uniq (false) for each slice: if true, delete
# only the first runtime path that matches. if false, delete all duplicate
# paths that match.
# @return void
# @see MachOFile#delete_rpath
def delete_rpath(path, options = {})
@ -291,7 +296,7 @@ module MachO
# @param filename [String] the file to write to
# @return [void]
def write(filename)
File.open(filename, "wb") { |f| f.write(@raw_data) }
File.binwrite(filename, @raw_data)
end
# Write all (fat) data to the file used to initialize the instance.
@ -301,7 +306,7 @@ module MachO
def write!
raise MachOError, "no initial file to write to" if filename.nil?
File.open(@filename, "wb") { |f| f.write(@raw_data) }
File.binwrite(@filename, @raw_data)
end
# @return [Hash] a hash representation of this {FatFile}

View File

@ -37,6 +37,18 @@ module MachO
# @api private
MH_CIGAM_64 = 0xcffaedfe
# compressed mach-o magic
# @api private
COMPRESSED_MAGIC = 0x636f6d70 # "comp"
# a compressed mach-o slice, using LZSS for compression
# @api private
COMP_TYPE_LZSS = 0x6c7a7373 # "lzss"
# a compressed mach-o slice, using LZVN ("FastLib") for compression
# @api private
COMP_TYPE_FASTLIB = 0x6c7a766e # "lzvn"
# association of magic numbers to string representations
# @api private
MH_MAGICS = {
@ -433,6 +445,11 @@ module MachO
# @api private
MH_KEXT_BUNDLE = 0xb
# a set of Mach-Os, running in the same userspace, sharing a linkedit. The kext collection files are an example
# of this object type
# @api private
MH_FILESET = 0xc
# association of filetypes to Symbol representations
# @api private
MH_FILETYPES = {
@ -447,6 +464,7 @@ module MachO
MH_DYLIB_STUB => :dylib_stub,
MH_DSYM => :dsym,
MH_KEXT_BUNDLE => :kext_bundle,
MH_FILESET => :fileset,
}.freeze
# association of mach header flag symbols to values
@ -478,6 +496,9 @@ module MachO
:MH_HAS_TLV_DESCRIPTORS => 0x800000,
:MH_NO_HEAP_EXECUTION => 0x1000000,
:MH_APP_EXTENSION_SAFE => 0x02000000,
:MH_NLIST_OUTOFSYNC_WITH_DYLDINFO => 0x04000000,
:MH_SIM_SUPPORT => 0x08000000,
:MH_DYLIB_IN_CACHE => 0x80000000,
}.freeze
# Fat binary header structure
@ -724,6 +745,11 @@ module MachO
filetype == Headers::MH_KEXT_BUNDLE
end
# @return [Boolean] whether or not the file is of type `MH_FILESET`
def fileset?
filetype == Headers::MH_FILESET
end
# @return [Boolean] true if the Mach-O has 32-bit magic, false otherwise
def magic32?
Utils.magic32?(magic)
@ -785,5 +811,88 @@ module MachO
}.merge super
end
end
# Prelinked kernel/"kernelcache" header structure
class PrelinkedKernelHeader < MachOStructure
# @return [Integer] the magic number for a compressed header ({COMPRESSED_MAGIC})
attr_reader :signature
# @return [Integer] the type of compression used
attr_reader :compress_type
# @return [Integer] a checksum for the uncompressed data
attr_reader :adler32
# @return [Integer] the size of the uncompressed data, in bytes
attr_reader :uncompressed_size
# @return [Integer] the size of the compressed data, in bytes
attr_reader :compressed_size
# @return [Integer] the version of the prelink format
attr_reader :prelink_version
# @return [void]
attr_reader :reserved
# @return [void]
attr_reader :platform_name
# @return [void]
attr_reader :root_path
# @see MachOStructure::FORMAT
# @api private
FORMAT = "L>6a40a64a256"
# @see MachOStructure::SIZEOF
# @api private
SIZEOF = 384
# @api private
def initialize(signature, compress_type, adler32, uncompressed_size, compressed_size, prelink_version, reserved, platform_name, root_path)
super()
@signature = signature
@compress_type = compress_type
@adler32 = adler32
@uncompressed_size = uncompressed_size
@compressed_size = compressed_size
@prelink_version = prelink_version
@reserved = reserved.unpack("L>10")
@platform_name = platform_name
@root_path = root_path
end
# @return [Boolean] whether this prelinked kernel supports KASLR
def kaslr?
prelink_version >= 1
end
# @return [Boolean] whether this prelinked kernel is compressed with LZSS
def lzss?
compress_type == COMP_TYPE_LZSS
end
# @return [Boolean] whether this prelinked kernel is compressed with LZVN
def lzvn?
compress_type == COMP_TYPE_FASTLIB
end
# @return [Hash] a hash representation of this {PrelinkedKernelHeader}
def to_h
{
"signature" => signature,
"compress_type" => compress_type,
"adler32" => adler32,
"uncompressed_size" => uncompressed_size,
"compressed_size" => compressed_size,
"prelink_version" => prelink_version,
"reserved" => reserved,
"platform_name" => platform_name,
"root_path" => root_path,
}.merge super
end
end
end
end

View File

@ -63,7 +63,8 @@ module MachO
0x31 => :LC_NOTE,
0x32 => :LC_BUILD_VERSION,
(0x33 | LC_REQ_DYLD) => :LC_DYLD_EXPORTS_TRIE,
(0x34 | LC_REQ_DYLD) => :LD_DYLD_CHAINED_FIXUPS,
(0x34 | LC_REQ_DYLD) => :LC_DYLD_CHAINED_FIXUPS,
(0x35 | LC_REQ_DYLD) => :LC_FILESET_ENTRY,
}.freeze
# association of symbol representations to load command constants
@ -150,7 +151,8 @@ module MachO
:LC_NOTE => "NoteCommand",
:LC_BUILD_VERSION => "BuildVersionCommand",
:LC_DYLD_EXPORTS_TRIE => "LinkeditDataCommand",
:LD_DYLD_CHAINED_FIXUPS => "LinkeditDataCommand",
:LC_DYLD_CHAINED_FIXUPS => "LinkeditDataCommand",
:LC_FILESET_ENTRY => "FilesetEntryCommand",
}.freeze
# association of segment name symbols to names
@ -173,6 +175,7 @@ module MachO
:SG_FVMLIB => 0x2,
:SG_NORELOC => 0x4,
:SG_PROTECTED_VERSION_1 => 0x8,
:SG_READ_ONLY => 0x10,
}.freeze
# The top-level Mach-O load command structure.
@ -1794,5 +1797,48 @@ module MachO
}.merge super
end
end
# A load command containing a description of a Mach-O that is a constituent of a fileset.
# Each entry is further described by its own Mach header.
# Corresponds to LC_FILESET_ENTRY.
class FilesetEntryCommand < LoadCommand
# @return [Integer] the virtual memory address of the entry
attr_reader :vmaddr
# @return [Integer] the file offset of the entry
attr_reader :fileoff
# @return [LCStr] the entry's ID
attr_reader :entry_id
# @return [void]
attr_reader :reserved
# @see MachOStructure::FORMAT
# @api private
FORMAT = "L=2Q=2L=2"
# @see MachOStructure::SIZEOF
# @api private
SIZEOF = 28
def initialize(view, cmd, cmdsize, vmaddr, fileoff, entry_id, reserved)
super(view, cmd, cmdsize)
@vmaddr = vmaddr
@fileoff = fileoff
@entry_id = LCStr.new(self, entry_id)
@reserved = reserved
end
# @return [Hash] a hash representation of this {FilesetEntryCommand}
def to_h
{
"vmaddr" => vmaddr,
"fileoff" => fileoff,
"entry_id" => entry_id,
"reserved" => reserved,
}.merge super
end
end
end
end

View File

@ -34,7 +34,11 @@ module MachO
# @param bin [String] a binary string containing raw Mach-O data
# @param opts [Hash] options to control the parser with
# @option opts [Boolean] :permissive whether to ignore unknown load commands
# @option opts [Boolean] :decompress whether to decompress, if capable
# @return [MachOFile] a new MachOFile
# @note The `:decompress` option relies on non-default dependencies. Compression
# is only used in niche Mach-Os, so leaving this disabled is a reasonable default for
# virtually all normal uses.
def self.new_from_bin(bin, **opts)
instance = allocate
instance.initialize_from_bin(bin, opts)
@ -46,13 +50,17 @@ module MachO
# @param filename [String] the Mach-O file to load from
# @param opts [Hash] options to control the parser with
# @option opts [Boolean] :permissive whether to ignore unknown load commands
# @option opts [Boolean] :decompress whether to decompress, if capable
# @raise [ArgumentError] if the given file does not exist
# @note The `:decompress` option relies on non-default dependencies. Compression
# is only used in niche Mach-Os, so leaving this disabled is a reasonable default for
# virtually all normal uses.
def initialize(filename, **opts)
raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
@filename = filename
@options = opts
@raw_data = File.open(@filename, "rb", &:read)
@raw_data = File.binread(@filename)
populate_fields
end
@ -152,8 +160,8 @@ module MachO
# the instance fields
# @raise [OffsetInsertionError] if the offset is not in the load command region
# @raise [HeaderPadError] if the new command exceeds the header pad buffer
# @note Calling this method with an arbitrary offset in the load command
# region **will leave the object in an inconsistent state**.
# @note Calling this method with an arbitrary offset in the load command region
# **will leave the object in an inconsistent state**.
def insert_command(offset, lc, options = {})
context = LoadCommands::LoadCommand::SerializationContext.context_for(self)
cmd_raw = lc.serialize(context)
@ -196,7 +204,7 @@ module MachO
# Appends a new load command to the Mach-O.
# @param lc [LoadCommands::LoadCommand] the load command being added
# @param options [Hash]
# @option options [Boolean] :repopulate (true) whether or not to repopulate
# @option f [Boolean] :repopulate (true) whether or not to repopulate
# the instance fields
# @return [void]
# @see #insert_command
@ -368,20 +376,20 @@ module MachO
# file.change_rpath("/usr/lib", "/usr/local/lib")
# @param old_path [String] the old runtime path
# @param new_path [String] the new runtime path
# @param _options [Hash]
# @param options [Hash]
# @option options [Boolean] :uniq (false) if true, change duplicate
# rpaths simultaneously.
# @return [void]
# @raise [RpathUnknownError] if no such old runtime path exists
# @raise [RpathExistsError] if the new runtime path already exists
# @note `_options` is currently unused and is provided for signature
# compatibility with {MachO::FatFile#change_rpath}
def change_rpath(old_path, new_path, _options = {})
def change_rpath(old_path, new_path, options = {})
old_lc = command(:LC_RPATH).find { |r| r.path.to_s == old_path }
raise RpathUnknownError, old_path if old_lc.nil?
raise RpathExistsError, new_path if rpaths.include?(new_path)
new_lc = LoadCommands::LoadCommand.create(:LC_RPATH, new_path)
delete_rpath(old_path)
delete_rpath(old_path, options)
insert_command(old_lc.view.offset, new_lc)
end
@ -409,13 +417,18 @@ module MachO
# file.delete_rpath("/lib")
# file.rpaths # => []
# @param path [String] the runtime path to delete
# @param _options [Hash]
# @param options [Hash]
# @option options [Boolean] :uniq (false) if true, also delete
# duplicates of the requested path. If false, delete the first
# instance (by offset) of the requested path.
# @return void
# @raise [RpathUnknownError] if no such runtime path exists
# @note `_options` is currently unused and is provided for signature
# compatibility with {MachO::FatFile#delete_rpath}
def delete_rpath(path, _options = {})
rpath_cmds = command(:LC_RPATH).select { |r| r.path.to_s == path }
def delete_rpath(path, options = {})
uniq = options.fetch(:uniq, false)
search_method = uniq ? :select : :find
# Cast rpath_cmds into an Array so we can handle the uniq and non-uniq cases the same way
rpath_cmds = Array(command(:LC_RPATH).method(search_method).call { |r| r.path.to_s == path })
raise RpathUnknownError, path if rpath_cmds.empty?
# delete the commands in reverse order, offset descending.
@ -426,7 +439,7 @@ module MachO
# @param filename [String] the file to write to
# @return [void]
def write(filename)
File.open(filename, "wb") { |f| f.write(@raw_data) }
File.binwrite(filename, @raw_data)
end
# Write all Mach-O data to the file used to initialize the instance.
@ -436,7 +449,7 @@ module MachO
def write!
raise MachOError, "no initial file to write to" if @filename.nil?
File.open(@filename, "wb") { |f| f.write(@raw_data) }
File.binwrite(@filename, @raw_data)
end
# @return [Hash] a hash representation of this {MachOFile}
@ -458,6 +471,9 @@ module MachO
# the smallest Mach-O header is 28 bytes
raise TruncatedFileError if @raw_data.size < 28
magic = @raw_data[0..3].unpack1("N")
populate_prelinked_kernel_header if Utils.compressed_magic?(magic)
magic = populate_and_check_magic
mh_klass = Utils.magic32?(magic) ? Headers::MachHeader : Headers::MachHeader64
mh = mh_klass.new_from_bin(endianness, @raw_data[0, mh_klass.bytesize])
@ -469,6 +485,48 @@ module MachO
mh
end
# Read a compressed Mach-O header and check its validity, as well as whether we're able
# to parse it.
# @return [void]
# @raise [CompressedMachOError] if we weren't asked to perform decompression
# @raise [DecompressionError] if decompression is impossible or fails
# @api private
def populate_prelinked_kernel_header
raise CompressedMachOError unless options.fetch(:decompress, false)
@plh = Headers::PrelinkedKernelHeader.new_from_bin :big, @raw_data[0, Headers::PrelinkedKernelHeader.bytesize]
raise DecompressionError, "unsupported compression type: LZSS" if @plh.lzss?
raise DecompressionError, "unknown compression type: 0x#{plh.compress_type.to_s 16}" unless @plh.lzvn?
decompress_macho_lzvn
end
# Attempt to decompress a Mach-O file from the data specified in a prelinked kernel header.
# @return [void]
# @raise [DecompressionError] if decompression is impossible or fails
# @api private
# @note This method rewrites the internal state of {MachOFile} to pretend as if it was never
# compressed to begin with, allowing all other APIs to transparently act on compressed Mach-Os.
def decompress_macho_lzvn
begin
require "lzfse"
rescue LoadError
raise DecompressionError, "LZVN required but the optional 'lzfse' gem is not installed"
end
# From this point onwards, the internal buffer of this MachOFile refers to the decompressed
# contents specified by the prelinked kernel header.
begin
@raw_data = LZFSE.lzvn_decompress @raw_data.slice(Headers::PrelinkedKernelHeader.bytesize, @plh.compressed_size)
# Sanity checks.
raise DecompressionError if @raw_data.size != @plh.uncompressed_size
# TODO: check the adler32 CRC in @plh
rescue LZFSE::DecodeError
raise DecompressionError, "LZVN decompression failed"
end
end
# Read just the file's magic number and check its validity.
# @return [Integer] the magic
# @raise [MagicError] if the magic is not valid Mach-O magic
@ -553,8 +611,8 @@ module MachO
segments.each do |seg|
seg.sections.each do |sect|
next if sect.empty?
next if sect.flag?(:S_ZEROFILL)
next if sect.flag?(:S_THREAD_LOCAL_ZEROFILL)
next if sect.type?(:S_ZEROFILL)
next if sect.type?(:S_THREAD_LOCAL_ZEROFILL)
next unless sect.offset < offset
offset = sect.offset

View File

@ -4,24 +4,24 @@ module MachO
# Classes and constants for parsing sections in Mach-O binaries.
module Sections
# type mask
SECTION_TYPE = 0x000000ff
SECTION_TYPE_MASK = 0x000000ff
# attributes mask
SECTION_ATTRIBUTES = 0xffffff00
SECTION_ATTRIBUTES_MASK = 0xffffff00
# user settable attributes mask
SECTION_ATTRIBUTES_USR = 0xff000000
SECTION_ATTRIBUTES_USR_MASK = 0xff000000
# system settable attributes mask
SECTION_ATTRIBUTES_SYS = 0x00ffff00
SECTION_ATTRIBUTES_SYS_MASK = 0x00ffff00
# maximum specifiable section alignment, as a power of 2
# @note see `MAXSECTALIGN` macro in `cctools/misc/lipo.c`
MAX_SECT_ALIGN = 15
# association of section flag symbols to values
# association of section type symbols to values
# @api private
SECTION_FLAGS = {
SECTION_TYPES = {
:S_REGULAR => 0x0,
:S_ZEROFILL => 0x1,
:S_CSTRING_LITERALS => 0x2,
@ -44,6 +44,12 @@ module MachO
:S_THREAD_LOCAL_VARIABLES => 0x13,
:S_THREAD_LOCAL_VARIABLE_POINTERS => 0x14,
:S_THREAD_LOCAL_INIT_FUNCTION_POINTERS => 0x15,
:S_INIT_FUNC_OFFSETS => 0x16,
}.freeze
# association of section attribute symbols to values
# @api private
SECTION_ATTRIBUTES = {
:S_ATTR_PURE_INSTRUCTIONS => 0x80000000,
:S_ATTR_NO_TOC => 0x40000000,
:S_ATTR_STRIP_STATIC_SYMS => 0x20000000,
@ -56,6 +62,13 @@ module MachO
:S_ATTR_LOC_RELOC => 0x00000100,
}.freeze
# association of section flag symbols to values
# @api private
SECTION_FLAGS = {
**SECTION_TYPES,
**SECTION_ATTRIBUTES,
}.freeze
# association of section name symbols to names
# @api private
SECTION_NAMES = {
@ -147,6 +160,33 @@ module MachO
size.zero?
end
# @return [Integer] the raw numeric type of this section
def type
flags & SECTION_TYPE_MASK
end
# @example
# puts "this section is regular" if sect.type?(:S_REGULAR)
# @param type_sym [Symbol] a section type symbol
# @return [Boolean] whether this section is of the given type
def type?(type_sym)
type == SECTION_TYPES[type_sym]
end
# @return [Integer] the raw numeric attributes of this section
def attributes
flags & SECTION_ATTRIBUTES_MASK
end
# @example
# puts "pure instructions" if sect.attribute?(:S_ATTR_PURE_INSTRUCTIONS)
# @param attr_sym [Symbol] a section attribute symbol
# @return [Boolean] whether this section is of the given type
def attribute?(attr_sym)
!!(attributes & SECTION_ATTRIBUTES[attr_sym])
end
# @deprecated Use {#type?} or {#attribute?} instead.
# @example
# puts "this section is regular" if sect.flag?(:S_REGULAR)
# @param flag [Symbol] a section flag symbol

View File

@ -51,6 +51,8 @@ module MachO
# @param options [Hash]
# @option options [Boolean] :strict (true) whether or not to fail loudly
# with an exception if the change cannot be performed
# @option options [Boolean] :uniq (false) whether or not to change duplicate
# rpaths simultaneously
# @return [void]
def self.change_rpath(filename, old_path, new_path, options = {})
file = MachO.open(filename)
@ -80,6 +82,8 @@ module MachO
# @param options [Hash]
# @option options [Boolean] :strict (true) whether or not to fail loudly
# with an exception if the change cannot be performed
# @option options [Boolean] :uniq (false) whether or not to delete duplicate
# rpaths simultaneously
# @return [void]
def self.delete_rpath(filename, old_path, options = {})
file = MachO.open(filename)

View File

@ -121,5 +121,12 @@ module MachO
def self.big_magic?(num)
[Headers::MH_MAGIC, Headers::MH_MAGIC_64].include? num
end
# Compares the given number to the known magic number for a compressed Mach-O slice.
# @param num [Integer] the number being checked
# @return [Boolean] whether `num` is a valid compressed header magic number
def self.compressed_magic?(num)
num == Headers::COMPRESSED_MAGIC
end
end
end