class PatchELF::MM
Memory management, provides malloc/free to allocate LOAD segments. @private
Attributes
extend_size[R]
threshold[R]
Public Class Methods
new(elf)
click to toggle source
Instantiate a {MM} object. @param [ELFTools::ELFFile] elf
# File lib/patchelf/mm.rb, line 14 def initialize(elf) @elf = elf @request = [] end
Public Instance Methods
dispatch!()
click to toggle source
Let the malloc / free requests be effective. @return [void]
# File lib/patchelf/mm.rb, line 35 def dispatch! return if @request.empty? @request_size = @request.map(&:first).inject(0, :+) # The malloc-ed area must be 'rw-' since the dynamic table will be modified during runtime. # Find all LOADs and calculate their f-gaps and m-gaps. # We prefer f-gap since it doesn't need move the whole binaries. # 1. Find if any f-gap has enough size, and one of the LOAD next to it is 'rw-'. # - expand (forwardlly), only need to change the attribute of LOAD. # 2. Do 1. again but consider m-gaps instead. # - expand (forwardlly), need to modify all section headers. # 3. We have to create a new LOAD, now we need to expand the first LOAD for putting new segment header. # First of all we check if there're less than two LOADs. abnormal_elf('No LOAD segment found, not an executable.') if load_segments.empty? # TODO: Handle only one LOAD. (be careful if memsz > filesz) fgap_method || mgap_method || new_load_method end
extended?()
click to toggle source
Query if extended. @return [Boolean]
# File lib/patchelf/mm.rb, line 57 def extended? defined?(@threshold) end
extended_offset(off)
click to toggle source
Get correct offset after the extension.
@param [Integer] off @return [Integer]
Shifted offset.
# File lib/patchelf/mm.rb, line 66 def extended_offset(off) return off unless defined?(@threshold) return off if off < @threshold off + @extend_size end
malloc(size, &block)
click to toggle source
@param [Integer] size @return [void] @yieldparam [Integer] off @yieldparam [Integer] vaddr @yieldreturn [void]
One can only do the following things in the block: 1. Set ELF headers' attributes (with ELFTools) 2. Invoke {Saver#inline_patch}
# File lib/patchelf/mm.rb, line 27 def malloc(size, &block) raise ArgumentError, 'malloc\'s size most be positive.' if size <= 0 @request << [size, block] end
Private Instance Methods
abnormal_elf(msg)
click to toggle source
# File lib/patchelf/mm.rb, line 182 def abnormal_elf(msg) raise ArgumentError, msg end
extend_backward(seg, size = @request_size)
click to toggle source
# File lib/patchelf/mm.rb, line 86 def extend_backward(seg, size = @request_size) invoke_callbacks(seg, seg.file_tail) seg.header.p_filesz += size seg.header.p_memsz += size true end
extend_forward(seg, size = @request_size)
click to toggle source
# File lib/patchelf/mm.rb, line 93 def extend_forward(seg, size = @request_size) seg.header.p_offset -= size seg.header.p_vaddr -= size seg.header.p_filesz += size seg.header.p_memsz += size invoke_callbacks(seg, seg.file_head) true end
fgap_method()
click to toggle source
# File lib/patchelf/mm.rb, line 75 def fgap_method idx = find_gap { |prv, nxt| nxt.file_head - prv.file_tail } return false if idx.nil? loads = load_segments # prefer extend backwardly return extend_backward(loads[idx - 1]) if writable?(loads[idx - 1]) extend_forward(loads[idx]) end
find_gap(check_sz: true) { |loads, l| ... }
click to toggle source
# File lib/patchelf/mm.rb, line 122 def find_gap(check_sz: true) loads = load_segments loads.each_with_index do |l, i| next if i.zero? next unless writable?(l) || writable?(loads[i - 1]) sz = yield(loads[i - 1], l) abnormal_elf('LOAD segments are out of order.') if check_sz && sz.negative? next unless sz >= @request_size return i end nil end
invoke_callbacks(seg, start)
click to toggle source
# File lib/patchelf/mm.rb, line 174 def invoke_callbacks(seg, start) cur = start @request.each do |sz, block| block.call(cur, seg.offset_to_vma(cur)) cur += sz end end
load_segments()
click to toggle source
# File lib/patchelf/mm.rb, line 170 def load_segments @elf.segments_by_type(:load) end
mgap_method()
click to toggle source
# File lib/patchelf/mm.rb, line 102 def mgap_method # | 1 | | 2 | # | 1 | | 2 | #=> # | 1 | | 2 | # | 1 | | 2 | idx = find_gap(check_sz: false) { |prv, nxt| PatchELF::Helper.aligndown(nxt.mem_head) - prv.mem_tail } return false if idx.nil? loads = load_segments @threshold = loads[idx].file_head @extend_size = PatchELF::Helper.alignup(@request_size) shift_attributes # prefer backward than forward return extend_backward(loads[idx - 1]) if writable?(loads[idx - 1]) # note: loads[idx].file_head has been changed in shift_attributes extend_forward(loads[idx], @extend_size) end
new_load_method()
click to toggle source
TODO
# File lib/patchelf/mm.rb, line 138 def new_load_method raise NotImplementedError end
shift_attributes()
click to toggle source
For all attributes >= threshold, += offset
# File lib/patchelf/mm.rb, line 147 def shift_attributes # ELFHeader->section_header # Sections: # all # Segments: # all # XXX: will be buggy if someday the number of segments can be changed. # Bottom-up @elf.each_sections do |sec| sec.header.sh_offset += extend_size if sec.header.sh_offset >= threshold end @elf.each_segments do |seg| next unless seg.header.p_offset >= threshold seg.header.p_offset += extend_size # 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) end @elf.header.e_shoff += extend_size if @elf.header.e_shoff >= threshold end
writable?(seg)
click to toggle source
# File lib/patchelf/mm.rb, line 142 def writable?(seg) seg.readable? && seg.writable? end