patchelf 1.2.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|