patchelf 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/patchelf/alt_saver.rb +1052 -0
- data/lib/patchelf/cli.rb +1 -1
- data/lib/patchelf/exceptions.rb +2 -0
- data/lib/patchelf/helper.rb +19 -5
- data/lib/patchelf/logger.rb +1 -1
- data/lib/patchelf/mm.rb +2 -2
- data/lib/patchelf/patcher.rb +10 -3
- data/lib/patchelf/saver.rb +18 -21
- data/lib/patchelf/version.rb +1 -1
- metadata +21 -13
@@ -0,0 +1,1052 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'elftools/constants'
|
4
|
+
require 'elftools/elf_file'
|
5
|
+
require 'elftools/structs'
|
6
|
+
require 'elftools/util'
|
7
|
+
require 'fileutils'
|
8
|
+
|
9
|
+
require 'patchelf/helper'
|
10
|
+
|
11
|
+
# :nodoc:
|
12
|
+
module PatchELF
|
13
|
+
# TODO: refactor buf_* methods here
|
14
|
+
# TODO: move all refinements into a separate file / helper file.
|
15
|
+
# refinements for cleaner syntax / speed / memory optimizations
|
16
|
+
module Refinements
|
17
|
+
refine StringIO do
|
18
|
+
# behaves like C memset. Equivalent to calling stream.write(char * nbytes)
|
19
|
+
# the benefit of preferring this over `stream.write(char * nbytes)` is only when data to be written is large.
|
20
|
+
# @param [String] char
|
21
|
+
# @param [Integer] nbytes
|
22
|
+
# @return[void]
|
23
|
+
def fill(char, nbytes)
|
24
|
+
at_once = Helper.page_size
|
25
|
+
pending = nbytes
|
26
|
+
|
27
|
+
if pending > at_once
|
28
|
+
to_write = char * at_once
|
29
|
+
while pending >= at_once
|
30
|
+
write(to_write)
|
31
|
+
pending -= at_once
|
32
|
+
end
|
33
|
+
end
|
34
|
+
write(char * pending) if pending.positive?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
using Refinements
|
39
|
+
|
40
|
+
# Internal use only.
|
41
|
+
# alternative to +Saver+, that aims to be byte to byte equivalent with NixOS/patchelf.
|
42
|
+
#
|
43
|
+
# *DISCLAIMER*: This differs from +Saver+ in number of ways. No lazy reading,
|
44
|
+
# inconsistent use of existing internal API(e.g: manual reading of data instead of calling +section.data+)
|
45
|
+
# @private
|
46
|
+
class AltSaver
|
47
|
+
attr_reader :in_file # @return [String] Input filename.
|
48
|
+
attr_reader :out_file # @return [String] Output filename.
|
49
|
+
|
50
|
+
# Instantiate a {AltSaver} object.
|
51
|
+
# the params passed are the same as the ones passed to +Saver+
|
52
|
+
# @param [String] in_file
|
53
|
+
# @param [String] out_file
|
54
|
+
# @param [{Symbol => String, Array}] set
|
55
|
+
def initialize(in_file, out_file, set)
|
56
|
+
@in_file = in_file
|
57
|
+
@out_file = out_file
|
58
|
+
@set = set
|
59
|
+
|
60
|
+
f = File.open(in_file, 'rb')
|
61
|
+
# the +@buffer+ and +@elf+ both could work on same +StringIO+ stream,
|
62
|
+
# the updating of @buffer in place blocks us from looking up old values.
|
63
|
+
# TODO: cache the values needed later, use same stream for +@buffer+ and +@elf+.
|
64
|
+
# also be sure to update the stream offset passed to Segments::Segment.
|
65
|
+
@elf = ELFTools::ELFFile.new(f)
|
66
|
+
@buffer = StringIO.new(f.tap(&:rewind).read) # StringIO makes easier to work with Bindata
|
67
|
+
|
68
|
+
@ehdr = @elf.header
|
69
|
+
@endian = @elf.endian
|
70
|
+
@elf_class = @elf.elf_class
|
71
|
+
|
72
|
+
@segments = @elf.segments # usage similar to phdrs
|
73
|
+
@sections = @elf.sections # usage similar to shdrs
|
74
|
+
update_section_idx!
|
75
|
+
|
76
|
+
# {String => String}
|
77
|
+
# section name to its data mapping
|
78
|
+
@replaced_sections = {}
|
79
|
+
@section_alignment = ehdr.e_phoff.num_bytes
|
80
|
+
|
81
|
+
# using the same environment flag as patchelf, makes it easier for debugging
|
82
|
+
Logger.level = ::Logger.const_get(ENV['PATCHELF_DEBUG'] ? :DEBUG : :WARN)
|
83
|
+
end
|
84
|
+
|
85
|
+
# @return [void]
|
86
|
+
def save!
|
87
|
+
@set.each { |mtd, val| send(:"modify_#{mtd}") if val }
|
88
|
+
rewrite_sections
|
89
|
+
|
90
|
+
FileUtils.cp(in_file, out_file) if out_file != in_file
|
91
|
+
patch_out
|
92
|
+
# Let output file have the same permission as input.
|
93
|
+
FileUtils.chmod(File.stat(in_file).mode, out_file)
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
attr_reader :ehdr, :endian, :elf_class
|
99
|
+
|
100
|
+
def old_sections
|
101
|
+
@old_sections ||= @elf.sections
|
102
|
+
end
|
103
|
+
|
104
|
+
def buf_cstr(off)
|
105
|
+
cstr = []
|
106
|
+
with_buf_at(off) do |buf|
|
107
|
+
loop do
|
108
|
+
c = buf.read 1
|
109
|
+
break if c.nil? || c == "\x00"
|
110
|
+
|
111
|
+
cstr.push c
|
112
|
+
end
|
113
|
+
end
|
114
|
+
cstr.join
|
115
|
+
end
|
116
|
+
|
117
|
+
def buf_move!(dst_idx, src_idx, n_bytes)
|
118
|
+
with_buf_at(src_idx) do |buf|
|
119
|
+
to_write = buf.read(n_bytes)
|
120
|
+
buf.seek dst_idx
|
121
|
+
buf.write to_write
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def dynstr
|
126
|
+
find_section '.dynstr'
|
127
|
+
end
|
128
|
+
|
129
|
+
# yields dynamic tag, and offset in buffer
|
130
|
+
def each_dynamic_tags
|
131
|
+
return unless block_given?
|
132
|
+
|
133
|
+
sec = find_section '.dynamic'
|
134
|
+
return unless sec
|
135
|
+
|
136
|
+
return if sec.header.sh_type == ELFTools::Constants::SHT_NOBITS
|
137
|
+
|
138
|
+
shdr = sec.header
|
139
|
+
with_buf_at(shdr.sh_offset) do |buf|
|
140
|
+
dyn = ELFTools::Structs::ELF_Dyn.new(elf_class: elf_class, endian: endian)
|
141
|
+
loop do
|
142
|
+
buf_dyn_offset = buf.tell
|
143
|
+
dyn.clear
|
144
|
+
dyn.read(buf)
|
145
|
+
break if dyn.d_tag == ELFTools::Constants::DT_NULL
|
146
|
+
|
147
|
+
yield dyn, buf_dyn_offset
|
148
|
+
# there's a possibility for caller to modify @buffer.pos, seek to avoid such issues
|
149
|
+
buf.seek buf_dyn_offset + dyn.num_bytes
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# the idea of uniquely identifying section by its name has its problems
|
155
|
+
# but this is how patchelf operates and is prone to bugs.
|
156
|
+
# e.g: https://github.com/NixOS/patchelf/issues/197
|
157
|
+
def find_section(sec_name)
|
158
|
+
idx = find_section_idx sec_name
|
159
|
+
return unless idx
|
160
|
+
|
161
|
+
@sections[idx]
|
162
|
+
end
|
163
|
+
|
164
|
+
def find_section_idx(sec_name)
|
165
|
+
@section_idx_by_name[sec_name]
|
166
|
+
end
|
167
|
+
|
168
|
+
def buf_grow!(newsz)
|
169
|
+
bufsz = @buffer.size
|
170
|
+
return if newsz <= bufsz
|
171
|
+
|
172
|
+
@buffer.truncate newsz
|
173
|
+
end
|
174
|
+
|
175
|
+
def modify_interpreter
|
176
|
+
@replaced_sections['.interp'] = "#{@set[:interpreter]}\x00"
|
177
|
+
end
|
178
|
+
|
179
|
+
def modify_needed
|
180
|
+
# due to gsoc time constraints only implmenting features used by brew.
|
181
|
+
raise NotImplementedError
|
182
|
+
end
|
183
|
+
|
184
|
+
# not checking for nil as modify_rpath is only called if @set[:rpath]
|
185
|
+
def modify_rpath
|
186
|
+
modify_rpath_helper @set[:rpath], force_rpath: true
|
187
|
+
end
|
188
|
+
|
189
|
+
# not checking for nil as modify_runpath is only called if @set[:runpath]
|
190
|
+
def modify_runpath
|
191
|
+
modify_rpath_helper @set[:runpath]
|
192
|
+
end
|
193
|
+
|
194
|
+
def collect_runpath_tags
|
195
|
+
tags = {}
|
196
|
+
each_dynamic_tags do |dyn, off|
|
197
|
+
case dyn.d_tag
|
198
|
+
when ELFTools::Constants::DT_RPATH
|
199
|
+
tag_type = :rpath
|
200
|
+
when ELFTools::Constants::DT_RUNPATH
|
201
|
+
tag_type = :runpath
|
202
|
+
else
|
203
|
+
next
|
204
|
+
end
|
205
|
+
|
206
|
+
# clone does shallow copy, and for some reason d_tag and d_val can't be pass as argument
|
207
|
+
dyn_rpath = ELFTools::Structs::ELF_Dyn.new(endian: endian, elf_class: elf_class)
|
208
|
+
dyn_rpath.assign({ d_tag: dyn.d_tag.to_i, d_val: dyn.d_val.to_i })
|
209
|
+
tags[tag_type] = { offset: off, header: dyn_rpath }
|
210
|
+
end
|
211
|
+
tags
|
212
|
+
end
|
213
|
+
|
214
|
+
def resolve_rpath_tag_conflict(dyn_tags, force_rpath: false)
|
215
|
+
dyn_runpath, dyn_rpath = dyn_tags.values_at(:runpath, :rpath)
|
216
|
+
|
217
|
+
update_sym =
|
218
|
+
if !force_rpath && dyn_rpath && dyn_runpath.nil?
|
219
|
+
:runpath
|
220
|
+
elsif force_rpath && dyn_runpath
|
221
|
+
:rpath
|
222
|
+
end
|
223
|
+
return unless update_sym
|
224
|
+
|
225
|
+
delete_sym, = %i[rpath runpath] - [update_sym]
|
226
|
+
dyn_tag = dyn_tags[update_sym] = dyn_tags[delete_sym]
|
227
|
+
dyn = dyn_tag[:header]
|
228
|
+
dyn.d_tag = ELFTools::Constants.const_get("DT_#{update_sym.upcase}")
|
229
|
+
with_buf_at(dyn_tag[:offset]) { |buf| dyn.write(buf) }
|
230
|
+
dyn_tags.delete(delete_sym)
|
231
|
+
end
|
232
|
+
|
233
|
+
def modify_rpath_helper(new_rpath, force_rpath: false)
|
234
|
+
shdr_dynstr = dynstr.header
|
235
|
+
|
236
|
+
dyn_tags = collect_runpath_tags
|
237
|
+
resolve_rpath_tag_conflict(dyn_tags, force_rpath: force_rpath)
|
238
|
+
# (:runpath, :rpath) order_matters.
|
239
|
+
resolved_rpath_dyn = dyn_tags.values_at(:runpath, :rpath).compact.first
|
240
|
+
|
241
|
+
old_rpath = ''
|
242
|
+
rpath_off = nil
|
243
|
+
if resolved_rpath_dyn
|
244
|
+
rpath_off = shdr_dynstr.sh_offset + resolved_rpath_dyn[:header].d_val
|
245
|
+
old_rpath = buf_cstr(rpath_off)
|
246
|
+
end
|
247
|
+
return if old_rpath == new_rpath
|
248
|
+
|
249
|
+
with_buf_at(rpath_off) { |b| b.write('X' * old_rpath.size) } if rpath_off
|
250
|
+
if new_rpath.size <= old_rpath.size
|
251
|
+
with_buf_at(rpath_off) { |b| b.write "#{new_rpath}\x00" }
|
252
|
+
return
|
253
|
+
end
|
254
|
+
|
255
|
+
Logger.debug 'rpath is too long, resizing...'
|
256
|
+
new_dynstr = replace_section '.dynstr', shdr_dynstr.sh_size + new_rpath.size + 1
|
257
|
+
new_rpath_strtab_idx = shdr_dynstr.sh_size.to_i
|
258
|
+
new_dynstr[new_rpath_strtab_idx..(new_rpath_strtab_idx + new_rpath.size)] = "#{new_rpath}\x00"
|
259
|
+
|
260
|
+
dyn_tags.each do |_, dyn|
|
261
|
+
dyn[:header].d_val = new_rpath_strtab_idx
|
262
|
+
with_buf_at(dyn[:offset]) { |b| dyn[:header].write(b) }
|
263
|
+
end
|
264
|
+
|
265
|
+
return unless dyn_tags.empty?
|
266
|
+
|
267
|
+
add_dt_rpath!(
|
268
|
+
d_tag: force_rpath ? ELFTools::Constants::DT_RPATH : ELFTools::Constants::DT_RUNPATH,
|
269
|
+
d_val: new_rpath_strtab_idx
|
270
|
+
)
|
271
|
+
end
|
272
|
+
|
273
|
+
def modify_soname
|
274
|
+
return unless ehdr.e_type == ELFTools::Constants::ET_DYN
|
275
|
+
|
276
|
+
# due to gsoc time constraints only implmenting features used by brew.
|
277
|
+
raise NotImplementedError
|
278
|
+
end
|
279
|
+
|
280
|
+
def add_segment!(**phdr_vals)
|
281
|
+
new_phdr = ELFTools::Structs::ELF_Phdr[elf_class].new(endian: endian, **phdr_vals)
|
282
|
+
# nil = no reference to stream; we only want @segments[i].header
|
283
|
+
new_segment = ELFTools::Segments::Segment.new(new_phdr, nil)
|
284
|
+
@segments.push new_segment
|
285
|
+
ehdr.e_phnum += 1
|
286
|
+
nil
|
287
|
+
end
|
288
|
+
|
289
|
+
def add_dt_rpath!(d_tag: nil, d_val: nil)
|
290
|
+
dyn_num_bytes = nil
|
291
|
+
dt_null_idx = 0
|
292
|
+
each_dynamic_tags do |dyn|
|
293
|
+
dyn_num_bytes ||= dyn.num_bytes
|
294
|
+
dt_null_idx += 1
|
295
|
+
end
|
296
|
+
|
297
|
+
if dyn_num_bytes.nil?
|
298
|
+
Logger.error 'no dynamic tags'
|
299
|
+
return
|
300
|
+
end
|
301
|
+
|
302
|
+
# allot for new dt_runpath
|
303
|
+
shdr_dynamic = find_section('.dynamic').header
|
304
|
+
new_dynamic_data = replace_section '.dynamic', shdr_dynamic.sh_size + dyn_num_bytes
|
305
|
+
|
306
|
+
# consider DT_NULL when copying
|
307
|
+
replacement_size = (dt_null_idx + 1) * dyn_num_bytes
|
308
|
+
|
309
|
+
# make space for dt_runpath tag at the top, shift data by one tag positon
|
310
|
+
new_dynamic_data[dyn_num_bytes..(replacement_size + dyn_num_bytes)] = new_dynamic_data[0..replacement_size]
|
311
|
+
|
312
|
+
dyn_rpath = ELFTools::Structs::ELF_Dyn.new endian: endian, elf_class: elf_class
|
313
|
+
dyn_rpath.d_tag = d_tag
|
314
|
+
dyn_rpath.d_val = d_val
|
315
|
+
|
316
|
+
zi = StringIO.new
|
317
|
+
dyn_rpath.write zi
|
318
|
+
zi.rewind
|
319
|
+
new_dynamic_data[0...dyn_num_bytes] = zi.read
|
320
|
+
end
|
321
|
+
|
322
|
+
# given a index into old_sections table
|
323
|
+
# returns the corresponding section index in @sections
|
324
|
+
#
|
325
|
+
# raises ArgumentError if old_shndx can't be found in old_sections
|
326
|
+
# TODO: handle case of non existing section in (new) @sections.
|
327
|
+
def new_section_idx(old_shndx)
|
328
|
+
return if old_shndx == ELFTools::Constants::SHN_UNDEF || old_shndx >= ELFTools::Constants::SHN_LORESERVE
|
329
|
+
|
330
|
+
raise ArgumentError if old_shndx >= old_sections.count
|
331
|
+
|
332
|
+
old_sec = old_sections[old_shndx]
|
333
|
+
raise PatchError, "old_sections[#{shndx}] is nil" if old_sec.nil?
|
334
|
+
|
335
|
+
# TODO: handle case of non existing section in (new) @sections.
|
336
|
+
find_section_idx(old_sec.name)
|
337
|
+
end
|
338
|
+
|
339
|
+
def page_size
|
340
|
+
Helper.page_size(ehdr.e_machine)
|
341
|
+
end
|
342
|
+
|
343
|
+
def patch_out
|
344
|
+
with_buf_at(0) { |b| ehdr.write(b) }
|
345
|
+
|
346
|
+
File.open(out_file, 'wb') do |f|
|
347
|
+
@buffer.rewind
|
348
|
+
f.write @buffer.read
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
# size includes NUL byte
|
353
|
+
def replace_section(section_name, size)
|
354
|
+
data = @replaced_sections[section_name]
|
355
|
+
unless data
|
356
|
+
shdr = find_section(section_name).header
|
357
|
+
# avoid calling +section.data+ as the @buffer contents may vary from
|
358
|
+
# the stream provided to section at initialization.
|
359
|
+
# ideally, calling section.data should work, however avoiding it to prevent
|
360
|
+
# future traps.
|
361
|
+
with_buf_at(shdr.sh_offset) { |b| data = b.read shdr.sh_size }
|
362
|
+
end
|
363
|
+
rep_data = if data.size == size
|
364
|
+
data
|
365
|
+
elsif data.size < size
|
366
|
+
data.ljust(size, "\x00")
|
367
|
+
else
|
368
|
+
"#{data[0...size]}\x00"
|
369
|
+
end
|
370
|
+
@replaced_sections[section_name] = rep_data
|
371
|
+
end
|
372
|
+
|
373
|
+
def write_phdrs_to_buf!
|
374
|
+
sort_phdrs!
|
375
|
+
with_buf_at(ehdr.e_phoff) do |buf|
|
376
|
+
@segments.each { |seg| seg.header.write(buf) }
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
def write_shdrs_to_buf!
|
381
|
+
raise PatchError, 'ehdr.e_shnum != @sections.count' if ehdr.e_shnum != @sections.count
|
382
|
+
|
383
|
+
sort_shdrs!
|
384
|
+
with_buf_at(ehdr.e_shoff) do |buf|
|
385
|
+
@sections.each { |section| section.header.write(buf) }
|
386
|
+
end
|
387
|
+
sync_dyn_tags!
|
388
|
+
end
|
389
|
+
|
390
|
+
# data for manual packing and unpacking of symbols in symtab sections.
|
391
|
+
def meta_sym_pack
|
392
|
+
return @meta_sym_pack if @meta_sym_pack
|
393
|
+
|
394
|
+
# resort to manual packing and unpacking of data,
|
395
|
+
# as using bindata is painfully slow :(
|
396
|
+
if elf_class == 32
|
397
|
+
sym_num_bytes = 16 # u32 u32 u32 u8 u8 u16
|
398
|
+
pack_code = endian == :little ? 'VVVCCv' : 'NNNCCn'
|
399
|
+
pack_st_info = 3
|
400
|
+
pack_st_shndx = 5
|
401
|
+
pack_st_value = 1
|
402
|
+
else # 64
|
403
|
+
sym_num_bytes = 24 # u32 u8 u8 u16 u64 u64
|
404
|
+
pack_code = endian == :little ? 'VCCvQ<Q<' : 'NCCnQ>Q>'
|
405
|
+
pack_st_info = 1
|
406
|
+
pack_st_shndx = 3
|
407
|
+
pack_st_value = 4
|
408
|
+
end
|
409
|
+
|
410
|
+
@meta_sym_pack = {
|
411
|
+
num_bytes: sym_num_bytes, code: pack_code,
|
412
|
+
st_info: pack_st_info, st_shndx: pack_st_shndx, st_value: pack_st_value
|
413
|
+
}
|
414
|
+
end
|
415
|
+
|
416
|
+
# yields +symbol+, +entry+
|
417
|
+
def each_symbol(shdr)
|
418
|
+
return unless [ELFTools::Constants::SHT_SYMTAB, ELFTools::Constants::SHT_DYNSYM].include?(shdr.sh_type)
|
419
|
+
|
420
|
+
pack_code, sym_num_bytes = meta_sym_pack.values_at(:code, :num_bytes)
|
421
|
+
|
422
|
+
with_buf_at(shdr.sh_offset) do |buf|
|
423
|
+
num_symbols = shdr.sh_size / sym_num_bytes
|
424
|
+
num_symbols.times do |entry|
|
425
|
+
sym = buf.read(sym_num_bytes).unpack(pack_code)
|
426
|
+
sym_modified = yield sym, entry
|
427
|
+
|
428
|
+
if sym_modified
|
429
|
+
buf.seek buf.tell - sym_num_bytes
|
430
|
+
buf.write sym.pack(pack_code)
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
def rewrite_headers(phdr_address)
|
437
|
+
# there can only be a single program header table according to ELF spec
|
438
|
+
@segments.find { |seg| seg.header.p_type == ELFTools::Constants::PT_PHDR }&.tap do |seg|
|
439
|
+
phdr = seg.header
|
440
|
+
phdr.p_offset = ehdr.e_phoff.to_i
|
441
|
+
phdr.p_vaddr = phdr.p_paddr = phdr_address.to_i
|
442
|
+
phdr.p_filesz = phdr.p_memsz = phdr.num_bytes * @segments.count # e_phentsize * e_phnum
|
443
|
+
end
|
444
|
+
write_phdrs_to_buf!
|
445
|
+
write_shdrs_to_buf!
|
446
|
+
|
447
|
+
pack = meta_sym_pack
|
448
|
+
@sections.each do |sec|
|
449
|
+
each_symbol(sec.header) do |sym, entry|
|
450
|
+
old_shndx = sym[pack[:st_shndx]]
|
451
|
+
|
452
|
+
begin
|
453
|
+
new_index = new_section_idx(old_shndx)
|
454
|
+
next unless new_index
|
455
|
+
rescue ArgumentError
|
456
|
+
Logger.warn "entry #{entry} in symbol table refers to a non existing section, skipping"
|
457
|
+
end
|
458
|
+
|
459
|
+
sym[pack[:st_shndx]] = new_index
|
460
|
+
|
461
|
+
# right 4 bits in the st_info field is st_type
|
462
|
+
if (sym[pack[:st_info]] & 0xF) == ELFTools::Constants::STT_SECTION
|
463
|
+
sym[pack[:st_value]] = @sections[new_index].header.sh_addr.to_i
|
464
|
+
end
|
465
|
+
true
|
466
|
+
end
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
def rewrite_sections
|
471
|
+
return if @replaced_sections.empty?
|
472
|
+
|
473
|
+
case ehdr.e_type
|
474
|
+
when ELFTools::Constants::ET_DYN
|
475
|
+
rewrite_sections_library
|
476
|
+
when ELFTools::Constants::ET_EXEC
|
477
|
+
rewrite_sections_executable
|
478
|
+
else
|
479
|
+
raise PatchError, 'unknown ELF type'
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
def replaced_section_indices
|
484
|
+
return enum_for(:replaced_section_indices) unless block_given?
|
485
|
+
|
486
|
+
last_replaced = 0
|
487
|
+
@sections.each_with_index do |sec, idx|
|
488
|
+
if @replaced_sections[sec.name]
|
489
|
+
last_replaced = idx
|
490
|
+
yield last_replaced
|
491
|
+
end
|
492
|
+
end
|
493
|
+
raise PatchError, 'last_replaced = 0' if last_replaced.zero?
|
494
|
+
raise PatchError, 'last_replaced + 1 >= @sections.size' if last_replaced + 1 >= @sections.size
|
495
|
+
end
|
496
|
+
|
497
|
+
def start_replacement_shdr
|
498
|
+
last_replaced = replaced_section_indices.max
|
499
|
+
start_replacement_hdr = @sections[last_replaced + 1].header
|
500
|
+
|
501
|
+
prev_sec_name = ''
|
502
|
+
(1..last_replaced).each do |idx|
|
503
|
+
sec = @sections[idx]
|
504
|
+
shdr = sec.header
|
505
|
+
if (sec.type == ELFTools::Constants::SHT_PROGBITS && sec.name != '.interp') || prev_sec_name == '.dynstr'
|
506
|
+
start_replacement_hdr = shdr
|
507
|
+
break
|
508
|
+
elsif @replaced_sections[sec.name].nil?
|
509
|
+
Logger.debug " replacing section #{sec.name} which is in the way"
|
510
|
+
replace_section(sec.name, shdr.sh_size)
|
511
|
+
end
|
512
|
+
prev_sec_name = sec.name
|
513
|
+
end
|
514
|
+
|
515
|
+
start_replacement_hdr
|
516
|
+
end
|
517
|
+
|
518
|
+
def copy_shdrs_to_eof
|
519
|
+
shoff_new = @buffer.size
|
520
|
+
# honestly idk why `ehdr.e_shoff` is considered when we are only moving shdrs.
|
521
|
+
sh_size = ehdr.e_shoff + (ehdr.e_shnum * ehdr.e_shentsize)
|
522
|
+
buf_grow! @buffer.size + sh_size
|
523
|
+
ehdr.e_shoff = shoff_new
|
524
|
+
raise PatchError, 'ehdr.e_shnum != @sections.size' if ehdr.e_shnum != @sections.size
|
525
|
+
|
526
|
+
with_buf_at(ehdr.e_shoff + @sections.first.header.num_bytes) do |buf| # skip writing to NULL section
|
527
|
+
@sections.each_with_index do |sec, idx|
|
528
|
+
next if idx.zero?
|
529
|
+
|
530
|
+
sec.header.write buf
|
531
|
+
end
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
def rewrite_sections_executable
|
536
|
+
sort_shdrs!
|
537
|
+
shdr = start_replacement_shdr
|
538
|
+
start_offset = shdr.sh_offset.to_i
|
539
|
+
start_addr = shdr.sh_addr.to_i
|
540
|
+
first_page = start_addr - start_offset
|
541
|
+
|
542
|
+
Logger.debug "first reserved offset/addr is 0x#{start_offset.to_s 16}/0x#{start_addr.to_s 16}"
|
543
|
+
|
544
|
+
unless start_addr % page_size == start_offset % page_size
|
545
|
+
raise PatchError, 'start_addr != start_offset (mod PAGE_SIZE)'
|
546
|
+
end
|
547
|
+
|
548
|
+
Logger.debug "first page is 0x#{first_page.to_i.to_s 16}"
|
549
|
+
|
550
|
+
copy_shdrs_to_eof if ehdr.e_shoff < start_offset
|
551
|
+
|
552
|
+
normalize_note_segments
|
553
|
+
|
554
|
+
seg_num_bytes = @segments.first.header.num_bytes
|
555
|
+
needed_space = (
|
556
|
+
ehdr.num_bytes +
|
557
|
+
(@segments.count * seg_num_bytes) +
|
558
|
+
@replaced_sections.sum { |_, str| Helper.alignup(str.size, @section_alignment) }
|
559
|
+
)
|
560
|
+
|
561
|
+
if needed_space > start_offset
|
562
|
+
needed_space += seg_num_bytes # new load segment is required
|
563
|
+
|
564
|
+
needed_pages = Helper.alignup(needed_space - start_offset, page_size) / page_size
|
565
|
+
Logger.debug "needed pages is #{needed_pages}"
|
566
|
+
raise PatchError, 'virtual address space underrun' if needed_pages * page_size > first_page
|
567
|
+
|
568
|
+
shift_file(needed_pages, start_offset)
|
569
|
+
|
570
|
+
first_page -= needed_pages * page_size
|
571
|
+
start_offset += needed_pages * page_size
|
572
|
+
end
|
573
|
+
Logger.debug "needed space is #{needed_space}"
|
574
|
+
|
575
|
+
cur_off = ehdr.num_bytes + (@segments.count * seg_num_bytes)
|
576
|
+
Logger.debug "clearing first #{start_offset - cur_off} bytes"
|
577
|
+
with_buf_at(cur_off) { |buf| buf.fill("\x00", (start_offset - cur_off)) }
|
578
|
+
|
579
|
+
cur_off = write_replaced_sections cur_off, first_page, 0
|
580
|
+
raise PatchError, "cur_off(#{cur_off}) != needed_space" if cur_off != needed_space
|
581
|
+
|
582
|
+
rewrite_headers first_page + ehdr.e_phoff
|
583
|
+
end
|
584
|
+
|
585
|
+
def replace_sections_in_the_way_of_phdr!
|
586
|
+
num_notes = @sections.count { |sec| sec.type == ELFTools::Constants::SHT_NOTE }
|
587
|
+
pht_size = ehdr.num_bytes + ((@segments.count + num_notes + 1) * @segments.first.header.num_bytes)
|
588
|
+
|
589
|
+
# replace sections that may overlap with expanded program header table
|
590
|
+
@sections.each_with_index do |sec, idx|
|
591
|
+
shdr = sec.header
|
592
|
+
next if idx.zero? || @replaced_sections[sec.name]
|
593
|
+
break if shdr.sh_offset > pht_size
|
594
|
+
|
595
|
+
replace_section sec.name, shdr.sh_size
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
def rewrite_sections_library
|
600
|
+
start_page = 0
|
601
|
+
first_page = 0
|
602
|
+
@segments.each do |seg|
|
603
|
+
phdr = seg.header
|
604
|
+
this_page = Helper.alignup(phdr.p_vaddr + phdr.p_memsz, page_size)
|
605
|
+
start_page = [start_page, this_page].max
|
606
|
+
first_page = phdr.p_vaddr - phdr.p_offset if phdr.p_type == ELFTools::Constants::PT_PHDR
|
607
|
+
end
|
608
|
+
|
609
|
+
Logger.debug "Last page is 0x#{start_page.to_s 16}"
|
610
|
+
Logger.debug "First page is 0x#{first_page.to_s 16}"
|
611
|
+
replace_sections_in_the_way_of_phdr!
|
612
|
+
needed_space = @replaced_sections.sum { |_, str| Helper.alignup(str.size, @section_alignment) }
|
613
|
+
Logger.debug "needed space = #{needed_space}"
|
614
|
+
|
615
|
+
start_offset = Helper.alignup(@buffer.size, page_size)
|
616
|
+
buf_grow! start_offset + needed_space
|
617
|
+
|
618
|
+
# executable shared object
|
619
|
+
if start_offset > start_page && @segments.any? { |seg| seg.header.p_type == ELFTools::Constants::PT_INTERP }
|
620
|
+
Logger.debug(
|
621
|
+
"shifting new PT_LOAD segment by #{start_offset - start_page} bytes to work around a Linux kernel bug"
|
622
|
+
)
|
623
|
+
start_page = start_offset
|
624
|
+
end
|
625
|
+
|
626
|
+
ehdr.e_phoff = ehdr.num_bytes
|
627
|
+
add_segment!(
|
628
|
+
p_type: ELFTools::Constants::PT_LOAD,
|
629
|
+
p_offset: start_offset,
|
630
|
+
p_vaddr: start_page,
|
631
|
+
p_paddr: start_page,
|
632
|
+
p_filesz: needed_space,
|
633
|
+
p_memsz: needed_space,
|
634
|
+
p_flags: ELFTools::Constants::PF_R | ELFTools::Constants::PF_W,
|
635
|
+
p_align: page_size
|
636
|
+
)
|
637
|
+
|
638
|
+
normalize_note_segments
|
639
|
+
|
640
|
+
cur_off = write_replaced_sections start_offset, start_page, start_offset
|
641
|
+
raise PatchError, 'cur_off != start_offset + needed_space' if cur_off != start_offset + needed_space
|
642
|
+
|
643
|
+
rewrite_headers(first_page + ehdr.e_phoff)
|
644
|
+
end
|
645
|
+
|
646
|
+
def normalize_note_segments
|
647
|
+
return if @replaced_sections.none? do |rsec_name, _|
|
648
|
+
find_section(rsec_name)&.type == ELFTools::Constants::SHT_NOTE
|
649
|
+
end
|
650
|
+
|
651
|
+
new_phdrs = []
|
652
|
+
|
653
|
+
phdrs_by_type(ELFTools::Constants::PT_NOTE) do |phdr|
|
654
|
+
# Binaries produced by older patchelf versions may contain empty PT_NOTE segments.
|
655
|
+
next if @sections.none? do |sec|
|
656
|
+
sec.header.sh_offset >= phdr.p_offset && sec.header.sh_offset < phdr.p_offset + phdr.p_filesz
|
657
|
+
end
|
658
|
+
|
659
|
+
new_phdrs += normalize_note_segment(phdr)
|
660
|
+
end
|
661
|
+
|
662
|
+
new_phdrs.each { |phdr| add_segment!(**phdr.snapshot) }
|
663
|
+
end
|
664
|
+
|
665
|
+
def normalize_note_segment(phdr)
|
666
|
+
start_off = phdr.p_offset.to_i
|
667
|
+
curr_off = start_off
|
668
|
+
end_off = start_off + phdr.p_filesz
|
669
|
+
|
670
|
+
new_phdrs = []
|
671
|
+
|
672
|
+
while curr_off < end_off
|
673
|
+
size = 0
|
674
|
+
sections_at_aligned_offset(curr_off) do |sec|
|
675
|
+
next if sec.type != ELFTools::Constants::SHT_NOTE
|
676
|
+
|
677
|
+
size = sec.header.sh_size.to_i
|
678
|
+
curr_off = sec.header.sh_offset.to_i
|
679
|
+
break
|
680
|
+
end
|
681
|
+
|
682
|
+
raise PatchError, 'cannot normalize PT_NOTE segment: non-contiguous SHT_NOTE sections' if size.zero?
|
683
|
+
|
684
|
+
if curr_off + size > end_off
|
685
|
+
raise PatchError, 'cannot normalize PT_NOTE segment: partially mapped SHT_NOTE section'
|
686
|
+
end
|
687
|
+
|
688
|
+
new_phdr = ELFTools::Structs::ELF_Phdr[elf_class].new(endian: endian, **phdr.snapshot)
|
689
|
+
new_phdr.p_offset = curr_off
|
690
|
+
new_phdr.p_vaddr = phdr.p_vaddr + (curr_off - start_off)
|
691
|
+
new_phdr.p_paddr = phdr.p_paddr + (curr_off - start_off)
|
692
|
+
new_phdr.p_filesz = size
|
693
|
+
new_phdr.p_memsz = size
|
694
|
+
|
695
|
+
if curr_off == start_off
|
696
|
+
phdr.assign(new_phdr)
|
697
|
+
else
|
698
|
+
new_phdrs << new_phdr
|
699
|
+
end
|
700
|
+
|
701
|
+
curr_off += size
|
702
|
+
end
|
703
|
+
|
704
|
+
new_phdrs
|
705
|
+
end
|
706
|
+
|
707
|
+
def sections_at_aligned_offset(offset)
|
708
|
+
@sections.each do |sec|
|
709
|
+
shdr = sec.header
|
710
|
+
|
711
|
+
aligned_offset = Helper.alignup(offset, shdr.sh_addralign)
|
712
|
+
next if shdr.sh_offset != aligned_offset
|
713
|
+
|
714
|
+
yield sec
|
715
|
+
end
|
716
|
+
end
|
717
|
+
|
718
|
+
def shift_sections(shift, start_offset)
|
719
|
+
ehdr.e_shoff += shift if ehdr.e_shoff >= start_offset
|
720
|
+
|
721
|
+
@sections.each_with_index do |sec, i|
|
722
|
+
next if i.zero? # dont touch NULL section
|
723
|
+
|
724
|
+
shdr = sec.header
|
725
|
+
next if shdr.sh_offset < start_offset
|
726
|
+
|
727
|
+
shdr.sh_offset += shift
|
728
|
+
end
|
729
|
+
end
|
730
|
+
|
731
|
+
def shift_segment_offset(phdr, shift)
|
732
|
+
phdr.p_offset += shift
|
733
|
+
phdr.p_align = page_size if phdr.p_align != 0 && (phdr.p_vaddr - phdr.p_offset) % phdr.p_align != 0
|
734
|
+
end
|
735
|
+
|
736
|
+
def shift_segment_virtual_address(phdr, shift)
|
737
|
+
phdr.p_paddr -= shift if phdr.p_paddr > shift
|
738
|
+
phdr.p_vaddr -= shift if phdr.p_vaddr > shift
|
739
|
+
end
|
740
|
+
|
741
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
742
|
+
def shift_segments(shift, start_offset)
|
743
|
+
split_index = -1
|
744
|
+
split_shift = 0
|
745
|
+
|
746
|
+
@segments.each_with_index do |seg, idx|
|
747
|
+
phdr = seg.header
|
748
|
+
p_start = phdr.p_offset
|
749
|
+
|
750
|
+
if p_start <= start_offset && p_start + phdr.p_filesz > start_offset &&
|
751
|
+
phdr.p_type == ELFTools::Constants::PT_LOAD
|
752
|
+
raise PatchError, "split_index(#{split_index}) != -1" if split_index != -1
|
753
|
+
|
754
|
+
split_index = idx
|
755
|
+
split_shift = start_offset - p_start
|
756
|
+
|
757
|
+
phdr.p_offset = start_offset
|
758
|
+
phdr.p_memsz -= split_shift
|
759
|
+
phdr.p_filesz -= split_shift
|
760
|
+
phdr.p_paddr += split_shift
|
761
|
+
phdr.p_vaddr += split_shift
|
762
|
+
|
763
|
+
p_start = start_offset
|
764
|
+
end
|
765
|
+
|
766
|
+
if p_start >= start_offset
|
767
|
+
shift_segment_offset(phdr, shift)
|
768
|
+
else
|
769
|
+
shift_segment_virtual_address(phdr, shift)
|
770
|
+
end
|
771
|
+
end
|
772
|
+
|
773
|
+
raise PatchError, "split_index(#{split_index}) == -1" if split_index == -1
|
774
|
+
|
775
|
+
[split_index, split_shift]
|
776
|
+
end
|
777
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
778
|
+
|
779
|
+
def shift_file(extra_pages, start_offset)
|
780
|
+
raise PatchError, "start_offset(#{start_offset}) < ehdr.num_bytes" if start_offset < ehdr.num_bytes
|
781
|
+
|
782
|
+
oldsz = @buffer.size
|
783
|
+
raise PatchError, "oldsz <= start_offset(#{start_offset})" if oldsz <= start_offset
|
784
|
+
|
785
|
+
shift = extra_pages * page_size
|
786
|
+
buf_grow!(oldsz + shift)
|
787
|
+
buf_move!(start_offset + shift, start_offset, oldsz - start_offset)
|
788
|
+
with_buf_at(start_offset) { |buf| buf.write "\x00" * shift }
|
789
|
+
|
790
|
+
ehdr.e_phoff = ehdr.num_bytes
|
791
|
+
|
792
|
+
shift_sections(shift, start_offset)
|
793
|
+
|
794
|
+
split_index, split_shift = shift_segments(shift, start_offset)
|
795
|
+
|
796
|
+
split_phdr = @segments[split_index].header
|
797
|
+
add_segment!(
|
798
|
+
p_type: ELFTools::Constants::PT_LOAD,
|
799
|
+
p_offset: split_phdr.p_offset - split_shift - shift,
|
800
|
+
p_vaddr: split_phdr.p_vaddr - split_shift - shift,
|
801
|
+
p_paddr: split_phdr.p_paddr - split_shift - shift,
|
802
|
+
p_filesz: split_shift + shift,
|
803
|
+
p_memsz: split_shift + shift,
|
804
|
+
p_flags: ELFTools::Constants::PF_R | ELFTools::Constants::PF_W,
|
805
|
+
p_align: page_size
|
806
|
+
)
|
807
|
+
end
|
808
|
+
|
809
|
+
def sort_phdrs!
|
810
|
+
pt_phdr = ELFTools::Constants::PT_PHDR
|
811
|
+
@segments.sort! do |me, you|
|
812
|
+
next 1 if you.header.p_type == pt_phdr
|
813
|
+
next -1 if me.header.p_type == pt_phdr
|
814
|
+
|
815
|
+
me.header.p_paddr.to_i <=> you.header.p_paddr.to_i
|
816
|
+
end
|
817
|
+
end
|
818
|
+
|
819
|
+
# section headers may contain sh_info and sh_link values that are
|
820
|
+
# references to another section
|
821
|
+
def collect_section_to_section_refs
|
822
|
+
rel_syms = [ELFTools::Constants::SHT_REL, ELFTools::Constants::SHT_RELA]
|
823
|
+
# Translate sh_link, sh_info mappings to section names.
|
824
|
+
@sections.each_with_object({ linkage: {}, info: {} }) do |s, collected|
|
825
|
+
hdr = s.header
|
826
|
+
collected[:linkage][s.name] = @sections[hdr.sh_link].name if hdr.sh_link.nonzero?
|
827
|
+
collected[:info][s.name] = @sections[hdr.sh_info].name if hdr.sh_info.nonzero? && rel_syms.include?(hdr.sh_type)
|
828
|
+
end
|
829
|
+
end
|
830
|
+
|
831
|
+
# @param collected
|
832
|
+
# this must be the value returned by +collect_section_to_section_refs+
|
833
|
+
def restore_section_to_section_refs!(collected)
|
834
|
+
rel_syms = [ELFTools::Constants::SHT_REL, ELFTools::Constants::SHT_RELA]
|
835
|
+
linkage, info = collected.values_at(:linkage, :info)
|
836
|
+
@sections.each do |sec|
|
837
|
+
hdr = sec.header
|
838
|
+
hdr.sh_link = find_section_idx(linkage[sec.name]) if hdr.sh_link.nonzero?
|
839
|
+
hdr.sh_info = find_section_idx(info[sec.name]) if hdr.sh_info.nonzero? && rel_syms.include?(hdr.sh_type)
|
840
|
+
end
|
841
|
+
end
|
842
|
+
|
843
|
+
def sort_shdrs!
|
844
|
+
return if @sections.empty?
|
845
|
+
|
846
|
+
section_dep_values = collect_section_to_section_refs
|
847
|
+
shstrtab = @sections[ehdr.e_shstrndx].header
|
848
|
+
@sections.sort! { |me, you| me.header.sh_offset.to_i <=> you.header.sh_offset.to_i }
|
849
|
+
update_section_idx!
|
850
|
+
restore_section_to_section_refs!(section_dep_values)
|
851
|
+
@sections.each_with_index do |sec, idx|
|
852
|
+
ehdr.e_shstrndx = idx if sec.header.sh_offset == shstrtab.sh_offset
|
853
|
+
end
|
854
|
+
end
|
855
|
+
|
856
|
+
def jmprel_section_name
|
857
|
+
sec_name = %w[.rel.plt .rela.plt .rela.IA_64.pltoff].find { |s| find_section(s) }
|
858
|
+
raise PatchError, 'cannot find section corresponding to DT_JMPREL' unless sec_name
|
859
|
+
|
860
|
+
sec_name
|
861
|
+
end
|
862
|
+
|
863
|
+
# given a +dyn.d_tag+, returns the section name it must be synced to.
|
864
|
+
# it may return nil, when given tag maps to no section,
|
865
|
+
# or when its okay to skip if section is not found.
|
866
|
+
def dyn_tag_to_section_name(d_tag)
|
867
|
+
case d_tag
|
868
|
+
when ELFTools::Constants::DT_STRTAB, ELFTools::Constants::DT_STRSZ
|
869
|
+
'.dynstr'
|
870
|
+
when ELFTools::Constants::DT_SYMTAB
|
871
|
+
'.dynsym'
|
872
|
+
when ELFTools::Constants::DT_HASH
|
873
|
+
'.hash'
|
874
|
+
when ELFTools::Constants::DT_GNU_HASH
|
875
|
+
# return nil if not found, patchelf claims no problem in skipping
|
876
|
+
find_section('.gnu.hash')&.name
|
877
|
+
when ELFTools::Constants::DT_MIPS_XHASH
|
878
|
+
return if ehdr.e_machine != ELFTools::Constants::EM_MIPS
|
879
|
+
|
880
|
+
'.MIPS.xhash'
|
881
|
+
when ELFTools::Constants::DT_JMPREL
|
882
|
+
jmprel_section_name
|
883
|
+
when ELFTools::Constants::DT_REL
|
884
|
+
# regarding .rel.got, NixOS/patchelf says
|
885
|
+
# "no idea if this makes sense, but it was needed for some program"
|
886
|
+
#
|
887
|
+
# return nil if not found, patchelf claims no problem in skipping
|
888
|
+
%w[.rel.dyn .rel.got].find { |s| find_section(s) }
|
889
|
+
when ELFTools::Constants::DT_RELA
|
890
|
+
# return nil if not found, patchelf claims no problem in skipping
|
891
|
+
find_section('.rela.dyn')&.name
|
892
|
+
when ELFTools::Constants::DT_VERNEED
|
893
|
+
'.gnu.version_r'
|
894
|
+
when ELFTools::Constants::DT_VERSYM
|
895
|
+
'.gnu.version'
|
896
|
+
end
|
897
|
+
end
|
898
|
+
|
899
|
+
# updates dyn tags by syncing it with @section values
|
900
|
+
def sync_dyn_tags!
|
901
|
+
dyn_table_offset = nil
|
902
|
+
each_dynamic_tags do |dyn, buf_off|
|
903
|
+
dyn_table_offset ||= buf_off
|
904
|
+
|
905
|
+
sec_name = dyn_tag_to_section_name(dyn.d_tag)
|
906
|
+
|
907
|
+
unless sec_name
|
908
|
+
if dyn.d_tag == ELFTools::Constants::DT_MIPS_RLD_MAP_REL && ehdr.e_machine == ELFTools::Constants::EM_MIPS
|
909
|
+
rld_map = find_section('.rld_map')
|
910
|
+
dyn.d_val = if rld_map
|
911
|
+
rld_map.header.sh_addr.to_i - (buf_off - dyn_table_offset) -
|
912
|
+
find_section('.dynamic').header.sh_addr.to_i
|
913
|
+
else
|
914
|
+
Logger.warn 'DT_MIPS_RLD_MAP_REL entry is present, but .rld_map section is not'
|
915
|
+
0
|
916
|
+
end
|
917
|
+
end
|
918
|
+
|
919
|
+
next
|
920
|
+
end
|
921
|
+
|
922
|
+
shdr = find_section(sec_name).header
|
923
|
+
dyn.d_val = dyn.d_tag == ELFTools::Constants::DT_STRSZ ? shdr.sh_size.to_i : shdr.sh_addr.to_i
|
924
|
+
|
925
|
+
with_buf_at(buf_off) { |wbuf| dyn.write(wbuf) }
|
926
|
+
end
|
927
|
+
end
|
928
|
+
|
929
|
+
def update_section_idx!
|
930
|
+
@section_idx_by_name = @sections.map.with_index { |sec, idx| [sec.name, idx] }.to_h
|
931
|
+
end
|
932
|
+
|
933
|
+
def with_buf_at(pos)
|
934
|
+
return unless block_given?
|
935
|
+
|
936
|
+
opos = @buffer.tell
|
937
|
+
@buffer.seek pos
|
938
|
+
yield @buffer
|
939
|
+
@buffer.seek opos
|
940
|
+
nil
|
941
|
+
end
|
942
|
+
|
943
|
+
def sync_sec_to_seg(shdr, phdr)
|
944
|
+
phdr.p_offset = shdr.sh_offset.to_i
|
945
|
+
phdr.p_vaddr = phdr.p_paddr = shdr.sh_addr.to_i
|
946
|
+
phdr.p_filesz = phdr.p_memsz = shdr.sh_size.to_i
|
947
|
+
end
|
948
|
+
|
949
|
+
def phdrs_by_type(seg_type)
|
950
|
+
return unless seg_type
|
951
|
+
|
952
|
+
@segments.each_with_index do |seg, idx|
|
953
|
+
next unless (phdr = seg.header).p_type == seg_type
|
954
|
+
|
955
|
+
yield phdr, idx
|
956
|
+
end
|
957
|
+
end
|
958
|
+
|
959
|
+
# Returns a blank shdr if the section doesn't exist.
|
960
|
+
def find_or_create_section_header(rsec_name)
|
961
|
+
shdr = find_section(rsec_name)&.header
|
962
|
+
shdr ||= ELFTools::Structs::ELF_Shdr.new(endian: endian, elf_class: elf_class)
|
963
|
+
shdr
|
964
|
+
end
|
965
|
+
|
966
|
+
def overwrite_replaced_sections
|
967
|
+
# the original source says this has to be done separately to
|
968
|
+
# prevent clobbering the previously written section contents.
|
969
|
+
@replaced_sections.each do |rsec_name, _|
|
970
|
+
shdr = find_section(rsec_name)&.header
|
971
|
+
next unless shdr
|
972
|
+
|
973
|
+
next if shdr.sh_type == ELFTools::Constants::SHT_NOBITS
|
974
|
+
|
975
|
+
with_buf_at(shdr.sh_offset) { |b| b.fill('X', shdr.sh_size) }
|
976
|
+
end
|
977
|
+
end
|
978
|
+
|
979
|
+
def write_section_aligment(shdr)
|
980
|
+
return if shdr.sh_type == ELFTools::Constants::SHT_NOTE && shdr.sh_addralign <= @section_alignment
|
981
|
+
|
982
|
+
shdr.sh_addralign = @section_alignment
|
983
|
+
end
|
984
|
+
|
985
|
+
def section_bounds_within_segment?(s_start, s_end, p_start, p_end)
|
986
|
+
(s_start >= p_start && s_start < p_end) || (s_end > p_start && s_end <= p_end)
|
987
|
+
end
|
988
|
+
|
989
|
+
def write_replaced_sections(cur_off, start_addr, start_offset)
|
990
|
+
overwrite_replaced_sections
|
991
|
+
|
992
|
+
noted_phdrs = Set.new
|
993
|
+
|
994
|
+
# the sort is necessary, the strategy in ruby and Cpp to iterate map/hash
|
995
|
+
# is different, patchelf v0.10 iterates the replaced_sections sorted by
|
996
|
+
# keys.
|
997
|
+
@replaced_sections.sort.each do |rsec_name, rsec_data|
|
998
|
+
shdr = find_or_create_section_header(rsec_name)
|
999
|
+
|
1000
|
+
Logger.debug <<~DEBUG
|
1001
|
+
rewriting section '#{rsec_name}'
|
1002
|
+
from offset 0x#{shdr.sh_offset.to_i.to_s 16}(size #{shdr.sh_size})
|
1003
|
+
to offset 0x#{cur_off.to_i.to_s 16}(size #{rsec_data.size})
|
1004
|
+
DEBUG
|
1005
|
+
|
1006
|
+
with_buf_at(cur_off) { |b| b.write rsec_data }
|
1007
|
+
|
1008
|
+
orig_sh_offset = shdr.sh_offset.to_i
|
1009
|
+
orig_sh_size = shdr.sh_size.to_i
|
1010
|
+
|
1011
|
+
shdr.sh_offset = cur_off
|
1012
|
+
shdr.sh_addr = start_addr + (cur_off - start_offset)
|
1013
|
+
shdr.sh_size = rsec_data.size
|
1014
|
+
|
1015
|
+
write_section_aligment(shdr)
|
1016
|
+
|
1017
|
+
seg_type = {
|
1018
|
+
'.interp' => ELFTools::Constants::PT_INTERP,
|
1019
|
+
'.dynamic' => ELFTools::Constants::PT_DYNAMIC,
|
1020
|
+
'.MIPS.abiflags' => ELFTools::Constants::PT_MIPS_ABIFLAGS,
|
1021
|
+
'.note.gnu.property' => ELFTools::Constants::PT_GNU_PROPERTY
|
1022
|
+
}[rsec_name]
|
1023
|
+
|
1024
|
+
phdrs_by_type(seg_type) { |phdr| sync_sec_to_seg(shdr, phdr) }
|
1025
|
+
|
1026
|
+
if shdr.sh_type == ELFTools::Constants::SHT_NOTE
|
1027
|
+
phdrs_by_type(ELFTools::Constants::PT_NOTE) do |phdr, idx|
|
1028
|
+
next if noted_phdrs.include?(idx)
|
1029
|
+
|
1030
|
+
s_start = orig_sh_offset
|
1031
|
+
s_end = s_start + orig_sh_size
|
1032
|
+
p_start = phdr.p_offset
|
1033
|
+
p_end = p_start + phdr.p_filesz
|
1034
|
+
|
1035
|
+
next unless section_bounds_within_segment?(s_start, s_end, p_start, p_end)
|
1036
|
+
|
1037
|
+
raise PatchError, 'unsupported overlap of SHT_NOTE and PT_NOTE' if p_start != s_start || p_end != s_end
|
1038
|
+
|
1039
|
+
sync_sec_to_seg(shdr, phdr)
|
1040
|
+
|
1041
|
+
noted_phdrs << idx
|
1042
|
+
end
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
cur_off += Helper.alignup(rsec_data.size, @section_alignment)
|
1046
|
+
end
|
1047
|
+
@replaced_sections.clear
|
1048
|
+
|
1049
|
+
cur_off
|
1050
|
+
end
|
1051
|
+
end
|
1052
|
+
end
|