Zig 0.17.0-dev (Split by item)

Shortcut keys
?
Show these tips
S
Focus search field
Esc
Clear focus & close tips
U
Jump to source code
↑
Move up in search results
↓
Move down in search results
↵
Go to active search result
This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig standard library. Public Struct

ElfDynLib

Public Struct
std.dynamic_library.ElfDynLib
pub const ElfDynLib = struct

Fields

strings: [*:0]u8
syms: [*]elf.Sym
hash_table: HashTable
versym: ?[*]elf.Versym
verdef: ?*elf.Verdef
memory: []align(std.heap.page_size_min) u8

Structs

Types

Values

  • pub const Error

    Separated to avoid referencing ElfDynLib, because its field types may not be valid on other targets.

Functions

fn openPath(io: Io, path: []const u8) !Io.Dir
fn resolveFromSearchPath(io: Io, search_path: []const u8, file_name: []const u8, delim: u8) ?Io.File
fn resolveFromParent(io: Io, dir_path: []const u8, file_name: []const u8) ?Io.File
fn resolveFromName(io: Io, path_or_name: []const u8, LD_LIBRARY_PATH: ?[]const u8) !Io.File

This implements enough to be able to load system libraries in general Places where it differs from dlopen:

  • DT_RPATH of the calling binary is not used as a search path
  • DT_RUNPATH of the calling binary is not used as a search path
  • /etc/ld.so.cache is not read
pub fn open(path: []const u8, LD_LIBRARY_PATH: ?[]const u8) Error!ElfDynLib

Trusts the file. Malicious file will be able to execute arbitrary code.

pub fn openZ(path_c: [*: 0]const u8, LD_LIBRARY_PATH: ?[]const u8) Error!ElfDynLib

Trusts the file. Malicious file will be able to execute arbitrary code.

pub fn close(self: *ElfDynLib) void

Trusts the file

pub fn lookup(self: *const ElfDynLib, comptime T: type, name: [: 0]const u8) ?T
pub fn lookupAddress(self: *const ElfDynLib, vername: []const u8, name: []const u8) ?usize

ElfDynLib specific Returns the address of the symbol

fn elfToProt(elf_prot: elf.PF) posix.PROT

File

dynamic_library.zig:152
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
pub const ElfDynLib = struct {
    strings: [*:0]u8,
    syms: [*]elf.Sym,
    hash_table: HashTable,
    versym: ?[*]elf.Versym,
    verdef: ?*elf.Verdef,
    memory: []align(std.heap.page_size_min) u8,

    pub const Error = ElfDynLibError;

    const HashTable = union(enum) {
        dt_hash: [*]posix.Elf_Symndx,
        dt_gnu_hash: *elf.gnu_hash.Header,
    };

    fn openPath(io: Io, path: []const u8) !Io.Dir {
        if (path.len == 0) return error.NotDir;
        var parts = std.mem.tokenizeScalar(u8, path, '/');
        var parent = if (path[0] == '/') try Io.Dir.cwd().openDir(io, "/", .{}) else Io.Dir.cwd();
        while (parts.next()) |part| {
            const child = try parent.openDir(io, part, .{});
            parent.close(io);
            parent = child;
        }
        return parent;
    }

    fn resolveFromSearchPath(io: Io, search_path: []const u8, file_name: []const u8, delim: u8) ?Io.File {
        var paths = std.mem.tokenizeScalar(u8, search_path, delim);
        while (paths.next()) |p| {
            var dir = openPath(io, p) catch continue;
            defer dir.close(io);
            return dir.openFile(io, file_name, .{}) catch continue;
        }
        return null;
    }

    fn resolveFromParent(io: Io, dir_path: []const u8, file_name: []const u8) ?Io.File {
        var dir = Io.Dir.cwd().openDir(io, dir_path, .{}) catch return null;
        defer dir.close(io);
        return dir.openFile(io, file_name, .{}) catch null;
    }

    // This implements enough to be able to load system libraries in general
    // Places where it differs from dlopen:
    // - DT_RPATH of the calling binary is not used as a search path
    // - DT_RUNPATH of the calling binary is not used as a search path
    // - /etc/ld.so.cache is not read
    fn resolveFromName(io: Io, path_or_name: []const u8, LD_LIBRARY_PATH: ?[]const u8) !Io.File {
        if (std.mem.findScalarPos(u8, path_or_name, 0, '/')) |_| {
            return Io.Dir.cwd().openFile(io, path_or_name, .{});
        }

        // Only read LD_LIBRARY_PATH if the binary is not setuid/setgid
        if (std.os.linux.geteuid() == std.os.linux.getuid() and
            std.os.linux.getegid() == std.os.linux.getgid())
        {
            if (LD_LIBRARY_PATH) |ld_library_path| {
                if (resolveFromSearchPath(io, ld_library_path, path_or_name, ':')) |file| {
                    return file;
                }
            }
        }

        if (resolveFromParent(io, "/lib", path_or_name)) |file| return file;
        if (resolveFromParent(io, "/lib64", path_or_name)) |file| return file;
        if (resolveFromParent(io, "/usr/lib", path_or_name)) |file| return file;
        if (resolveFromParent(io, "/usr/lib64", path_or_name)) |file| return file;
        return error.FileNotFound;
    }

    /// Trusts the file. Malicious file will be able to execute arbitrary code.
    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.
    pub fn openZ(path_c: [*:0]const u8, LD_LIBRARY_PATH: ?[]const u8) Error!ElfDynLib {
        return open(mem.sliceTo(path_c, 0), LD_LIBRARY_PATH);
    }

    /// Trusts the file
    pub fn close(self: *ElfDynLib) void {
        posix.munmap(self.memory);
        self.* = undefined;
    }

    pub fn lookup(self: *const ElfDynLib, comptime T: type, name: [:0]const u8) ?T {
        if (self.lookupAddress("", name)) |symbol| {
            return @as(T, @ptrFromInt(symbol));
        } else {
            return null;
        }
    }

    pub const GnuHashSection32 = struct {
        symoffset: u32,
        bloom_shift: u32,
        bloom: []u32,
        buckets: []u32,
        chain: [*]elf.gnu_hash.ChainEntry,

        pub fn fromPtr(header: *elf.gnu_hash.Header) @This() {
            const header_offset = @intFromPtr(header);
            const bloom_offset = header_offset + @sizeOf(elf.gnu_hash.Header);
            const buckets_offset = bloom_offset + header.bloom_size * @sizeOf(u32);
            const chain_offset = buckets_offset + header.nbuckets * @sizeOf(u32);

            const bloom_ptr: [*]u32 = @ptrFromInt(bloom_offset);
            const buckets_ptr: [*]u32 = @ptrFromInt(buckets_offset);
            const chain_ptr: [*]elf.gnu_hash.ChainEntry = @ptrFromInt(chain_offset);

            return .{
                .symoffset = header.symoffset,
                .bloom_shift = header.bloom_shift,
                .bloom = bloom_ptr[0..header.bloom_size],
                .buckets = buckets_ptr[0..header.nbuckets],
                .chain = chain_ptr,
            };
        }
    };

    pub const GnuHashSection64 = struct {
        symoffset: u32,
        bloom_shift: u32,
        bloom: []u64,
        buckets: []u32,
        chain: [*]elf.gnu_hash.ChainEntry,

        pub fn fromPtr(header: *elf.gnu_hash.Header) @This() {
            const header_offset = @intFromPtr(header);
            const bloom_offset = header_offset + @sizeOf(elf.gnu_hash.Header);
            const buckets_offset = bloom_offset + header.bloom_size * @sizeOf(u64);
            const chain_offset = buckets_offset + header.nbuckets * @sizeOf(u32);

            const bloom_ptr: [*]u64 = @ptrFromInt(bloom_offset);
            const buckets_ptr: [*]u32 = @ptrFromInt(buckets_offset);
            const chain_ptr: [*]elf.gnu_hash.ChainEntry = @ptrFromInt(chain_offset);

            return .{
                .symoffset = header.symoffset,
                .bloom_shift = header.bloom_shift,
                .bloom = bloom_ptr[0..header.bloom_size],
                .buckets = buckets_ptr[0..header.nbuckets],
                .chain = chain_ptr,
            };
        }
    };

    /// ElfDynLib specific
    /// Returns the address of the symbol
    pub fn lookupAddress(self: *const ElfDynLib, vername: []const u8, name: []const u8) ?usize {
        const maybe_versym = if (self.verdef == null) null else self.versym;

        const OK_TYPES = (1 << elf.STT_NOTYPE | 1 << elf.STT_OBJECT | 1 << elf.STT_FUNC | 1 << elf.STT_COMMON);
        const OK_BINDS = (1 << elf.STB_GLOBAL | 1 << elf.STB_WEAK | 1 << elf.STB_GNU_UNIQUE);

        switch (self.hash_table) {
            .dt_hash => |hashtab| {
                var i: usize = 0;
                while (i < hashtab[1]) : (i += 1) {
                    if (0 == (@as(u32, 1) << @as(u5, @intCast(self.syms[i].st_info & 0xf)) & OK_TYPES)) continue;
                    if (0 == (@as(u32, 1) << @as(u5, @intCast(self.syms[i].st_info >> 4)) & OK_BINDS)) continue;
                    if (0 == self.syms[i].st_shndx) continue;
                    if (!mem.eql(u8, name, mem.sliceTo(self.strings + self.syms[i].st_name, 0))) continue;
                    if (maybe_versym) |versym| {
                        if (!checkver(self.verdef.?, versym[i], vername, self.strings))
                            continue;
                    }
                    return @intFromPtr(self.memory.ptr) + self.syms[i].st_value;
                }
            },
            .dt_gnu_hash => |gnu_hash_header| {
                const GnuHashSection = switch (@bitSizeOf(usize)) {
                    32 => GnuHashSection32,
                    64 => GnuHashSection64,
                    else => |bit_size| @compileError("Unsupported bit size " ++ bit_size),
                };

                const gnu_hash_section: GnuHashSection = .fromPtr(gnu_hash_header);
                const hash = elf.gnu_hash.calculate(name);

                const bloom_index = (hash / @bitSizeOf(usize)) % gnu_hash_header.bloom_size;
                const bloom_val = gnu_hash_section.bloom[bloom_index];

                const bit_index_0 = hash % @bitSizeOf(usize);
                const bit_index_1 = (hash >> @intCast(gnu_hash_header.bloom_shift)) % @bitSizeOf(usize);

                const one: usize = 1;
                const bit_mask: usize = (one << @intCast(bit_index_0)) | (one << @intCast(bit_index_1));

                if (bloom_val & bit_mask != bit_mask) {
                    // Symbol is not in bloom filter, so it definitely isn't here.
                    return null;
                }

                const bucket_index = hash % gnu_hash_header.nbuckets;
                const chain_index = gnu_hash_section.buckets[bucket_index] - gnu_hash_header.symoffset;

                const chains = gnu_hash_section.chain;
                const hash_as_entry: elf.gnu_hash.ChainEntry = @bitCast(hash);

                var current_index = chain_index;
                var at_end_of_chain = false;
                while (!at_end_of_chain) : (current_index += 1) {
                    const current_entry = chains[current_index];
                    at_end_of_chain = current_entry.end_of_chain;

                    if (current_entry.hash != hash_as_entry.hash) continue;

                    // check that symbol matches
                    const symbol_index = current_index + gnu_hash_header.symoffset;
                    const symbol = self.syms[symbol_index];

                    if (0 == (@as(u32, 1) << @as(u5, @intCast(symbol.st_info & 0xf)) & OK_TYPES)) continue;
                    if (0 == (@as(u32, 1) << @as(u5, @intCast(symbol.st_info >> 4)) & OK_BINDS)) continue;
                    if (0 == symbol.st_shndx) continue;

                    const symbol_name = mem.sliceTo(self.strings + symbol.st_name, 0);
                    if (!mem.eql(u8, name, symbol_name)) {
                        continue;
                    }

                    if (maybe_versym) |versym| {
                        if (!checkver(self.verdef.?, versym[symbol_index], vername, self.strings)) {
                            continue;
                        }
                    }

                    return @intFromPtr(self.memory.ptr) + symbol.st_value;
                }
            },
        }

        return null;
    }

    fn elfToProt(elf_prot: elf.PF) posix.PROT {
        return .{
            .READ = elf_prot.R,
            .WRITE = elf_prot.W,
            .EXEC = elf_prot.X,
        };
    }
}