Update patchelf.rb to 1.4.0
This commit is contained in:
parent
a7c5e40c12
commit
031bd9cd18
@ -84,8 +84,8 @@ GEM
|
|||||||
sorbet-runtime (>= 0.5)
|
sorbet-runtime (>= 0.5)
|
||||||
parser (3.1.2.1)
|
parser (3.1.2.1)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
patchelf (1.3.0)
|
patchelf (1.4.0)
|
||||||
elftools (>= 1.1.3)
|
elftools (>= 1.2)
|
||||||
plist (3.6.0)
|
plist (3.6.0)
|
||||||
pry (0.14.1)
|
pry (0.14.1)
|
||||||
coderay (~> 1.1)
|
coderay (~> 1.1)
|
||||||
|
|||||||
@ -13,17 +13,18 @@ module PatchELF::Helper
|
|||||||
def alignup(val, align = T.unsafe(nil)); end
|
def alignup(val, align = T.unsafe(nil)); end
|
||||||
def color_enabled?; end
|
def color_enabled?; end
|
||||||
def colorize(str, type); end
|
def colorize(str, type); end
|
||||||
|
def page_size(e_machine = T.unsafe(nil)); end
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def aligndown(val, align = T.unsafe(nil)); end
|
def aligndown(val, align = T.unsafe(nil)); end
|
||||||
def alignup(val, align = T.unsafe(nil)); end
|
def alignup(val, align = T.unsafe(nil)); end
|
||||||
def color_enabled?; end
|
def color_enabled?; end
|
||||||
def colorize(str, type); end
|
def colorize(str, type); end
|
||||||
|
def page_size(e_machine = T.unsafe(nil)); end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
PatchELF::Helper::COLOR_CODE = T.let(T.unsafe(nil), Hash)
|
PatchELF::Helper::COLOR_CODE = T.let(T.unsafe(nil), Hash)
|
||||||
PatchELF::Helper::PAGE_SIZE = T.let(T.unsafe(nil), Integer)
|
|
||||||
|
|
||||||
module PatchELF::Logger
|
module PatchELF::Logger
|
||||||
private
|
private
|
||||||
@ -80,7 +80,7 @@ $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version
|
|||||||
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rainbow-3.1.1/lib")
|
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rainbow-3.1.1/lib")
|
||||||
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/sorbet-runtime-0.5.10461/lib")
|
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/sorbet-runtime-0.5.10461/lib")
|
||||||
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/parlour-8.0.0/lib")
|
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/parlour-8.0.0/lib")
|
||||||
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/patchelf-1.3.0/lib")
|
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/patchelf-1.4.0/lib")
|
||||||
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/plist-3.6.0/lib")
|
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/plist-3.6.0/lib")
|
||||||
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/pry-0.14.1/lib")
|
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/pry-0.14.1/lib")
|
||||||
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rack-3.0.0/lib")
|
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rack-3.0.0/lib")
|
||||||
|
|||||||
@ -11,7 +11,7 @@ require 'patchelf/helper'
|
|||||||
# :nodoc:
|
# :nodoc:
|
||||||
module PatchELF
|
module PatchELF
|
||||||
# TODO: refactor buf_* methods here
|
# TODO: refactor buf_* methods here
|
||||||
# TODO: move all refinements into a seperate file / helper file.
|
# TODO: move all refinements into a separate file / helper file.
|
||||||
# refinements for cleaner syntax / speed / memory optimizations
|
# refinements for cleaner syntax / speed / memory optimizations
|
||||||
module Refinements
|
module Refinements
|
||||||
refine StringIO do
|
refine StringIO do
|
||||||
@ -21,7 +21,7 @@ module PatchELF
|
|||||||
# @param [Integer] nbytes
|
# @param [Integer] nbytes
|
||||||
# @return[void]
|
# @return[void]
|
||||||
def fill(char, nbytes)
|
def fill(char, nbytes)
|
||||||
at_once = Helper::PAGE_SIZE
|
at_once = Helper.page_size
|
||||||
pending = nbytes
|
pending = nbytes
|
||||||
|
|
||||||
if pending > at_once
|
if pending > at_once
|
||||||
@ -133,6 +133,8 @@ module PatchELF
|
|||||||
sec = find_section '.dynamic'
|
sec = find_section '.dynamic'
|
||||||
return unless sec
|
return unless sec
|
||||||
|
|
||||||
|
return if sec.header.sh_type == ELFTools::Constants::SHT_NOBITS
|
||||||
|
|
||||||
shdr = sec.header
|
shdr = sec.header
|
||||||
with_buf_at(shdr.sh_offset) do |buf|
|
with_buf_at(shdr.sh_offset) do |buf|
|
||||||
dyn = ELFTools::Structs::ELF_Dyn.new(elf_class: elf_class, endian: endian)
|
dyn = ELFTools::Structs::ELF_Dyn.new(elf_class: elf_class, endian: endian)
|
||||||
@ -171,7 +173,7 @@ module PatchELF
|
|||||||
end
|
end
|
||||||
|
|
||||||
def modify_interpreter
|
def modify_interpreter
|
||||||
@replaced_sections['.interp'] = @set[:interpreter] + "\x00"
|
@replaced_sections['.interp'] = "#{@set[:interpreter]}\x00"
|
||||||
end
|
end
|
||||||
|
|
||||||
def modify_needed
|
def modify_needed
|
||||||
@ -234,14 +236,13 @@ module PatchELF
|
|||||||
dyn_tags = collect_runpath_tags
|
dyn_tags = collect_runpath_tags
|
||||||
resolve_rpath_tag_conflict(dyn_tags, force_rpath: force_rpath)
|
resolve_rpath_tag_conflict(dyn_tags, force_rpath: force_rpath)
|
||||||
# (:runpath, :rpath) order_matters.
|
# (:runpath, :rpath) order_matters.
|
||||||
resolved_rpath_dyns = dyn_tags.values_at(:runpath, :rpath).compact
|
resolved_rpath_dyn = dyn_tags.values_at(:runpath, :rpath).compact.first
|
||||||
|
|
||||||
old_rpath = ''
|
old_rpath = ''
|
||||||
rpath_off = nil
|
rpath_off = nil
|
||||||
resolved_rpath_dyns.each do |dyn|
|
if resolved_rpath_dyn
|
||||||
rpath_off = shdr_dynstr.sh_offset + dyn[:header].d_val
|
rpath_off = shdr_dynstr.sh_offset + resolved_rpath_dyn[:header].d_val
|
||||||
old_rpath = buf_cstr(rpath_off)
|
old_rpath = buf_cstr(rpath_off)
|
||||||
break
|
|
||||||
end
|
end
|
||||||
return if old_rpath == new_rpath
|
return if old_rpath == new_rpath
|
||||||
|
|
||||||
@ -293,6 +294,11 @@ module PatchELF
|
|||||||
dt_null_idx += 1
|
dt_null_idx += 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if dyn_num_bytes.nil?
|
||||||
|
Logger.error 'no dynamic tags'
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
# allot for new dt_runpath
|
# allot for new dt_runpath
|
||||||
shdr_dynamic = find_section('.dynamic').header
|
shdr_dynamic = find_section('.dynamic').header
|
||||||
new_dynamic_data = replace_section '.dynamic', shdr_dynamic.sh_size + dyn_num_bytes
|
new_dynamic_data = replace_section '.dynamic', shdr_dynamic.sh_size + dyn_num_bytes
|
||||||
@ -331,7 +337,7 @@ module PatchELF
|
|||||||
end
|
end
|
||||||
|
|
||||||
def page_size
|
def page_size
|
||||||
Helper::PAGE_SIZE
|
Helper.page_size(ehdr.e_machine)
|
||||||
end
|
end
|
||||||
|
|
||||||
def patch_out
|
def patch_out
|
||||||
@ -359,7 +365,7 @@ module PatchELF
|
|||||||
elsif data.size < size
|
elsif data.size < size
|
||||||
data.ljust(size, "\x00")
|
data.ljust(size, "\x00")
|
||||||
else
|
else
|
||||||
data[0...size] + "\x00"
|
"#{data[0...size]}\x00"
|
||||||
end
|
end
|
||||||
@replaced_sections[section_name] = rep_data
|
@replaced_sections[section_name] = rep_data
|
||||||
end
|
end
|
||||||
@ -512,7 +518,7 @@ module PatchELF
|
|||||||
def copy_shdrs_to_eof
|
def copy_shdrs_to_eof
|
||||||
shoff_new = @buffer.size
|
shoff_new = @buffer.size
|
||||||
# honestly idk why `ehdr.e_shoff` is considered when we are only moving shdrs.
|
# honestly idk why `ehdr.e_shoff` is considered when we are only moving shdrs.
|
||||||
sh_size = ehdr.e_shoff + ehdr.e_shnum * ehdr.e_shentsize
|
sh_size = ehdr.e_shoff + (ehdr.e_shnum * ehdr.e_shentsize)
|
||||||
buf_grow! @buffer.size + sh_size
|
buf_grow! @buffer.size + sh_size
|
||||||
ehdr.e_shoff = shoff_new
|
ehdr.e_shoff = shoff_new
|
||||||
raise PatchError, 'ehdr.e_shnum != @sections.size' if ehdr.e_shnum != @sections.size
|
raise PatchError, 'ehdr.e_shnum != @sections.size' if ehdr.e_shnum != @sections.size
|
||||||
@ -529,11 +535,11 @@ module PatchELF
|
|||||||
def rewrite_sections_executable
|
def rewrite_sections_executable
|
||||||
sort_shdrs!
|
sort_shdrs!
|
||||||
shdr = start_replacement_shdr
|
shdr = start_replacement_shdr
|
||||||
start_offset = shdr.sh_offset
|
start_offset = shdr.sh_offset.to_i
|
||||||
start_addr = shdr.sh_addr
|
start_addr = shdr.sh_addr.to_i
|
||||||
first_page = start_addr - start_offset
|
first_page = start_addr - start_offset
|
||||||
|
|
||||||
Logger.debug "first reserved offset/addr is 0x#{start_offset.to_i.to_s 16}/0x#{start_addr.to_i.to_s 16}"
|
Logger.debug "first reserved offset/addr is 0x#{start_offset.to_s 16}/0x#{start_addr.to_s 16}"
|
||||||
|
|
||||||
unless start_addr % page_size == start_offset % page_size
|
unless start_addr % page_size == start_offset % page_size
|
||||||
raise PatchError, 'start_addr != start_offset (mod PAGE_SIZE)'
|
raise PatchError, 'start_addr != start_offset (mod PAGE_SIZE)'
|
||||||
@ -543,6 +549,8 @@ module PatchELF
|
|||||||
|
|
||||||
copy_shdrs_to_eof if ehdr.e_shoff < start_offset
|
copy_shdrs_to_eof if ehdr.e_shoff < start_offset
|
||||||
|
|
||||||
|
normalize_note_segments
|
||||||
|
|
||||||
seg_num_bytes = @segments.first.header.num_bytes
|
seg_num_bytes = @segments.first.header.num_bytes
|
||||||
needed_space = (
|
needed_space = (
|
||||||
ehdr.num_bytes +
|
ehdr.num_bytes +
|
||||||
@ -557,10 +565,10 @@ module PatchELF
|
|||||||
Logger.debug "needed pages is #{needed_pages}"
|
Logger.debug "needed pages is #{needed_pages}"
|
||||||
raise PatchError, 'virtual address space underrun' if needed_pages * page_size > first_page
|
raise PatchError, 'virtual address space underrun' if needed_pages * page_size > first_page
|
||||||
|
|
||||||
|
shift_file(needed_pages, start_offset)
|
||||||
|
|
||||||
first_page -= needed_pages * page_size
|
first_page -= needed_pages * page_size
|
||||||
start_offset += needed_pages * page_size
|
start_offset += needed_pages * page_size
|
||||||
|
|
||||||
shift_file(needed_pages, first_page)
|
|
||||||
end
|
end
|
||||||
Logger.debug "needed space is #{needed_space}"
|
Logger.debug "needed space is #{needed_space}"
|
||||||
|
|
||||||
@ -575,27 +583,31 @@ module PatchELF
|
|||||||
end
|
end
|
||||||
|
|
||||||
def replace_sections_in_the_way_of_phdr!
|
def replace_sections_in_the_way_of_phdr!
|
||||||
pht_size = ehdr.num_bytes + (@segments.count + 1) * @segments.first.header.num_bytes
|
num_notes = @sections.count { |sec| sec.type == ELFTools::Constants::SHT_NOTE }
|
||||||
|
pht_size = ehdr.num_bytes + ((@segments.count + num_notes + 1) * @segments.first.header.num_bytes)
|
||||||
|
|
||||||
# replace sections that may overlap with expanded program header table
|
# replace sections that may overlap with expanded program header table
|
||||||
@sections.each_with_index do |sec, idx|
|
@sections.each_with_index do |sec, idx|
|
||||||
shdr = sec.header
|
shdr = sec.header
|
||||||
next if idx.zero? || @replaced_sections[sec.name]
|
next if idx.zero? || @replaced_sections[sec.name]
|
||||||
break if shdr.sh_addr > pht_size
|
break if shdr.sh_offset > pht_size
|
||||||
|
|
||||||
replace_section sec.name, shdr.sh_size
|
replace_section sec.name, shdr.sh_size
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def seg_end_addr(seg)
|
def rewrite_sections_library
|
||||||
|
start_page = 0
|
||||||
|
first_page = 0
|
||||||
|
@segments.each do |seg|
|
||||||
phdr = seg.header
|
phdr = seg.header
|
||||||
Helper.alignup(phdr.p_vaddr + phdr.p_memsz, page_size)
|
this_page = Helper.alignup(phdr.p_vaddr + phdr.p_memsz, page_size)
|
||||||
|
start_page = [start_page, this_page].max
|
||||||
|
first_page = phdr.p_vaddr - phdr.p_offset if phdr.p_type == ELFTools::Constants::PT_PHDR
|
||||||
end
|
end
|
||||||
|
|
||||||
def rewrite_sections_library
|
|
||||||
start_page = seg_end_addr(@segments.max_by(&method(:seg_end_addr)))
|
|
||||||
|
|
||||||
Logger.debug "Last page is 0x#{start_page.to_s 16}"
|
Logger.debug "Last page is 0x#{start_page.to_s 16}"
|
||||||
|
Logger.debug "First page is 0x#{first_page.to_s 16}"
|
||||||
replace_sections_in_the_way_of_phdr!
|
replace_sections_in_the_way_of_phdr!
|
||||||
needed_space = @replaced_sections.sum { |_, str| Helper.alignup(str.size, @section_alignment) }
|
needed_space = @replaced_sections.sum { |_, str| Helper.alignup(str.size, @section_alignment) }
|
||||||
Logger.debug "needed space = #{needed_space}"
|
Logger.debug "needed space = #{needed_space}"
|
||||||
@ -623,42 +635,172 @@ module PatchELF
|
|||||||
p_align: page_size
|
p_align: page_size
|
||||||
)
|
)
|
||||||
|
|
||||||
|
normalize_note_segments
|
||||||
|
|
||||||
cur_off = write_replaced_sections start_offset, start_page, start_offset
|
cur_off = write_replaced_sections start_offset, start_page, start_offset
|
||||||
raise PatchError, 'cur_off != start_offset + needed_space' if cur_off != start_offset + needed_space
|
raise PatchError, 'cur_off != start_offset + needed_space' if cur_off != start_offset + needed_space
|
||||||
|
|
||||||
rewrite_headers ehdr.e_phoff
|
rewrite_headers(first_page + ehdr.e_phoff)
|
||||||
end
|
end
|
||||||
|
|
||||||
def shift_file(extra_pages, start_page)
|
def normalize_note_segments
|
||||||
oldsz = @buffer.size
|
return if @replaced_sections.none? do |rsec_name, _|
|
||||||
shift = extra_pages * page_size
|
find_section(rsec_name)&.type == ELFTools::Constants::SHT_NOTE
|
||||||
buf_grow!(oldsz + shift)
|
end
|
||||||
buf_move! shift, 0, oldsz
|
|
||||||
with_buf_at(ehdr.num_bytes) { |buf| buf.write "\x00" * (shift - ehdr.num_bytes) }
|
|
||||||
|
|
||||||
ehdr.e_phoff = ehdr.num_bytes
|
new_phdrs = []
|
||||||
ehdr.e_shoff = ehdr.e_shoff + shift
|
|
||||||
|
phdrs_by_type(ELFTools::Constants::PT_NOTE) do |phdr|
|
||||||
|
# Binaries produced by older patchelf versions may contain empty PT_NOTE segments.
|
||||||
|
next if @sections.none? do |sec|
|
||||||
|
sec.header.sh_offset >= phdr.p_offset && sec.header.sh_offset < phdr.p_offset + phdr.p_filesz
|
||||||
|
end
|
||||||
|
|
||||||
|
new_phdrs += normalize_note_segment(phdr)
|
||||||
|
end
|
||||||
|
|
||||||
|
new_phdrs.each { |phdr| add_segment!(**phdr.snapshot) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def normalize_note_segment(phdr)
|
||||||
|
start_off = phdr.p_offset.to_i
|
||||||
|
curr_off = start_off
|
||||||
|
end_off = start_off + phdr.p_filesz
|
||||||
|
|
||||||
|
new_phdrs = []
|
||||||
|
|
||||||
|
while curr_off < end_off
|
||||||
|
size = 0
|
||||||
|
sections_at_aligned_offset(curr_off) do |sec|
|
||||||
|
next if sec.type != ELFTools::Constants::SHT_NOTE
|
||||||
|
|
||||||
|
size = sec.header.sh_size.to_i
|
||||||
|
curr_off = sec.header.sh_offset.to_i
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
raise PatchError, 'cannot normalize PT_NOTE segment: non-contiguous SHT_NOTE sections' if size.zero?
|
||||||
|
|
||||||
|
if curr_off + size > end_off
|
||||||
|
raise PatchError, 'cannot normalize PT_NOTE segment: partially mapped SHT_NOTE section'
|
||||||
|
end
|
||||||
|
|
||||||
|
new_phdr = ELFTools::Structs::ELF_Phdr[elf_class].new(endian: endian, **phdr.snapshot)
|
||||||
|
new_phdr.p_offset = curr_off
|
||||||
|
new_phdr.p_vaddr = phdr.p_vaddr + (curr_off - start_off)
|
||||||
|
new_phdr.p_paddr = phdr.p_paddr + (curr_off - start_off)
|
||||||
|
new_phdr.p_filesz = size
|
||||||
|
new_phdr.p_memsz = size
|
||||||
|
|
||||||
|
if curr_off == start_off
|
||||||
|
phdr.assign(new_phdr)
|
||||||
|
else
|
||||||
|
new_phdrs << new_phdr
|
||||||
|
end
|
||||||
|
|
||||||
|
curr_off += size
|
||||||
|
end
|
||||||
|
|
||||||
|
new_phdrs
|
||||||
|
end
|
||||||
|
|
||||||
|
def sections_at_aligned_offset(offset)
|
||||||
|
@sections.each do |sec|
|
||||||
|
shdr = sec.header
|
||||||
|
|
||||||
|
aligned_offset = Helper.alignup(offset, shdr.sh_addralign)
|
||||||
|
next if shdr.sh_offset != aligned_offset
|
||||||
|
|
||||||
|
yield sec
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def shift_sections(shift, start_offset)
|
||||||
|
ehdr.e_shoff += shift if ehdr.e_shoff >= start_offset
|
||||||
|
|
||||||
@sections.each_with_index do |sec, i|
|
@sections.each_with_index do |sec, i|
|
||||||
next if i.zero? # dont touch NULL section
|
next if i.zero? # dont touch NULL section
|
||||||
|
|
||||||
shdr = sec.header
|
shdr = sec.header
|
||||||
|
next if shdr.sh_offset < start_offset
|
||||||
|
|
||||||
shdr.sh_offset += shift
|
shdr.sh_offset += shift
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@segments.each do |seg|
|
def shift_segment_offset(phdr, shift)
|
||||||
phdr = seg.header
|
|
||||||
phdr.p_offset += shift
|
phdr.p_offset += shift
|
||||||
phdr.p_align = page_size if phdr.p_align != 0 && (phdr.p_vaddr - phdr.p_offset) % phdr.p_align != 0
|
phdr.p_align = page_size if phdr.p_align != 0 && (phdr.p_vaddr - phdr.p_offset) % phdr.p_align != 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def shift_segment_virtual_address(phdr, shift)
|
||||||
|
phdr.p_paddr -= shift if phdr.p_paddr > shift
|
||||||
|
phdr.p_vaddr -= shift if phdr.p_vaddr > shift
|
||||||
|
end
|
||||||
|
|
||||||
|
# rubocop:disable Metrics/PerceivedComplexity
|
||||||
|
def shift_segments(shift, start_offset)
|
||||||
|
split_index = -1
|
||||||
|
split_shift = 0
|
||||||
|
|
||||||
|
@segments.each_with_index do |seg, idx|
|
||||||
|
phdr = seg.header
|
||||||
|
p_start = phdr.p_offset
|
||||||
|
|
||||||
|
if p_start <= start_offset && p_start + phdr.p_filesz > start_offset &&
|
||||||
|
phdr.p_type == ELFTools::Constants::PT_LOAD
|
||||||
|
raise PatchError, "split_index(#{split_index}) != -1" if split_index != -1
|
||||||
|
|
||||||
|
split_index = idx
|
||||||
|
split_shift = start_offset - p_start
|
||||||
|
|
||||||
|
phdr.p_offset = start_offset
|
||||||
|
phdr.p_memsz -= split_shift
|
||||||
|
phdr.p_filesz -= split_shift
|
||||||
|
phdr.p_paddr += split_shift
|
||||||
|
phdr.p_vaddr += split_shift
|
||||||
|
|
||||||
|
p_start = start_offset
|
||||||
|
end
|
||||||
|
|
||||||
|
if p_start >= start_offset
|
||||||
|
shift_segment_offset(phdr, shift)
|
||||||
|
else
|
||||||
|
shift_segment_virtual_address(phdr, shift)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
raise PatchError, "split_index(#{split_index}) == -1" if split_index == -1
|
||||||
|
|
||||||
|
[split_index, split_shift]
|
||||||
|
end
|
||||||
|
# rubocop:enable Metrics/PerceivedComplexity
|
||||||
|
|
||||||
|
def shift_file(extra_pages, start_offset)
|
||||||
|
raise PatchError, "start_offset(#{start_offset}) < ehdr.num_bytes" if start_offset < ehdr.num_bytes
|
||||||
|
|
||||||
|
oldsz = @buffer.size
|
||||||
|
raise PatchError, "oldsz <= start_offset(#{start_offset})" if oldsz <= start_offset
|
||||||
|
|
||||||
|
shift = extra_pages * page_size
|
||||||
|
buf_grow!(oldsz + shift)
|
||||||
|
buf_move!(start_offset + shift, start_offset, oldsz - start_offset)
|
||||||
|
with_buf_at(start_offset) { |buf| buf.write "\x00" * shift }
|
||||||
|
|
||||||
|
ehdr.e_phoff = ehdr.num_bytes
|
||||||
|
|
||||||
|
shift_sections(shift, start_offset)
|
||||||
|
|
||||||
|
split_index, split_shift = shift_segments(shift, start_offset)
|
||||||
|
|
||||||
|
split_phdr = @segments[split_index].header
|
||||||
add_segment!(
|
add_segment!(
|
||||||
p_type: ELFTools::Constants::PT_LOAD,
|
p_type: ELFTools::Constants::PT_LOAD,
|
||||||
p_offset: 0,
|
p_offset: split_phdr.p_offset - split_shift - shift,
|
||||||
p_vaddr: start_page,
|
p_vaddr: split_phdr.p_vaddr - split_shift - shift,
|
||||||
p_paddr: start_page,
|
p_paddr: split_phdr.p_paddr - split_shift - shift,
|
||||||
p_filesz: shift,
|
p_filesz: split_shift + shift,
|
||||||
p_memsz: shift,
|
p_memsz: split_shift + shift,
|
||||||
p_flags: ELFTools::Constants::PF_R | ELFTools::Constants::PF_W,
|
p_flags: ELFTools::Constants::PF_R | ELFTools::Constants::PF_W,
|
||||||
p_align: page_size
|
p_align: page_size
|
||||||
)
|
)
|
||||||
@ -699,12 +841,23 @@ module PatchELF
|
|||||||
end
|
end
|
||||||
|
|
||||||
def sort_shdrs!
|
def sort_shdrs!
|
||||||
|
return if @sections.empty?
|
||||||
|
|
||||||
section_dep_values = collect_section_to_section_refs
|
section_dep_values = collect_section_to_section_refs
|
||||||
shstrtab_name = @sections[ehdr.e_shstrndx].name
|
shstrtab = @sections[ehdr.e_shstrndx].header
|
||||||
@sections.sort! { |me, you| me.header.sh_offset.to_i <=> you.header.sh_offset.to_i }
|
@sections.sort! { |me, you| me.header.sh_offset.to_i <=> you.header.sh_offset.to_i }
|
||||||
update_section_idx!
|
update_section_idx!
|
||||||
restore_section_to_section_refs!(section_dep_values)
|
restore_section_to_section_refs!(section_dep_values)
|
||||||
ehdr.e_shstrndx = find_section_idx shstrtab_name
|
@sections.each_with_index do |sec, idx|
|
||||||
|
ehdr.e_shstrndx = idx if sec.header.sh_offset == shstrtab.sh_offset
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def jmprel_section_name
|
||||||
|
sec_name = %w[.rel.plt .rela.plt .rela.IA_64.pltoff].find { |s| find_section(s) }
|
||||||
|
raise PatchError, 'cannot find section corresponding to DT_JMPREL' unless sec_name
|
||||||
|
|
||||||
|
sec_name
|
||||||
end
|
end
|
||||||
|
|
||||||
# given a +dyn.d_tag+, returns the section name it must be synced to.
|
# given a +dyn.d_tag+, returns the section name it must be synced to.
|
||||||
@ -719,12 +872,14 @@ module PatchELF
|
|||||||
when ELFTools::Constants::DT_HASH
|
when ELFTools::Constants::DT_HASH
|
||||||
'.hash'
|
'.hash'
|
||||||
when ELFTools::Constants::DT_GNU_HASH
|
when ELFTools::Constants::DT_GNU_HASH
|
||||||
'.gnu.hash'
|
# return nil if not found, patchelf claims no problem in skipping
|
||||||
when ELFTools::Constants::DT_JMPREL
|
find_section('.gnu.hash')&.name
|
||||||
sec_name = %w[.rel.plt .rela.plt .rela.IA_64.pltoff].find { |s| find_section(s) }
|
when ELFTools::Constants::DT_MIPS_XHASH
|
||||||
raise PatchError, 'cannot find section corresponding to DT_JMPREL' unless sec_name
|
return if ehdr.e_machine != ELFTools::Constants::EM_MIPS
|
||||||
|
|
||||||
sec_name
|
'.MIPS.xhash'
|
||||||
|
when ELFTools::Constants::DT_JMPREL
|
||||||
|
jmprel_section_name
|
||||||
when ELFTools::Constants::DT_REL
|
when ELFTools::Constants::DT_REL
|
||||||
# regarding .rel.got, NixOS/patchelf says
|
# regarding .rel.got, NixOS/patchelf says
|
||||||
# "no idea if this makes sense, but it was needed for some program"
|
# "no idea if this makes sense, but it was needed for some program"
|
||||||
@ -743,9 +898,26 @@ module PatchELF
|
|||||||
|
|
||||||
# updates dyn tags by syncing it with @section values
|
# updates dyn tags by syncing it with @section values
|
||||||
def sync_dyn_tags!
|
def sync_dyn_tags!
|
||||||
|
dyn_table_offset = nil
|
||||||
each_dynamic_tags do |dyn, buf_off|
|
each_dynamic_tags do |dyn, buf_off|
|
||||||
|
dyn_table_offset ||= buf_off
|
||||||
|
|
||||||
sec_name = dyn_tag_to_section_name(dyn.d_tag)
|
sec_name = dyn_tag_to_section_name(dyn.d_tag)
|
||||||
next unless sec_name
|
|
||||||
|
unless sec_name
|
||||||
|
if dyn.d_tag == ELFTools::Constants::DT_MIPS_RLD_MAP_REL && ehdr.e_machine == ELFTools::Constants::EM_MIPS
|
||||||
|
rld_map = find_section('.rld_map')
|
||||||
|
dyn.d_val = if rld_map
|
||||||
|
rld_map.header.sh_addr.to_i - (buf_off - dyn_table_offset) -
|
||||||
|
find_section('.dynamic').header.sh_addr.to_i
|
||||||
|
else
|
||||||
|
Logger.warn 'DT_MIPS_RLD_MAP_REL entry is present, but .rld_map section is not'
|
||||||
|
0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
shdr = find_section(sec_name).header
|
shdr = find_section(sec_name).header
|
||||||
dyn.d_val = dyn.d_tag == ELFTools::Constants::DT_STRSZ ? shdr.sh_size.to_i : shdr.sh_addr.to_i
|
dyn.d_val = dyn.d_tag == ELFTools::Constants::DT_STRSZ ? shdr.sh_size.to_i : shdr.sh_addr.to_i
|
||||||
@ -784,22 +956,46 @@ module PatchELF
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def write_replaced_sections(cur_off, start_addr, start_offset)
|
# Returns a blank shdr if the section doesn't exist.
|
||||||
sht_no_bits = ELFTools::Constants::SHT_NOBITS
|
def find_or_create_section_header(rsec_name)
|
||||||
|
shdr = find_section(rsec_name)&.header
|
||||||
|
shdr ||= ELFTools::Structs::ELF_Shdr.new(endian: endian, elf_class: elf_class)
|
||||||
|
shdr
|
||||||
|
end
|
||||||
|
|
||||||
# the original source says this has to be done seperately to
|
def overwrite_replaced_sections
|
||||||
|
# the original source says this has to be done separately to
|
||||||
# prevent clobbering the previously written section contents.
|
# prevent clobbering the previously written section contents.
|
||||||
@replaced_sections.each do |rsec_name, _|
|
@replaced_sections.each do |rsec_name, _|
|
||||||
shdr = find_section(rsec_name).header
|
shdr = find_section(rsec_name)&.header
|
||||||
with_buf_at(shdr.sh_offset) { |b| b.fill('X', shdr.sh_size) } if shdr.sh_type != sht_no_bits
|
next unless shdr
|
||||||
|
|
||||||
|
next if shdr.sh_type == ELFTools::Constants::SHT_NOBITS
|
||||||
|
|
||||||
|
with_buf_at(shdr.sh_offset) { |b| b.fill('X', shdr.sh_size) }
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_section_aligment(shdr)
|
||||||
|
return if shdr.sh_type == ELFTools::Constants::SHT_NOTE && shdr.sh_addralign <= @section_alignment
|
||||||
|
|
||||||
|
shdr.sh_addralign = @section_alignment
|
||||||
|
end
|
||||||
|
|
||||||
|
def section_bounds_within_segment?(s_start, s_end, p_start, p_end)
|
||||||
|
(s_start >= p_start && s_start < p_end) || (s_end > p_start && s_end <= p_end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_replaced_sections(cur_off, start_addr, start_offset)
|
||||||
|
overwrite_replaced_sections
|
||||||
|
|
||||||
|
noted_phdrs = Set.new
|
||||||
|
|
||||||
# the sort is necessary, the strategy in ruby and Cpp to iterate map/hash
|
# the sort is necessary, the strategy in ruby and Cpp to iterate map/hash
|
||||||
# is different, patchelf v0.10 iterates the replaced_sections sorted by
|
# is different, patchelf v0.10 iterates the replaced_sections sorted by
|
||||||
# keys.
|
# keys.
|
||||||
@replaced_sections.sort.each do |rsec_name, rsec_data|
|
@replaced_sections.sort.each do |rsec_name, rsec_data|
|
||||||
section = find_section(rsec_name)
|
shdr = find_or_create_section_header(rsec_name)
|
||||||
shdr = section.header
|
|
||||||
|
|
||||||
Logger.debug <<~DEBUG
|
Logger.debug <<~DEBUG
|
||||||
rewriting section '#{rsec_name}'
|
rewriting section '#{rsec_name}'
|
||||||
@ -809,18 +1005,43 @@ module PatchELF
|
|||||||
|
|
||||||
with_buf_at(cur_off) { |b| b.write rsec_data }
|
with_buf_at(cur_off) { |b| b.write rsec_data }
|
||||||
|
|
||||||
|
orig_sh_offset = shdr.sh_offset.to_i
|
||||||
|
orig_sh_size = shdr.sh_size.to_i
|
||||||
|
|
||||||
shdr.sh_offset = cur_off
|
shdr.sh_offset = cur_off
|
||||||
shdr.sh_addr = start_addr + (cur_off - start_offset)
|
shdr.sh_addr = start_addr + (cur_off - start_offset)
|
||||||
shdr.sh_size = rsec_data.size
|
shdr.sh_size = rsec_data.size
|
||||||
shdr.sh_addralign = @section_alignment
|
|
||||||
|
write_section_aligment(shdr)
|
||||||
|
|
||||||
seg_type = {
|
seg_type = {
|
||||||
'.interp' => ELFTools::Constants::PT_INTERP,
|
'.interp' => ELFTools::Constants::PT_INTERP,
|
||||||
'.dynamic' => ELFTools::Constants::PT_DYNAMIC
|
'.dynamic' => ELFTools::Constants::PT_DYNAMIC,
|
||||||
}[section.name]
|
'.MIPS.abiflags' => ELFTools::Constants::PT_MIPS_ABIFLAGS,
|
||||||
|
'.note.gnu.property' => ELFTools::Constants::PT_GNU_PROPERTY
|
||||||
|
}[rsec_name]
|
||||||
|
|
||||||
phdrs_by_type(seg_type) { |phdr| sync_sec_to_seg(shdr, phdr) }
|
phdrs_by_type(seg_type) { |phdr| sync_sec_to_seg(shdr, phdr) }
|
||||||
|
|
||||||
|
if shdr.sh_type == ELFTools::Constants::SHT_NOTE
|
||||||
|
phdrs_by_type(ELFTools::Constants::PT_NOTE) do |phdr, idx|
|
||||||
|
next if noted_phdrs.include?(idx)
|
||||||
|
|
||||||
|
s_start = orig_sh_offset
|
||||||
|
s_end = s_start + orig_sh_size
|
||||||
|
p_start = phdr.p_offset
|
||||||
|
p_end = p_start + phdr.p_filesz
|
||||||
|
|
||||||
|
next unless section_bounds_within_segment?(s_start, s_end, p_start, p_end)
|
||||||
|
|
||||||
|
raise PatchError, 'unsupported overlap of SHT_NOTE and PT_NOTE' if p_start != s_start || p_end != s_end
|
||||||
|
|
||||||
|
sync_sec_to_seg(shdr, phdr)
|
||||||
|
|
||||||
|
noted_phdrs << idx
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
cur_off += Helper.alignup(rsec_data.size, @section_alignment)
|
cur_off += Helper.alignup(rsec_data.size, @section_alignment)
|
||||||
end
|
end
|
||||||
@replaced_sections.clear
|
@replaced_sections.clear
|
||||||
@ -139,7 +139,7 @@ module PatchELF
|
|||||||
@options[:set][:soname] = soname
|
@options[:set][:soname] = soname
|
||||||
end
|
end
|
||||||
|
|
||||||
opts.on('--version', 'Show current gem\'s version.') {}
|
opts.on('--version', 'Show current gem\'s version.')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -6,8 +6,10 @@ require 'elftools/exceptions'
|
|||||||
module PatchELF
|
module PatchELF
|
||||||
# Raised on an error during ELF modification.
|
# Raised on an error during ELF modification.
|
||||||
class PatchError < ELFTools::ELFError; end
|
class PatchError < ELFTools::ELFError; end
|
||||||
|
|
||||||
# Raised when Dynamic Tag is missing
|
# Raised when Dynamic Tag is missing
|
||||||
class MissingTagError < PatchError; end
|
class MissingTagError < PatchError; end
|
||||||
|
|
||||||
# Raised on missing Program Header(segment)
|
# Raised on missing Program Header(segment)
|
||||||
class MissingSegmentError < PatchError; end
|
class MissingSegmentError < PatchError; end
|
||||||
end
|
end
|
||||||
@ -3,9 +3,6 @@
|
|||||||
module PatchELF
|
module PatchELF
|
||||||
# Helper methods for internal usage.
|
# Helper methods for internal usage.
|
||||||
module Helper
|
module Helper
|
||||||
# The size of one page.
|
|
||||||
PAGE_SIZE = 0x1000
|
|
||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
# Color codes for pretty print.
|
# Color codes for pretty print.
|
||||||
@ -16,6 +13,23 @@ module PatchELF
|
|||||||
error: "\e[38;5;196m" # heavy red
|
error: "\e[38;5;196m" # heavy red
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
|
# The size of one page.
|
||||||
|
def page_size(e_machine = nil)
|
||||||
|
# Different architectures have different minimum section alignments.
|
||||||
|
case e_machine
|
||||||
|
when ELFTools::Constants::EM_SPARC,
|
||||||
|
ELFTools::Constants::EM_MIPS,
|
||||||
|
ELFTools::Constants::EM_PPC,
|
||||||
|
ELFTools::Constants::EM_PPC64,
|
||||||
|
ELFTools::Constants::EM_AARCH64,
|
||||||
|
ELFTools::Constants::EM_TILEGX,
|
||||||
|
ELFTools::Constants::EM_LOONGARCH
|
||||||
|
0x10000
|
||||||
|
else
|
||||||
|
0x1000
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# For wrapping string with color codes for prettier inspect.
|
# For wrapping string with color codes for prettier inspect.
|
||||||
# @param [String] str
|
# @param [String] str
|
||||||
# Content to colorize.
|
# Content to colorize.
|
||||||
@ -48,7 +62,7 @@ module PatchELF
|
|||||||
# #=> 32
|
# #=> 32
|
||||||
# aligndown(0x10, 0x8)
|
# aligndown(0x10, 0x8)
|
||||||
# #=> 16
|
# #=> 16
|
||||||
def aligndown(val, align = PAGE_SIZE)
|
def aligndown(val, align = page_size)
|
||||||
val - (val & (align - 1))
|
val - (val & (align - 1))
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -63,7 +77,7 @@ module PatchELF
|
|||||||
# #=> 64
|
# #=> 64
|
||||||
# alignup(0x10, 0x8)
|
# alignup(0x10, 0x8)
|
||||||
# #=> 16
|
# #=> 16
|
||||||
def alignup(val, align = PAGE_SIZE)
|
def alignup(val, align = page_size)
|
||||||
(val & (align - 1)).zero? ? val : (aligndown(val, align) + align)
|
(val & (align - 1)).zero? ? val : (aligndown(val, align) + align)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -115,7 +115,7 @@ module PatchELF
|
|||||||
# prefer backward than forward
|
# prefer backward than forward
|
||||||
return extend_backward(loads[idx - 1]) if writable?(loads[idx - 1])
|
return extend_backward(loads[idx - 1]) if writable?(loads[idx - 1])
|
||||||
|
|
||||||
# note: loads[idx].file_head has been changed in shift_attributes
|
# NOTE: loads[idx].file_head has been changed in shift_attributes
|
||||||
extend_forward(loads[idx], @extend_size)
|
extend_forward(loads[idx], @extend_size)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -161,7 +161,7 @@ module PatchELF
|
|||||||
|
|
||||||
seg.header.p_offset += extend_size
|
seg.header.p_offset += extend_size
|
||||||
# We have to change align of LOAD segment since ld.so checks it.
|
# We have to change align of LOAD segment since ld.so checks it.
|
||||||
seg.header.p_align = Helper::PAGE_SIZE if seg.is_a?(ELFTools::Segments::LoadSegment)
|
seg.header.p_align = Helper.page_size if seg.is_a?(ELFTools::Segments::LoadSegment)
|
||||||
end
|
end
|
||||||
|
|
||||||
@elf.header.e_shoff += extend_size if @elf.header.e_shoff >= threshold
|
@elf.header.e_shoff += extend_size if @elf.header.e_shoff >= threshold
|
||||||
@ -30,7 +30,7 @@ module PatchELF
|
|||||||
@elf = ELFTools::ELFFile.new(File.open(filename))
|
@elf = ELFTools::ELFFile.new(File.open(filename))
|
||||||
@set = {}
|
@set = {}
|
||||||
@rpath_sym = :runpath
|
@rpath_sym = :runpath
|
||||||
@on_error = !logging ? :exception : on_error
|
@on_error = logging ? on_error : :exception
|
||||||
|
|
||||||
on_error_syms = %i[exception log silent]
|
on_error_syms = %i[exception log silent]
|
||||||
raise ArgumentError, "on_error must be one of #{on_error_syms}" unless on_error_syms.include?(@on_error)
|
raise ArgumentError, "on_error must be one of #{on_error_syms}" unless on_error_syms.include?(@on_error)
|
||||||
@ -53,8 +53,8 @@ module PatchELF
|
|||||||
def patch_interpreter
|
def patch_interpreter
|
||||||
return if @set[:interpreter].nil?
|
return if @set[:interpreter].nil?
|
||||||
|
|
||||||
new_interp = @set[:interpreter] + "\x00"
|
new_interp = "#{@set[:interpreter]}\x00"
|
||||||
old_interp = @elf.segment_by_type(:interp).interp_name + "\x00"
|
old_interp = "#{@elf.segment_by_type(:interp).interp_name}\x00"
|
||||||
return if old_interp == new_interp
|
return if old_interp == new_interp
|
||||||
|
|
||||||
# These headers must be found here but not in the proc.
|
# These headers must be found here but not in the proc.
|
||||||
@ -184,7 +184,7 @@ module PatchELF
|
|||||||
need_size = strtab_string.size + @strtab_extend_requests.reduce(0) { |sum, (str, _)| sum + str.size + 1 }
|
need_size = strtab_string.size + @strtab_extend_requests.reduce(0) { |sum, (str, _)| sum + str.size + 1 }
|
||||||
dynstr = section_header('.dynstr')
|
dynstr = section_header('.dynstr')
|
||||||
@mm.malloc(need_size) do |off, vaddr|
|
@mm.malloc(need_size) do |off, vaddr|
|
||||||
new_str = strtab_string + @strtab_extend_requests.map(&:first).join("\x00") + "\x00"
|
new_str = "#{strtab_string}#{@strtab_extend_requests.map(&:first).join("\x00")}\x00"
|
||||||
inline_patch(off, new_str)
|
inline_patch(off, new_str)
|
||||||
cur = strtab_string.size
|
cur = strtab_string.size
|
||||||
@strtab_extend_requests.each do |str, block|
|
@strtab_extend_requests.each do |str, block|
|
||||||
@ -206,7 +206,7 @@ module PatchELF
|
|||||||
# @yieldparam [Integer] idx
|
# @yieldparam [Integer] idx
|
||||||
# @yieldreturn [void]
|
# @yieldreturn [void]
|
||||||
def reg_str_table(str, &block)
|
def reg_str_table(str, &block)
|
||||||
idx = strtab_string.index(str + "\x00")
|
idx = strtab_string.index("#{str}\x00")
|
||||||
# Request string is already exist
|
# Request string is already exist
|
||||||
return yield idx if idx
|
return yield idx if idx
|
||||||
|
|
||||||
@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
module PatchELF
|
module PatchELF
|
||||||
# Current gem version.
|
# Current gem version.
|
||||||
VERSION = '1.3.0'.freeze
|
VERSION = '1.4.0'.freeze
|
||||||
end
|
end
|
||||||
Loading…
x
Reference in New Issue
Block a user