Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions lib/caotral/binary/elf/reader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -143,16 +143,16 @@ def read

def validate_relocations
rela_dyn = @context.sections.find { |section| section.section_name.to_s == ".rela.dyn" }
pt_load = @context.program_headers.find { |ph| ph.type == :LOAD }
pt_loads = @context.program_headers.select { |ph| ph.type == :LOAD }
dynamic = @context.sections.find { |section| section.section_name.to_s == ".dynamic" }
rela_plt = @context.sections.find { |section| section.section_name.to_s == ".rela.plt" }
rela_plt_exists = !rela_plt.body.empty?
got_plt = @context.sections.find { |s| s.section_name.to_s == ".got.plt" }
failed_messages = []
unless rela_dyn && pt_load && dynamic
unless rela_dyn && pt_loads.any? && dynamic
data = [
rela_dyn ? nil : ".rela.dyn section",
pt_load ? nil : "LOAD program header",
pt_loads.any? ? nil : "LOAD program header",
dynamic ? nil : ".dynamic section"
].compact.join(", ")
raise Caotral::Binary::ELF::Error, "Missing required relocations inputs: #{data}"
Expand All @@ -170,8 +170,11 @@ def validate_relocations
end
end

valid_range = (pt_load.vaddr...(pt_load.vaddr + pt_load.memsz))
unless rela_dyn.body.all? { |rel| valid_range.include?(rel.offset) }
within_load = ->(offset) do
pt_loads.any? { |ph| (ph.vaddr ... (ph.vaddr + ph.memsz)).include?(offset) }
end
valid_relocations = rela_dyn.body.all? { |rel| within_load.call(rel.offset) }
unless valid_relocations
failed_messages << "Relocation entries in .rela.dyn exceed LOAD segment range"
end

Expand All @@ -187,7 +190,7 @@ def validate_relocations
failed_messages << "#{basemsg} DT_PLTGOT does not match .got.plt address" if got_plt && !plt_got

rela_plt.body.each do |rel|
vr = valid_range.include?(rel.offset)
vr = within_load.call(rel.offset)
js = rel.type_name == :AMD64_JUMP_SLOT
failed_messages << "Relocation entries in .rela.plt exceed LOAD segment range" unless vr
failed_messages << "Unexpected relocation type in .rela.plt: #{rel.type_name}" unless js
Expand Down
2 changes: 2 additions & 0 deletions lib/caotral/binary/elf/section_header.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ def addr = @addr.pack("C*").unpack1("Q<")
def link = @link.pack("C*").unpack1("L<")
def addralign = @addralign.pack("C*").unpack1("Q<")
def allocated? = (@flags.pack("C*").unpack1("Q<") & SHF[:ALLOC]) != 0
def writable? = (@flags.pack("C*").unpack1("Q<") & SHF[:WRITE]) != 0
def execinstr? = (@flags.pack("C*").unpack1("Q<") & SHF[:EXECINSTR]) != 0

private def bytes = [@name, @type, @flags, @addr, @offset, @size, @link, @info, @addralign, @entsize]
end
Expand Down
36 changes: 18 additions & 18 deletions lib/caotral/linker/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ def build
sections = []
rel_sections = []
rel_texts = []
pending_relative_relocations = []
elf.header = elf_obj.header.dup
strtab_names = []
text_offsets = {}
Expand Down Expand Up @@ -184,24 +185,22 @@ def build
section.body.each do |rel|
sym = base_index.nil? ? rel.sym : base_index + rel.sym
if rel.type == REL_TYPES[:AMD64_64]
base_offset = case section.section_name.to_s
when /\.rela?\.text/
vaddr + start_len + text_offsets.fetch(elf_obj.object_id, 0)
when /\.rela?\.data/
vaddr + text_offset + data_offsets.fetch(elf_obj.object_id, 0) + start_len
else 0
end
offset = rel.offset + base_offset
sym = symtab_section.body[sym_index = base_index.nil? ? rel.sym : base_index + rel.sym]
sym_addr = if sym.shndx == text_index
vaddr + sym.value
elsif sym.shndx == data_index
vaddr + text_offset + sym.value + start_len
else
0
end
addend = sym_addr - base_addr
rela_dyn_section.body << Caotral::Binary::ELF::Section::Rel.new.set!(offset:, info: (0 << 32) | REL_TYPES[:AMD64_RELATIVE], addend:)
relocation = Caotral::Binary::ELF::Section::Rel.new.set!(
offset: 0,
info: (0 << 32) | REL_TYPES[:AMD64_RELATIVE],
addend: 0
)
symbol = symtab_section.body[sym]
addend = rel.addend? ? rel.addend : 0
target, target_offset =
case section.section_name.to_s
when /\.rela?\.text/
[text_section, rel.offset + start_len + text_offsets.fetch(elf_obj.object_id, 0)]
when /\.rela?\.data/
[data_section, rel.offset + data_offsets.fetch(elf_obj.object_id, 0)]
end
rela_dyn_section.body << relocation
pending_relative_relocations << { relocation:, target:, target_offset:, symbol:, addend: }
next
elsif (undefined = symtab.body[rel.sym]&.shndx.to_i == 0) && ALLOW_RELOCATION_TYPES.include?(rel.type)
sym = base_index.to_i + rel.sym
Expand Down Expand Up @@ -425,6 +424,7 @@ def build

@linker_metadata[:got_plt_offsets] = got_plt_offsets
@linker_metadata[:pending_text_relocations] = rel_texts
@linker_metadata[:pending_relative_relocations] = pending_relative_relocations

elf
end
Expand Down
15 changes: 15 additions & 0 deletions lib/caotral/linker/finalizer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def apply!
finalize_entry!
finalize_plt_relocations!
finalize_text_relocations!
finalize_relative_relocations!
finalize_dynamic_sections! if dynamic?
finalize_shared_sections! if dynamic? && plt
finalize_section_headers!
Expand Down Expand Up @@ -74,6 +75,19 @@ def finalize_text_relocations!
end
end

def finalize_relative_relocations!
pending_relative_relocations.each do |rrh|
symbol = rrh[:symbol]
symbol_section = @elf.sections[symbol.shndx]
target = rrh[:target]
relocation = rrh[:relocation]
relocation.set!(
offset: target.header.addr + rrh[:target_offset],
addend: symbol_section.header.addr + symbol.value + rrh[:addend]
)
end
end

def finalize_dynamic_sections!
dynsym.body.each do |dynsym_body|
if dynsym_body.shndx != 0
Expand Down Expand Up @@ -183,6 +197,7 @@ def dynamic = @dynamic ||= @elf.find_by_name(".dynamic")
def rela_dyn = @rela_dyn ||= @elf.find_by_name(".rela.dyn")
def shstrtab = @shstrtab ||= @elf.find_by_name(".shstrtab")
def pending_text_relocations = @pending_text_relocations ||= @metadata.fetch(:pending_text_relocations, [])
def pending_relative_relocations = @pending_relative_relocations ||= @metadata.fetch(:pending_relative_relocations, [])
def got_plt_offsets = @got_plt_offsets ||= @metadata.fetch(:got_plt_offsets, {})
def dynamic? = (@shared || @pie)
end
Expand Down
92 changes: 58 additions & 34 deletions lib/caotral/linker/layout.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ class Linker
class Layout
attr_reader :debug, :shared, :executable, :pie
LATE_SECTIONS = ["", ".shstrtab"].freeze
PF = Caotral::Binary::ELF::ProgramHeader::PF
LOAD_ALIGNMENT = 0x1000

def initialize(elf:, shared:, executable:, pie:)
@elf = elf
@shared, @executable, @pie, @file_offset = shared, executable, pie, 0
end

def apply!
classify_load_sections!
build_program_headers!
layout_sections!
layout_section_headers!
Expand All @@ -23,19 +26,34 @@ def apply!
end

private
def classify_load_sections!
allocated_sections = @elf.sections.select { |s| s.header.allocated? }
read_only_sections = allocated_sections.select { |s| !s.header.execinstr? && !s.header.writable? }
executable_sections = allocated_sections.select { |s| s.header.execinstr? && !s.header.writable? }
writable_sections = allocated_sections.select { |s| !s.header.execinstr? && s.header.writable? }
if allocated_sections.any? { |s| s.header.execinstr? && s.header.writable? }
raise Caotral::Linker::Error, "can not support alloc section write and executable"
end

@load_sections = {
R: read_only_sections,
RX: executable_sections,
RW: writable_sections,
}
end

def build_program_headers!
lph = build_program_header(type: 1)
read_only = build_program_header(type: 1, flags: PF[:R])
executable = build_program_header(type: 1, flags: PF[:RX]) if @load_sections[:RX].any?
writable = build_program_header(type: 1, flags: PF[:RW]) if @load_sections[:RW].any?
lph = [read_only, executable, writable].compact

iph = build_program_header(type: 3) if @elf.find_by_name(".interp")
dph = build_program_header(type: 2) if @elf.find_by_name(".dynamic")
pph = build_program_header(type: 6) if @pie
if @pie || @shared
gsph = build_program_header(
type: 0x6474e551,
flags: Caotral::Binary::ELF::ProgramHeader::PF[:RW]
)
end
gsph = build_program_header(type: 0x6474e551, flags: PF[:RW]) if dynamic?

@elf.program_headers.replace([pph, lph, iph, dph, gsph].compact)
@elf.program_headers.replace([pph, *lph, iph, dph, gsph].compact)
end

def build_program_header(type:, flags: 0)
Expand All @@ -44,14 +62,23 @@ def build_program_header(type:, flags: 0)
ph
end
def layout_sections!
header_end = (64 + (@elf.program_headers.size * 56))
first_section = @elf.sections.find { |s| s.section_name && !LATE_SECTIONS.include?(s.section_name) }
first_offset = first_section&.header&.offset.to_i || 0
@file_offset = [header_end, first_offset].max
@file_offset = header_end
text = @elf.find_by_name(".text")
@load_bias = text ? text.header.addr - text.header.offset : 0

@elf.sections.each do |section|
@load_sections.each do |flags, sections|
next if sections.empty?

@file_offset = align_up(@file_offset, LOAD_ALIGNMENT) unless flags == :R
Comment thread
katsyoshi marked this conversation as resolved.
Comment thread
katsyoshi marked this conversation as resolved.
sections.each do |section|
@file_offset = section.layout!(offset: @file_offset)
end
end

@elf.sections.reject { |s| s.header.allocated? }.each do |section|
next if section.section_name.nil?
next if LATE_SECTIONS.include?(section.section_name)

@file_offset = section.layout!(offset: @file_offset)
end

Expand All @@ -60,7 +87,7 @@ def layout_sections!
@elf.sections.each do |section|
next unless section.header.allocated?

addr = text.header.addr + (section.header.offset - text.header.offset)
addr = @load_bias + section.header.offset
section.header.set!(addr:)
end
end
Expand All @@ -79,56 +106,48 @@ def layout_section_headers!
@file_offset += @elf.sections.sum { |section| section.header.build.bytesize }
end
def finalize_program_headers!
text = @elf.find_by_name(".text")
load_bias = text.nil? ? 0 : text.header.addr - text.header.offset
phdr = @elf.program_headers.any? { |ph| ph.type == :PHDR }
@elf.program_headers.each do |ph|
case ph.type
when :PHDR
offset, align = 64, 8
vaddr = load_bias + offset
vaddr = @load_bias + offset
paddr = vaddr
filesz = @elf.program_headers.size * 56
memsz = filesz
flags = Caotral::Binary::ELF::ProgramHeader::PF[:R]
flags = PF[:R]
when :LOAD
text = @elf.find_by_name(".text")
next unless text
allocated_sections = @load_sections[ph.flags]

allocated_sections = @elf.sections.select { |s| s.header.allocated? }
segment_start = phdr ? 0 : allocated_sections.map { |s| s.header.offset }.min
segment_end = allocated_sections.map { |s| s.header.offset + s.header.size }.max

next unless segment_end
section_end = allocated_sections.map { |s| s.header.offset + s.header.size }.max
segment_start = ph.flags == :R ? 0 : allocated_sections.map { |s| s.header.offset }.min
segment_end = ph.flags == :R ? [header_end, section_end].compact.max : section_end

offset = segment_start
vaddr = load_bias + offset
vaddr = @load_bias + offset
paddr = vaddr
filesz = segment_end - segment_start
memsz = filesz
flags = Caotral::Binary::ELF::ProgramHeader::PF[:RWX]
align = 0x1000
flags = PF[ph.flags]
align = LOAD_ALIGNMENT
when :INTERP
interp = @elf.find_by_name(".interp")
raise Caotral::Linker::Error, "Missing .interp section" unless interp
header = interp.header
offset, vaddr, paddr, align = header.offset, header.addr, header.addr, 1
filesz = interp.header.size
memsz = filesz
flags = Caotral::Binary::ELF::ProgramHeader::PF[:R]
flags = PF[:R]
when :DYNAMIC
dynamic = @elf.find_by_name(".dynamic")
raise Caotral::Linker::Error, "Missing .dynamic section" unless dynamic
header = dynamic.header
offset, vaddr, paddr, align = header.offset, header.addr, header.addr, header.addralign
filesz = header.size
memsz = filesz
flags = Caotral::Binary::ELF::ProgramHeader::PF[:RW]
flags = PF[:RW]
when :GNU_STACK
offset, vaddr, paddr, align = 0, 0, 0, 0
filesz = 0
memsz = 0
flags = Caotral::Binary::ELF::ProgramHeader::PF[:RW]
filesz, memsz, flags = 0, 0, PF[:RW]
else
raise Caotral::Linker::Error, "Not Implemented: #{ph.type} program header layout"
end
Expand All @@ -151,7 +170,12 @@ def finalize_elf_header!
shoffset: @section_headers_offset
)
end
def align_up(val, align) = (val + align - 1) / align * align
def dynamic? = @pie || @shared
def header_end
raise Error, "must have ELF header" unless Caotral::Binary::ELF::Header === @elf.header
@elf.header.build.bytesize + @elf.program_headers.sum { |ph| ph.build.bytesize }
end
end
end
end
2 changes: 1 addition & 1 deletion sample/C/movabs.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ uintptr_t get_addr(void) {
int foo = 42;

int main(void) {
return (int)get_addr();
return *(int *)get_addr();
}
10 changes: 10 additions & 0 deletions sample/assembler/large_rodata.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.section .rodata
.balign 16
.fill 9216, 1, 0x41

.text
.globl main
.type main, @function
main:
mov $42, %eax
ret
14 changes: 14 additions & 0 deletions sample/assembler/rela_data.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.section .data
.align 8
ptr:
.quad value
value:
.quad 42
.section .text
.globl _start
_start:
mov ptr(%rip), %rax
mov (%rax), %rdi
mov $60, %rax
syscall

1 change: 1 addition & 0 deletions sig/caotral/binary/elf/section/symtab.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ class Caotral::Binary::ELF::Section::Symtab

def name_offset: () -> Integer
def info: () -> Integer
def shndx: () -> Integer
def value: () -> Integer
end
2 changes: 2 additions & 0 deletions sig/caotral/binary/elf/section_header.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ class Caotral::Binary::ELF::SectionHeader
def type: () -> Symbol?
def info: () -> Integer
def addr: () -> Integer
def writable?: () -> bool
def execinstr?: () -> bool
end
8 changes: 8 additions & 0 deletions sig/caotral/linker.rbs
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
class Caotral::Linker
type pending_relative_relocation = {
relocation: Caotral::Binary::ELF::Section::Rel,
target: Caotral::Binary::ELF::Section,
target_offset: Integer,
symbol: Caotral::Binary::ELF::Section::Symtab,
addend: Integer
}

@inputs: Array[String]
@output: String
@linker: String
Expand Down
6 changes: 6 additions & 0 deletions sig/caotral/linker/finalizer.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ class Caotral::Linker::Finalizer
@shared: bool
@executable: bool
@pie: bool
@pending_relative_relocations: Array[Caotral::Linker::pending_relative_relocation]?

def initialize: (elf: Caotral::Binary::ELF, metadata: Hash[Symbol, untyped], shared: bool, executable: bool, pie: bool, debug: bool) -> void
def apply!: () -> Caotral::Binary::ELF

private

def finalize_relative_relocations!: () -> void
def pending_relative_relocations: () -> Array[Caotral::Linker::pending_relative_relocation]
end
Loading
Loading