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