Skip to content

Commit

Permalink
ElfDynLib: resolve lib from system paths
Browse files Browse the repository at this point in the history
Implements the base that should usually work that is
- Check LD_LIBRARY_PATH if the binary is no setuid setgid binary
- Check /lib, /usr/lib, in that order

The missing parts are:
- DT_RPATH and DT_RUNPATH handling from the calling executable
- Reading /etc/ld.so.cache

For more details check man page of dlopen(3)
  • Loading branch information
Cloudef authored and andrewrk committed Aug 24, 2024
1 parent 9374725 commit 2d7c26c
Showing 1 changed file with 64 additions and 1 deletion.
65 changes: 64 additions & 1 deletion lib/std/dynamic_library.zig
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,72 @@ pub const ElfDynLib = struct {

pub const Error = ElfDynLibError;

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

fn resolveFromSearchPath(search_path: []const u8, file_name: []const u8, delim: u8) ?posix.fd_t {
var paths = std.mem.tokenizeScalar(u8, search_path, delim);
while (paths.next()) |p| {
var dir = openPath(p) catch continue;
defer dir.close();
const fd = posix.openat(dir.fd, file_name, .{
.ACCMODE = .RDONLY,
.CLOEXEC = true,
}, 0) catch continue;
return fd;
}
return null;
}

fn resolveFromParent(dir_path: []const u8, file_name: []const u8) ?posix.fd_t {
var dir = std.fs.cwd().openDir(dir_path, .{}) catch return null;
defer dir.close();
return posix.openat(dir.fd, file_name, .{
.ACCMODE = .RDONLY,
.CLOEXEC = true,
}, 0) 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(path_or_name: []const u8) !posix.fd_t {
// If filename contains a slash ("/"), then it is interpreted as a (relative or absolute) pathname
if (std.mem.indexOfScalarPos(u8, path_or_name, 0, '/')) |_| {
return posix.open(path_or_name, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
}

// 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 (posix.getenvZ("LD_LIBRARY_PATH")) |ld_library_path| {
if (resolveFromSearchPath(ld_library_path, path_or_name, ':')) |fd| {
return fd;
}
}
}

// Lastly the directories /lib and /usr/lib are searched (in this exact order)
if (resolveFromParent("/lib", path_or_name)) |fd| return fd;
if (resolveFromParent("/usr/lib", path_or_name)) |fd| return fd;
return error.FileNotFound;
}

/// Trusts the file. Malicious file will be able to execute arbitrary code.
pub fn open(path: []const u8) Error!ElfDynLib {
const fd = try posix.open(path, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
const fd = try resolveFromName(path);
defer posix.close(fd);

const stat = try posix.fstat(fd);
Expand Down

0 comments on commit 2d7c26c

Please sign in to comment.