pub fn open(path: []const u8, LD_LIBRARY_PATH: ?[]const u8) Error!ElfDynLib {
const io = std.Options.debug_io;
const file = try resolveFromName(io, path, LD_LIBRARY_PATH);
defer file.close(io);
const stat = try file.stat(io);
const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig;
const page_size = std.heap.pageSize();
// This one is to read the ELF info. We do more mmapping later
// corresponding to the actual LOAD sections.
const file_bytes = try posix.mmap(
null,
mem.alignForward(usize, size, page_size),
.{ .READ = true },
.{ .TYPE = .PRIVATE },
file.handle,
0,
);
defer posix.munmap(file_bytes);
const eh = @as(*elf.Ehdr, @ptrCast(file_bytes.ptr));
if (!mem.eql(u8, eh.e_ident[0..4], elf.MAGIC)) return error.NotElfFile;
if (eh.e_type != elf.ET.DYN) return error.NotDynamicLibrary;
const elf_addr = @intFromPtr(file_bytes.ptr);
// Iterate over the program header entries to find out the
// dynamic vector as well as the total size of the virtual memory.
var maybe_dynv: ?[*]usize = null;
var virt_addr_end: usize = 0;
{
var i: usize = 0;
var ph_addr: usize = elf_addr + eh.e_phoff;
while (i < eh.e_phnum) : ({
i += 1;
ph_addr += eh.e_phentsize;
}) {
const ph = @as(*elf.ElfN.Phdr, @ptrFromInt(ph_addr));
switch (ph.type) {
.LOAD => virt_addr_end = @max(virt_addr_end, ph.vaddr + ph.memsz),
.DYNAMIC => maybe_dynv = @as([*]usize, @ptrFromInt(elf_addr + ph.offset)),
else => {},
}
}
}
const dynv = maybe_dynv orelse return error.MissingDynamicLinkingInformation;
// Reserve the entire range (with no permissions) so that we can do MAP.FIXED below.
const all_loaded_mem = try posix.mmap(
null,
virt_addr_end,
.{},
.{ .TYPE = .PRIVATE, .ANONYMOUS = true },
-1,
0,
);
errdefer posix.munmap(all_loaded_mem);
const base = @intFromPtr(all_loaded_mem.ptr);
// Now iterate again and actually load all the program sections.
{
var i: usize = 0;
var ph_addr: usize = elf_addr + eh.e_phoff;
while (i < eh.e_phnum) : ({
i += 1;
ph_addr += eh.e_phentsize;
}) {
const ph = @as(*elf.ElfN.Phdr, @ptrFromInt(ph_addr));
switch (ph.type) {
.LOAD => {
// The VirtAddr may not be page-aligned; in such case there will be
// extra nonsense mapped before/after the VirtAddr,MemSiz
const aligned_addr = (base + ph.vaddr) & ~(@as(usize, page_size) - 1);
const extra_bytes = (base + ph.vaddr) - aligned_addr;
const extended_memsz = mem.alignForward(usize, ph.memsz + extra_bytes, page_size);
const ptr = @as([*]align(std.heap.page_size_min) u8, @ptrFromInt(aligned_addr));
const prot = elfToProt(ph.flags);
_ = try posix.mmap(
ptr,
extended_memsz,
prot,
.{ .TYPE = .PRIVATE, .FIXED = true },
file.handle,
ph.offset - extra_bytes,
);
},
else => {},
}
}
}
var maybe_strings: ?[*:0]u8 = null;
var maybe_syms: ?[*]elf.Sym = null;
var maybe_hashtab: ?[*]posix.Elf_Symndx = null;
var maybe_gnu_hash: ?*elf.gnu_hash.Header = null;
var maybe_versym: ?[*]elf.Versym = null;
var maybe_verdef: ?*elf.Verdef = null;
{
var i: usize = 0;
while (dynv[i] != 0) : (i += 2) {
const p = base + dynv[i + 1];
switch (dynv[i]) {
elf.DT_STRTAB => maybe_strings = @ptrFromInt(p),
elf.DT_SYMTAB => maybe_syms = @ptrFromInt(p),
elf.DT_HASH => maybe_hashtab = @ptrFromInt(p),
elf.DT_GNU_HASH => maybe_gnu_hash = @ptrFromInt(p),
elf.DT_VERSYM => maybe_versym = @ptrFromInt(p),
elf.DT_VERDEF => maybe_verdef = @ptrFromInt(p),
else => {},
}
}
}
const hash_table: HashTable = if (maybe_gnu_hash) |gnu_hash|
.{ .dt_gnu_hash = gnu_hash }
else if (maybe_hashtab) |hashtab|
.{ .dt_hash = hashtab }
else
return error.ElfHashTableNotFound;
return .{
.memory = all_loaded_mem,
.strings = maybe_strings orelse return error.ElfStringSectionNotFound,
.syms = maybe_syms orelse return error.ElfSymSectionNotFound,
.hash_table = hash_table,
.versym = maybe_versym,
.verdef = maybe_verdef,
};
}
Trusts the file. Malicious file will be able to execute arbitrary code.