| ... | ... | @@ -7,14 +7,41 @@ const elf = std.elf; |
| 7 | 7 | const windows = std.os.windows; |
| 8 | 8 | const system = std.os.system; |
| 9 | 9 | |
| 10 | | pub const DynLib = switch (builtin.os.tag) { |
| 11 | | .linux => if (!builtin.link_libc or builtin.abi == .musl and builtin.link_mode == .static) |
| 12 | | ElfDynLib |
| 13 | | else |
| 14 | | DlDynLib, |
| 15 | | .windows => WindowsDynLib, |
| 16 | | .macos, .tvos, .watchos, .ios, .freebsd, .netbsd, .openbsd, .dragonfly, .solaris, .illumos => DlDynLib, |
| 17 | | else => void, |
| 10 | /// Cross-platform dynamic library loading and symbol lookup. |
| 11 | /// Platform-specific functionality is available through the `inner` field. |
| 12 | pub const DynLib = struct { |
| 13 | const InnerType = switch (builtin.os.tag) { |
| 14 | .linux => if (!builtin.link_libc or builtin.abi == .musl and builtin.link_mode == .static) |
| 15 | ElfDynLib |
| 16 | else |
| 17 | DlDynLib, |
| 18 | .windows => WindowsDynLib, |
| 19 | .macos, .tvos, .watchos, .ios, .freebsd, .netbsd, .openbsd, .dragonfly, .solaris, .illumos => DlDynLib, |
| 20 | else => @compileError("unsupported platform"), |
| 21 | }; |
| 22 | |
| 23 | inner: InnerType, |
| 24 | |
| 25 | pub const Error = ElfDynLib.Error || DlDynLib.Error || WindowsDynLib.Error; |
| 26 | |
| 27 | /// Trusts the file. Malicious file will be able to execute arbitrary code. |
| 28 | pub fn open(path: []const u8) Error!DynLib { |
| 29 | return .{ .inner = try InnerType.open(path) }; |
| 30 | } |
| 31 | |
| 32 | /// Trusts the file. Malicious file will be able to execute arbitrary code. |
| 33 | pub fn openZ(path_c: [*:0]const u8) Error!DynLib { |
| 34 | return .{ .inner = try InnerType.open(path_c) }; |
| 35 | } |
| 36 | |
| 37 | /// Trusts the file. |
| 38 | pub fn close(self: *DynLib) void { |
| 39 | return self.inner.close(); |
| 40 | } |
| 41 | |
| 42 | pub fn lookup(self: *DynLib, comptime T: type, name: [:0]const u8) ?T { |
| 43 | return self.inner.lookup(T, name); |
| 44 | } |
| 18 | 45 | }; |
| 19 | 46 | |
| 20 | 47 | // The link_map structure is not completely specified beside the fields |
| ... | ... | @@ -59,12 +86,12 @@ pub fn get_DYNAMIC() ?[*]elf.Dyn { |
| 59 | 86 | return @extern([*]elf.Dyn, .{ .name = "_DYNAMIC", .linkage = .weak }); |
| 60 | 87 | } |
| 61 | 88 | |
| 62 | | pub fn linkmap_iterator(phdrs: []elf.Phdr) !LinkMap.Iterator { |
| 89 | pub fn linkmap_iterator(phdrs: []elf.Phdr) error{InvalidExe}!LinkMap.Iterator { |
| 63 | 90 | _ = phdrs; |
| 64 | 91 | const _DYNAMIC = get_DYNAMIC() orelse { |
| 65 | 92 | // No PT_DYNAMIC means this is either a statically-linked program or a |
| 66 | 93 | // badly corrupted dynamically-linked one. |
| 67 | | return LinkMap.Iterator{ .current = null }; |
| 94 | return .{ .current = null }; |
| 68 | 95 | }; |
| 69 | 96 | |
| 70 | 97 | const link_map_ptr = init: { |
| ... | ... | @@ -89,10 +116,10 @@ pub fn linkmap_iterator(phdrs: []elf.Phdr) !LinkMap.Iterator { |
| 89 | 116 | else => {}, |
| 90 | 117 | } |
| 91 | 118 | } |
| 92 | | return LinkMap.Iterator{ .current = null }; |
| 119 | return .{ .current = null }; |
| 93 | 120 | }; |
| 94 | 121 | |
| 95 | | return LinkMap.Iterator{ .current = link_map_ptr }; |
| 122 | return .{ .current = link_map_ptr }; |
| 96 | 123 | } |
| 97 | 124 | |
| 98 | 125 | pub const ElfDynLib = struct { |
| ... | ... | @@ -111,10 +138,10 @@ pub const ElfDynLib = struct { |
| 111 | 138 | ElfStringSectionNotFound, |
| 112 | 139 | ElfSymSectionNotFound, |
| 113 | 140 | ElfHashTableNotFound, |
| 114 | | }; |
| 141 | } || os.OpenError || os.MMapError; |
| 115 | 142 | |
| 116 | 143 | /// Trusts the file. Malicious file will be able to execute arbitrary code. |
| 117 | | pub fn open(path: []const u8) !ElfDynLib { |
| 144 | pub fn open(path: []const u8) Error!ElfDynLib { |
| 118 | 145 | const fd = try os.open(path, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); |
| 119 | 146 | defer os.close(fd); |
| 120 | 147 | |
| ... | ... | @@ -239,7 +266,7 @@ pub const ElfDynLib = struct { |
| 239 | 266 | } |
| 240 | 267 | } |
| 241 | 268 | |
| 242 | | return ElfDynLib{ |
| 269 | return .{ |
| 243 | 270 | .memory = all_loaded_mem, |
| 244 | 271 | .strings = maybe_strings orelse return error.ElfStringSectionNotFound, |
| 245 | 272 | .syms = maybe_syms orelse return error.ElfSymSectionNotFound, |
| ... | ... | @@ -250,7 +277,7 @@ pub const ElfDynLib = struct { |
| 250 | 277 | } |
| 251 | 278 | |
| 252 | 279 | /// Trusts the file. Malicious file will be able to execute arbitrary code. |
| 253 | | pub fn openZ(path_c: [*:0]const u8) !ElfDynLib { |
| 280 | pub fn openZ(path_c: [*:0]const u8) Error!ElfDynLib { |
| 254 | 281 | return open(mem.sliceTo(path_c, 0)); |
| 255 | 282 | } |
| 256 | 283 | |
| ... | ... | @@ -260,7 +287,7 @@ pub const ElfDynLib = struct { |
| 260 | 287 | self.* = undefined; |
| 261 | 288 | } |
| 262 | 289 | |
| 263 | | pub fn lookup(self: *ElfDynLib, comptime T: type, name: [:0]const u8) ?T { |
| 290 | pub fn lookup(self: *const ElfDynLib, comptime T: type, name: [:0]const u8) ?T { |
| 264 | 291 | if (self.lookupAddress("", name)) |symbol| { |
| 265 | 292 | return @as(T, @ptrFromInt(symbol)); |
| 266 | 293 | } else { |
| ... | ... | @@ -268,6 +295,7 @@ pub const ElfDynLib = struct { |
| 268 | 295 | } |
| 269 | 296 | } |
| 270 | 297 | |
| 298 | /// ElfDynLib specific |
| 271 | 299 | /// Returns the address of the symbol |
| 272 | 300 | pub fn lookupAddress(self: *const ElfDynLib, vername: []const u8, name: []const u8) ?usize { |
| 273 | 301 | const maybe_versym = if (self.verdef == null) null else self.versym; |
| ... | ... | @@ -314,41 +342,59 @@ fn checkver(def_arg: *elf.Verdef, vsym_arg: i32, vername: []const u8, strings: [ |
| 314 | 342 | return mem.eql(u8, vername, mem.sliceTo(strings + aux.vda_name, 0)); |
| 315 | 343 | } |
| 316 | 344 | |
| 345 | test "ElfDynLib" { |
| 346 | if (builtin.os.tag != .linux) { |
| 347 | return error.SkipZigTest; |
| 348 | } |
| 349 | |
| 350 | try testing.expectError(error.FileNotFound, ElfDynLib.open("invalid_so.so")); |
| 351 | } |
| 352 | |
| 317 | 353 | pub const WindowsDynLib = struct { |
| 318 | | pub const Error = error{FileNotFound}; |
| 354 | pub const Error = error{ |
| 355 | FileNotFound, |
| 356 | InvalidPath, |
| 357 | } || windows.LoadLibraryError; |
| 319 | 358 | |
| 320 | 359 | dll: windows.HMODULE, |
| 321 | 360 | |
| 322 | | pub fn open(path: []const u8) !WindowsDynLib { |
| 361 | pub fn open(path: []const u8) Error!WindowsDynLib { |
| 323 | 362 | return openEx(path, .none); |
| 324 | 363 | } |
| 325 | 364 | |
| 326 | | pub fn openEx(path: []const u8, flags: windows.LoadLibraryFlags) !WindowsDynLib { |
| 327 | | const path_w = try windows.sliceToPrefixedFileW(null, path); |
| 365 | /// WindowsDynLib specific |
| 366 | /// Opens dynamic library with specified library loading flags. |
| 367 | pub fn openEx(path: []const u8, flags: windows.LoadLibraryFlags) Error!WindowsDynLib { |
| 368 | const path_w = windows.sliceToPrefixedFileW(null, path) catch return error.InvalidPath; |
| 328 | 369 | return openExW(path_w.span().ptr, flags); |
| 329 | 370 | } |
| 330 | 371 | |
| 331 | | pub fn openZ(path_c: [*:0]const u8) !WindowsDynLib { |
| 372 | pub fn openZ(path_c: [*:0]const u8) Error!WindowsDynLib { |
| 332 | 373 | return openExZ(path_c, .none); |
| 333 | 374 | } |
| 334 | 375 | |
| 335 | | pub fn openExZ(path_c: [*:0]const u8, flags: windows.LoadLibraryFlags) !WindowsDynLib { |
| 376 | /// WindowsDynLib specific |
| 377 | /// Opens dynamic library with specified library loading flags. |
| 378 | pub fn openExZ(path_c: [*:0]const u8, flags: windows.LoadLibraryFlags) Error!WindowsDynLib { |
| 336 | 379 | const path_w = try windows.cStrToPrefixedFileW(null, path_c); |
| 337 | 380 | return openExW(path_w.span().ptr, flags); |
| 338 | 381 | } |
| 339 | 382 | |
| 340 | | pub fn openW(path_w: [*:0]const u16) !WindowsDynLib { |
| 383 | /// WindowsDynLib specific |
| 384 | pub fn openW(path_w: [*:0]const u16) Error!WindowsDynLib { |
| 341 | 385 | return openExW(path_w, .none); |
| 342 | 386 | } |
| 343 | 387 | |
| 344 | | pub fn openExW(path_w: [*:0]const u16, flags: windows.LoadLibraryFlags) !WindowsDynLib { |
| 388 | /// WindowsDynLib specific |
| 389 | /// Opens dynamic library with specified library loading flags. |
| 390 | pub fn openExW(path_w: [*:0]const u16, flags: windows.LoadLibraryFlags) Error!WindowsDynLib { |
| 345 | 391 | var offset: usize = 0; |
| 346 | 392 | if (path_w[0] == '\\' and path_w[1] == '?' and path_w[2] == '?' and path_w[3] == '\\') { |
| 347 | 393 | // + 4 to skip over the \??\ |
| 348 | 394 | offset = 4; |
| 349 | 395 | } |
| 350 | 396 | |
| 351 | | return WindowsDynLib{ |
| 397 | return .{ |
| 352 | 398 | .dll = try windows.LoadLibraryExW(path_w + offset, flags), |
| 353 | 399 | }; |
| 354 | 400 | } |
| ... | ... | @@ -368,17 +414,17 @@ pub const WindowsDynLib = struct { |
| 368 | 414 | }; |
| 369 | 415 | |
| 370 | 416 | pub const DlDynLib = struct { |
| 371 | | pub const Error = error{FileNotFound}; |
| 417 | pub const Error = error{ FileNotFound, NameTooLong }; |
| 372 | 418 | |
| 373 | 419 | handle: *anyopaque, |
| 374 | 420 | |
| 375 | | pub fn open(path: []const u8) !DlDynLib { |
| 421 | pub fn open(path: []const u8) Error!DlDynLib { |
| 376 | 422 | const path_c = try os.toPosixPath(path); |
| 377 | 423 | return openZ(&path_c); |
| 378 | 424 | } |
| 379 | 425 | |
| 380 | | pub fn openZ(path_c: [*:0]const u8) !DlDynLib { |
| 381 | | return DlDynLib{ |
| 426 | pub fn openZ(path_c: [*:0]const u8) Error!DlDynLib { |
| 427 | return .{ |
| 382 | 428 | .handle = system.dlopen(path_c, system.RTLD.LAZY) orelse { |
| 383 | 429 | return error.FileNotFound; |
| 384 | 430 | }, |
| ... | ... | @@ -386,7 +432,10 @@ pub const DlDynLib = struct { |
| 386 | 432 | } |
| 387 | 433 | |
| 388 | 434 | pub fn close(self: *DlDynLib) void { |
| 389 | | _ = system.dlclose(self.handle); |
| 435 | switch (std.os.errno(system.dlclose(self.handle))) { |
| 436 | .SUCCESS => return, |
| 437 | else => unreachable, |
| 438 | } |
| 390 | 439 | self.* = undefined; |
| 391 | 440 | } |
| 392 | 441 | |
| ... | ... | @@ -399,6 +448,13 @@ pub const DlDynLib = struct { |
| 399 | 448 | return null; |
| 400 | 449 | } |
| 401 | 450 | } |
| 451 | |
| 452 | /// DlDynLib specific |
| 453 | /// Returns human readable string describing most recent error than occurred from `lookup` |
| 454 | /// or `null` if no error has occurred since initialization or when `getError` was last called. |
| 455 | pub fn getError() ?[:0]const u8 { |
| 456 | return mem.span(system.dlerror()); |
| 457 | } |
| 402 | 458 | }; |
| 403 | 459 | |
| 404 | 460 | test "dynamic_library" { |
| ... | ... | @@ -409,8 +465,5 @@ test "dynamic_library" { |
| 409 | 465 | else => return error.SkipZigTest, |
| 410 | 466 | }; |
| 411 | 467 | |
| 412 | | _ = DynLib.open(libname) catch |err| { |
| 413 | | try testing.expect(err == error.FileNotFound); |
| 414 | | return; |
| 415 | | }; |
| 468 | try testing.expectError(error.FileNotFound, DynLib.open(libname)); |
| 416 | 469 | } |