1const std = @import("std.zig"); 2const builtin = @import("builtin"); 3const mem = std.mem; 4const os = std.os; 5const assert = std.debug.assert; 6const testing = std.testing; 7const elf = std.elf; 8const windows = std.os.windows; 9const system = std.os.system; 10const maxInt = std.math.maxInt; 11const max = std.math.max; 12 13pub const DynLib = switch (builtin.os.tag) { 14 .linux => if (builtin.link_libc) DlDynlib else ElfDynLib, 15 .windows => WindowsDynLib, 16 .macos, .tvos, .watchos, .ios, .freebsd, .netbsd, .openbsd, .dragonfly, .solaris => DlDynlib, 17 else => void, 18}; 19 20// The link_map structure is not completely specified beside the fields 21// reported below, any libc is free to store additional data in the remaining 22// space. 23// An iterator is provided in order to traverse the linked list in a idiomatic 24// fashion. 25const LinkMap = extern struct { 26 l_addr: usize, 27 l_name: [*:0]const u8, 28 l_ld: ?*elf.Dyn, 29 l_next: ?*LinkMap, 30 l_prev: ?*LinkMap, 31 32 pub const Iterator = struct { 33 current: ?*LinkMap, 34 35 pub fn end(self: *Iterator) bool { 36 return self.current == null; 37 } 38 39 pub fn next(self: *Iterator) ?*LinkMap { 40 if (self.current) |it| { 41 self.current = it.l_next; 42 return it; 43 } 44 return null; 45 } 46 }; 47}; 48 49const RDebug = extern struct { 50 r_version: i32, 51 r_map: ?*LinkMap, 52 r_brk: usize, 53 r_ldbase: usize, 54}; 55 56/// TODO make it possible to reference this same external symbol 2x so we don't need this 57/// helper function. 58pub fn get_DYNAMIC() ?[*]elf.Dyn { 59 return @extern([*]elf.Dyn, .{ .name = "_DYNAMIC", .linkage = .Weak }); 60} 61 62pub fn linkmap_iterator(phdrs: []elf.Phdr) !LinkMap.Iterator { 63 _ = phdrs; 64 const _DYNAMIC = get_DYNAMIC() orelse { 65 // No PT_DYNAMIC means this is either a statically-linked program or a 66 // badly corrupted dynamically-linked one. 67 return LinkMap.Iterator{ .current = null }; 68 }; 69 70 const link_map_ptr = init: { 71 var i: usize = 0; 72 while (_DYNAMIC[i].d_tag != elf.DT_NULL) : (i += 1) { 73 switch (_DYNAMIC[i].d_tag) { 74 elf.DT_DEBUG => { 75 const ptr = @intToPtr(?*RDebug, _DYNAMIC[i].d_val); 76 if (ptr) |r_debug| { 77 if (r_debug.r_version != 1) return error.InvalidExe; 78 break :init r_debug.r_map; 79 } 80 }, 81 elf.DT_PLTGOT => { 82 const ptr = @intToPtr(?[*]usize, _DYNAMIC[i].d_val); 83 if (ptr) |got_table| { 84 // The address to the link_map structure is stored in 85 // the second slot 86 break :init @intToPtr(?*LinkMap, got_table[1]); 87 } 88 }, 89 else => {}, 90 } 91 } 92 return LinkMap.Iterator{ .current = null }; 93 }; 94 95 return LinkMap.Iterator{ .current = link_map_ptr }; 96} 97 98pub const ElfDynLib = struct { 99 strings: [*:0]u8, 100 syms: [*]elf.Sym, 101 hashtab: [*]os.Elf_Symndx, 102 versym: ?[*]u16, 103 verdef: ?*elf.Verdef, 104 memory: []align(mem.page_size) u8, 105 106 pub const Error = error{ 107 NotElfFile, 108 NotDynamicLibrary, 109 MissingDynamicLinkingInformation, 110 ElfStringSectionNotFound, 111 ElfSymSectionNotFound, 112 ElfHashTableNotFound, 113 }; 114 115 /// Trusts the file. Malicious file will be able to execute arbitrary code. 116 pub fn open(path: []const u8) !ElfDynLib { 117 const fd = try os.open(path, 0, os.O.RDONLY | os.O.CLOEXEC); 118 defer os.close(fd); 119 120 const stat = try os.fstat(fd); 121 const size = try std.math.cast(usize, stat.size); 122 123 // This one is to read the ELF info. We do more mmapping later 124 // corresponding to the actual LOAD sections. 125 const file_bytes = try os.mmap( 126 null, 127 mem.alignForward(size, mem.page_size), 128 os.PROT.READ, 129 os.MAP.PRIVATE, 130 fd, 131 0, 132 ); 133 defer os.munmap(file_bytes); 134 135 const eh = @ptrCast(*elf.Ehdr, file_bytes.ptr); 136 if (!mem.eql(u8, eh.e_ident[0..4], "\x7fELF")) return error.NotElfFile; 137 if (eh.e_type != elf.ET.DYN) return error.NotDynamicLibrary; 138 139 const elf_addr = @ptrToInt(file_bytes.ptr); 140 141 // Iterate over the program header entries to find out the 142 // dynamic vector as well as the total size of the virtual memory. 143 var maybe_dynv: ?[*]usize = null; 144 var virt_addr_end: usize = 0; 145 { 146 var i: usize = 0; 147 var ph_addr: usize = elf_addr + eh.e_phoff; 148 while (i < eh.e_phnum) : ({ 149 i += 1; 150 ph_addr += eh.e_phentsize; 151 }) { 152 const ph = @intToPtr(*elf.Phdr, ph_addr); 153 switch (ph.p_type) { 154 elf.PT_LOAD => virt_addr_end = max(virt_addr_end, ph.p_vaddr + ph.p_memsz), 155 elf.PT_DYNAMIC => maybe_dynv = @intToPtr([*]usize, elf_addr + ph.p_offset), 156 else => {}, 157 } 158 } 159 } 160 const dynv = maybe_dynv orelse return error.MissingDynamicLinkingInformation; 161 162 // Reserve the entire range (with no permissions) so that we can do MAP.FIXED below. 163 const all_loaded_mem = try os.mmap( 164 null, 165 virt_addr_end, 166 os.PROT.NONE, 167 os.MAP.PRIVATE | os.MAP.ANONYMOUS, 168 -1, 169 0, 170 ); 171 errdefer os.munmap(all_loaded_mem); 172 173 const base = @ptrToInt(all_loaded_mem.ptr); 174 175 // Now iterate again and actually load all the program sections. 176 { 177 var i: usize = 0; 178 var ph_addr: usize = elf_addr + eh.e_phoff; 179 while (i < eh.e_phnum) : ({ 180 i += 1; 181 ph_addr += eh.e_phentsize; 182 }) { 183 const ph = @intToPtr(*elf.Phdr, ph_addr); 184 switch (ph.p_type) { 185 elf.PT_LOAD => { 186 // The VirtAddr may not be page-aligned; in such case there will be 187 // extra nonsense mapped before/after the VirtAddr,MemSiz 188 const aligned_addr = (base + ph.p_vaddr) & ~(@as(usize, mem.page_size) - 1); 189 const extra_bytes = (base + ph.p_vaddr) - aligned_addr; 190 const extended_memsz = mem.alignForward(ph.p_memsz + extra_bytes, mem.page_size); 191 const ptr = @intToPtr([*]align(mem.page_size) u8, aligned_addr); 192 const prot = elfToMmapProt(ph.p_flags); 193 if ((ph.p_flags & elf.PF_W) == 0) { 194 // If it does not need write access, it can be mapped from the fd. 195 _ = try os.mmap( 196 ptr, 197 extended_memsz, 198 prot, 199 os.MAP.PRIVATE | os.MAP.FIXED, 200 fd, 201 ph.p_offset - extra_bytes, 202 ); 203 } else { 204 const sect_mem = try os.mmap( 205 ptr, 206 extended_memsz, 207 prot, 208 os.MAP.PRIVATE | os.MAP.FIXED | os.MAP.ANONYMOUS, 209 -1, 210 0, 211 ); 212 mem.copy(u8, sect_mem, file_bytes[0..ph.p_filesz]); 213 } 214 }, 215 else => {}, 216 } 217 } 218 } 219 220 var maybe_strings: ?[*:0]u8 = null; 221 var maybe_syms: ?[*]elf.Sym = null; 222 var maybe_hashtab: ?[*]os.Elf_Symndx = null; 223 var maybe_versym: ?[*]u16 = null; 224 var maybe_verdef: ?*elf.Verdef = null; 225 226 { 227 var i: usize = 0; 228 while (dynv[i] != 0) : (i += 2) { 229 const p = base + dynv[i + 1]; 230 switch (dynv[i]) { 231 elf.DT_STRTAB => maybe_strings = @intToPtr([*:0]u8, p), 232 elf.DT_SYMTAB => maybe_syms = @intToPtr([*]elf.Sym, p), 233 elf.DT_HASH => maybe_hashtab = @intToPtr([*]os.Elf_Symndx, p), 234 elf.DT_VERSYM => maybe_versym = @intToPtr([*]u16, p), 235 elf.DT_VERDEF => maybe_verdef = @intToPtr(*elf.Verdef, p), 236 else => {}, 237 } 238 } 239 } 240 241 return ElfDynLib{ 242 .memory = all_loaded_mem, 243 .strings = maybe_strings orelse return error.ElfStringSectionNotFound, 244 .syms = maybe_syms orelse return error.ElfSymSectionNotFound, 245 .hashtab = maybe_hashtab orelse return error.ElfHashTableNotFound, 246 .versym = maybe_versym, 247 .verdef = maybe_verdef, 248 }; 249 } 250 251 /// Trusts the file. Malicious file will be able to execute arbitrary code. 252 pub fn openZ(path_c: [*:0]const u8) !ElfDynLib { 253 return open(mem.sliceTo(path_c, 0)); 254 } 255 256 /// Trusts the file 257 pub fn close(self: *ElfDynLib) void { 258 os.munmap(self.memory); 259 self.* = undefined; 260 } 261 262 pub fn lookup(self: *ElfDynLib, comptime T: type, name: [:0]const u8) ?T { 263 if (self.lookupAddress("", name)) |symbol| { 264 return @intToPtr(T, symbol); 265 } else { 266 return null; 267 } 268 } 269 270 /// Returns the address of the symbol 271 pub fn lookupAddress(self: *const ElfDynLib, vername: []const u8, name: []const u8) ?usize { 272 const maybe_versym = if (self.verdef == null) null else self.versym; 273 274 const OK_TYPES = (1 << elf.STT_NOTYPE | 1 << elf.STT_OBJECT | 1 << elf.STT_FUNC | 1 << elf.STT_COMMON); 275 const OK_BINDS = (1 << elf.STB_GLOBAL | 1 << elf.STB_WEAK | 1 << elf.STB_GNU_UNIQUE); 276 277 var i: usize = 0; 278 while (i < self.hashtab[1]) : (i += 1) { 279 if (0 == (@as(u32, 1) << @intCast(u5, self.syms[i].st_info & 0xf) & OK_TYPES)) continue; 280 if (0 == (@as(u32, 1) << @intCast(u5, self.syms[i].st_info >> 4) & OK_BINDS)) continue; 281 if (0 == self.syms[i].st_shndx) continue; 282 if (!mem.eql(u8, name, mem.sliceTo(self.strings + self.syms[i].st_name, 0))) continue; 283 if (maybe_versym) |versym| { 284 if (!checkver(self.verdef.?, versym[i], vername, self.strings)) 285 continue; 286 } 287 return @ptrToInt(self.memory.ptr) + self.syms[i].st_value; 288 } 289 290 return null; 291 } 292 293 fn elfToMmapProt(elf_prot: u64) u32 { 294 var result: u32 = os.PROT.NONE; 295 if ((elf_prot & elf.PF_R) != 0) result |= os.PROT.READ; 296 if ((elf_prot & elf.PF_W) != 0) result |= os.PROT.WRITE; 297 if ((elf_prot & elf.PF_X) != 0) result |= os.PROT.EXEC; 298 return result; 299 } 300}; 301 302fn checkver(def_arg: *elf.Verdef, vsym_arg: i32, vername: []const u8, strings: [*:0]u8) bool { 303 var def = def_arg; 304 const vsym = @bitCast(u32, vsym_arg) & 0x7fff; 305 while (true) { 306 if (0 == (def.vd_flags & elf.VER_FLG_BASE) and (def.vd_ndx & 0x7fff) == vsym) 307 break; 308 if (def.vd_next == 0) 309 return false; 310 def = @intToPtr(*elf.Verdef, @ptrToInt(def) + def.vd_next); 311 } 312 const aux = @intToPtr(*elf.Verdaux, @ptrToInt(def) + def.vd_aux); 313 return mem.eql(u8, vername, mem.sliceTo(strings + aux.vda_name, 0)); 314} 315 316pub const WindowsDynLib = struct { 317 pub const Error = error{FileNotFound}; 318 319 dll: windows.HMODULE, 320 321 pub fn open(path: []const u8) !WindowsDynLib { 322 const path_w = try windows.sliceToPrefixedFileW(path); 323 return openW(path_w.span().ptr); 324 } 325 326 pub fn openZ(path_c: [*:0]const u8) !WindowsDynLib { 327 const path_w = try windows.cStrToPrefixedFileW(path_c); 328 return openW(path_w.span().ptr); 329 } 330 331 pub fn openW(path_w: [*:0]const u16) !WindowsDynLib { 332 var offset: usize = 0; 333 if (path_w[0] == '\\' and path_w[1] == '?' and path_w[2] == '?' and path_w[3] == '\\') { 334 // + 4 to skip over the \??\ 335 offset = 4; 336 } 337 338 return WindowsDynLib{ 339 .dll = try windows.LoadLibraryW(path_w + offset), 340 }; 341 } 342 343 pub fn close(self: *WindowsDynLib) void { 344 windows.FreeLibrary(self.dll); 345 self.* = undefined; 346 } 347 348 pub fn lookup(self: *WindowsDynLib, comptime T: type, name: [:0]const u8) ?T { 349 if (windows.kernel32.GetProcAddress(self.dll, name.ptr)) |addr| { 350 return @ptrCast(T, addr); 351 } else { 352 return null; 353 } 354 } 355}; 356 357pub const DlDynlib = struct { 358 pub const Error = error{FileNotFound}; 359 360 handle: *anyopaque, 361 362 pub fn open(path: []const u8) !DlDynlib { 363 const path_c = try os.toPosixPath(path); 364 return openZ(&path_c); 365 } 366 367 pub fn openZ(path_c: [*:0]const u8) !DlDynlib { 368 return DlDynlib{ 369 .handle = system.dlopen(path_c, system.RTLD.LAZY) orelse { 370 return error.FileNotFound; 371 }, 372 }; 373 } 374 375 pub fn close(self: *DlDynlib) void { 376 _ = system.dlclose(self.handle); 377 self.* = undefined; 378 } 379 380 pub fn lookup(self: *DlDynlib, comptime T: type, name: [:0]const u8) ?T { 381 // dlsym (and other dl-functions) secretly take shadow parameter - return address on stack 382 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66826 383 if (@call(.{ .modifier = .never_tail }, system.dlsym, .{ self.handle, name.ptr })) |symbol| { 384 return @ptrCast(T, symbol); 385 } else { 386 return null; 387 } 388 } 389}; 390 391test "dynamic_library" { 392 const libname = switch (builtin.os.tag) { 393 .linux, .freebsd, .openbsd => "invalid_so.so", 394 .windows => "invalid_dll.dll", 395 .macos, .tvos, .watchos, .ios => "invalid_dylib.dylib", 396 else => return error.SkipZigTest, 397 }; 398 399 _ = DynLib.open(libname) catch |err| { 400 try testing.expect(err == error.FileNotFound); 401 return; 402 }; 403} 404