| ... | @@ -7,14 +7,41 @@ const elf = std.elf; | ... | @@ -7,14 +7,41 @@ const elf = std.elf; |
| 7 | const windows = std.os.windows; | 7 | const windows = std.os.windows; |
| 8 | const system = std.os.system; | 8 | const system = std.os.system; |
| 9 | | 9 | |
| 10 | pub const DynLib = switch (builtin.os.tag) { | 10 | /// Cross-platform dynamic library loading and symbol lookup. |
| 11 | .linux => if (!builtin.link_libc or builtin.abi == .musl and builtin.link_mode == .static) | 11 | /// Platform-specific functionality is available through the `inner` field. |
| 12 | ElfDynLib | 12 | pub const DynLib = struct { |
| 13 | else | 13 | const InnerType = switch (builtin.os.tag) { |
| 14 | DlDynLib, | 14 | .linux => if (!builtin.link_libc or builtin.abi == .musl and builtin.link_mode == .static) |
| 15 | .windows => WindowsDynLib, | 15 | ElfDynLib |
| 16 | .macos, .tvos, .watchos, .ios, .freebsd, .netbsd, .openbsd, .dragonfly, .solaris, .illumos => DlDynLib, | 16 | else |
| 17 | else => void, | 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 | // The link_map structure is not completely specified beside the fields | 47 | // The link_map structure is not completely specified beside the fields |
| ... | @@ -59,12 +86,12 @@ pub fn get_DYNAMIC() ?[*]elf.Dyn { | ... | @@ -59,12 +86,12 @@ pub fn get_DYNAMIC() ?[*]elf.Dyn { |
| 59 | return @extern([*]elf.Dyn, .{ .name = "_DYNAMIC", .linkage = .weak }); | 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 | _ = phdrs; | 90 | _ = phdrs; |
| 64 | const _DYNAMIC = get_DYNAMIC() orelse { | 91 | const _DYNAMIC = get_DYNAMIC() orelse { |
| 65 | // No PT_DYNAMIC means this is either a statically-linked program or a | 92 | // No PT_DYNAMIC means this is either a statically-linked program or a |
| 66 | // badly corrupted dynamically-linked one. | 93 | // badly corrupted dynamically-linked one. |
| 67 | return LinkMap.Iterator{ .current = null }; | 94 | return .{ .current = null }; |
| 68 | }; | 95 | }; |
| 69 | | 96 | |
| 70 | const link_map_ptr = init: { | 97 | const link_map_ptr = init: { |
| ... | @@ -89,10 +116,10 @@ pub fn linkmap_iterator(phdrs: []elf.Phdr) !LinkMap.Iterator { | ... | @@ -89,10 +116,10 @@ pub fn linkmap_iterator(phdrs: []elf.Phdr) !LinkMap.Iterator { |
| 89 | else => {}, | 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 | pub const ElfDynLib = struct { | 125 | pub const ElfDynLib = struct { |
| ... | @@ -111,10 +138,10 @@ pub const ElfDynLib = struct { | ... | @@ -111,10 +138,10 @@ pub const ElfDynLib = struct { |
| 111 | ElfStringSectionNotFound, | 138 | ElfStringSectionNotFound, |
| 112 | ElfSymSectionNotFound, | 139 | ElfSymSectionNotFound, |
| 113 | ElfHashTableNotFound, | 140 | ElfHashTableNotFound, |
| 114 | }; | 141 | } || os.OpenError || os.MMapError; |
| 115 | | 142 | |
| 116 | /// Trusts the file. Malicious file will be able to execute arbitrary code. | 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 | const fd = try os.open(path, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); | 145 | const fd = try os.open(path, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); |
| 119 | defer os.close(fd); | 146 | defer os.close(fd); |
| 120 | | 147 | |
| ... | @@ -239,7 +266,7 @@ pub const ElfDynLib = struct { | ... | @@ -239,7 +266,7 @@ pub const ElfDynLib = struct { |
| 239 | } | 266 | } |
| 240 | } | 267 | } |
| 241 | | 268 | |
| 242 | return ElfDynLib{ | 269 | return .{ |
| 243 | .memory = all_loaded_mem, | 270 | .memory = all_loaded_mem, |
| 244 | .strings = maybe_strings orelse return error.ElfStringSectionNotFound, | 271 | .strings = maybe_strings orelse return error.ElfStringSectionNotFound, |
| 245 | .syms = maybe_syms orelse return error.ElfSymSectionNotFound, | 272 | .syms = maybe_syms orelse return error.ElfSymSectionNotFound, |
| ... | @@ -250,7 +277,7 @@ pub const ElfDynLib = struct { | ... | @@ -250,7 +277,7 @@ pub const ElfDynLib = struct { |
| 250 | } | 277 | } |
| 251 | | 278 | |
| 252 | /// Trusts the file. Malicious file will be able to execute arbitrary code. | 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 | return open(mem.sliceTo(path_c, 0)); | 281 | return open(mem.sliceTo(path_c, 0)); |
| 255 | } | 282 | } |
| 256 | | 283 | |
| ... | @@ -260,7 +287,7 @@ pub const ElfDynLib = struct { | ... | @@ -260,7 +287,7 @@ pub const ElfDynLib = struct { |
| 260 | self.* = undefined; | 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 | if (self.lookupAddress("", name)) |symbol| { | 291 | if (self.lookupAddress("", name)) |symbol| { |
| 265 | return @as(T, @ptrFromInt(symbol)); | 292 | return @as(T, @ptrFromInt(symbol)); |
| 266 | } else { | 293 | } else { |
| ... | @@ -268,6 +295,7 @@ pub const ElfDynLib = struct { | ... | @@ -268,6 +295,7 @@ pub const ElfDynLib = struct { |
| 268 | } | 295 | } |
| 269 | } | 296 | } |
| 270 | | 297 | |
| | 298 | /// ElfDynLib specific |
| 271 | /// Returns the address of the symbol | 299 | /// Returns the address of the symbol |
| 272 | pub fn lookupAddress(self: *const ElfDynLib, vername: []const u8, name: []const u8) ?usize { | 300 | pub fn lookupAddress(self: *const ElfDynLib, vername: []const u8, name: []const u8) ?usize { |
| 273 | const maybe_versym = if (self.verdef == null) null else self.versym; | 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,41 +342,59 @@ fn checkver(def_arg: *elf.Verdef, vsym_arg: i32, vername: []const u8, strings: [ |
| 314 | return mem.eql(u8, vername, mem.sliceTo(strings + aux.vda_name, 0)); | 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 | pub const WindowsDynLib = struct { | 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 | dll: windows.HMODULE, | 359 | dll: windows.HMODULE, |
| 321 | | 360 | |
| 322 | pub fn open(path: []const u8) !WindowsDynLib { | 361 | pub fn open(path: []const u8) Error!WindowsDynLib { |
| 323 | return openEx(path, .none); | 362 | return openEx(path, .none); |
| 324 | } | 363 | } |
| 325 | | 364 | |
| 326 | pub fn openEx(path: []const u8, flags: windows.LoadLibraryFlags) !WindowsDynLib { | 365 | /// WindowsDynLib specific |
| 327 | const path_w = try windows.sliceToPrefixedFileW(null, path); | 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 | return openExW(path_w.span().ptr, flags); | 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 | return openExZ(path_c, .none); | 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 | const path_w = try windows.cStrToPrefixedFileW(null, path_c); | 379 | const path_w = try windows.cStrToPrefixedFileW(null, path_c); |
| 337 | return openExW(path_w.span().ptr, flags); | 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 | return openExW(path_w, .none); | 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 | var offset: usize = 0; | 391 | var offset: usize = 0; |
| 346 | if (path_w[0] == '\\' and path_w[1] == '?' and path_w[2] == '?' and path_w[3] == '\\') { | 392 | if (path_w[0] == '\\' and path_w[1] == '?' and path_w[2] == '?' and path_w[3] == '\\') { |
| 347 | // + 4 to skip over the \??\ | 393 | // + 4 to skip over the \??\ |
| 348 | offset = 4; | 394 | offset = 4; |
| 349 | } | 395 | } |
| 350 | | 396 | |
| 351 | return WindowsDynLib{ | 397 | return .{ |
| 352 | .dll = try windows.LoadLibraryExW(path_w + offset, flags), | 398 | .dll = try windows.LoadLibraryExW(path_w + offset, flags), |
| 353 | }; | 399 | }; |
| 354 | } | 400 | } |
| ... | @@ -368,17 +414,17 @@ pub const WindowsDynLib = struct { | ... | @@ -368,17 +414,17 @@ pub const WindowsDynLib = struct { |
| 368 | }; | 414 | }; |
| 369 | | 415 | |
| 370 | pub const DlDynLib = struct { | 416 | pub const DlDynLib = struct { |
| 371 | pub const Error = error{FileNotFound}; | 417 | pub const Error = error{ FileNotFound, NameTooLong }; |
| 372 | | 418 | |
| 373 | handle: *anyopaque, | 419 | handle: *anyopaque, |
| 374 | | 420 | |
| 375 | pub fn open(path: []const u8) !DlDynLib { | 421 | pub fn open(path: []const u8) Error!DlDynLib { |
| 376 | const path_c = try os.toPosixPath(path); | 422 | const path_c = try os.toPosixPath(path); |
| 377 | return openZ(&path_c); | 423 | return openZ(&path_c); |
| 378 | } | 424 | } |
| 379 | | 425 | |
| 380 | pub fn openZ(path_c: [*:0]const u8) !DlDynLib { | 426 | pub fn openZ(path_c: [*:0]const u8) Error!DlDynLib { |
| 381 | return DlDynLib{ | 427 | return .{ |
| 382 | .handle = system.dlopen(path_c, system.RTLD.LAZY) orelse { | 428 | .handle = system.dlopen(path_c, system.RTLD.LAZY) orelse { |
| 383 | return error.FileNotFound; | 429 | return error.FileNotFound; |
| 384 | }, | 430 | }, |
| ... | @@ -386,7 +432,10 @@ pub const DlDynLib = struct { | ... | @@ -386,7 +432,10 @@ pub const DlDynLib = struct { |
| 386 | } | 432 | } |
| 387 | | 433 | |
| 388 | pub fn close(self: *DlDynLib) void { | 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 | self.* = undefined; | 439 | self.* = undefined; |
| 391 | } | 440 | } |
| 392 | | 441 | |
| ... | @@ -399,6 +448,13 @@ pub const DlDynLib = struct { | ... | @@ -399,6 +448,13 @@ pub const DlDynLib = struct { |
| 399 | return null; | 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 | test "dynamic_library" { | 460 | test "dynamic_library" { |
| ... | @@ -409,8 +465,5 @@ test "dynamic_library" { | ... | @@ -409,8 +465,5 @@ test "dynamic_library" { |
| 409 | else => return error.SkipZigTest, | 465 | else => return error.SkipZigTest, |
| 410 | }; | 466 | }; |
| 411 | | 467 | |
| 412 | _ = DynLib.open(libname) catch |err| { | 468 | try testing.expectError(error.FileNotFound, DynLib.open(libname)); |
| 413 | try testing.expect(err == error.FileNotFound); | | |
| 414 | return; | | |
| 415 | }; | | |
| 416 | } | 469 | } |