/proc/self/exe support
This commit is contained in:
@@ -52,6 +52,9 @@ pub var address_allocator: AddressAllocator = .empty;
|
||||
pub var allocated_pages: std.AutoHashMapUnmanaged(u64, void) = .empty;
|
||||
pub var mutex: std.Thread.Mutex = .{};
|
||||
|
||||
pub var target_exec_path_buf: [std.fs.max_path_bytes]u8 = @splat(0);
|
||||
pub var target_exec_path: []const u8 = undefined;
|
||||
|
||||
/// Initialize the patcher.
|
||||
/// NOTE: This should only be called **once**.
|
||||
pub fn init() !void {
|
||||
|
||||
16
src/main.zig
16
src/main.zig
@@ -49,11 +49,21 @@ pub fn main() !void {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize patcher
|
||||
try Patcher.init();
|
||||
const file = try lookupFile(mem.sliceTo(std.os.argv[arg_index], 0));
|
||||
|
||||
{
|
||||
// Initialize patcher
|
||||
try Patcher.init();
|
||||
// Resolve the absolute path of the target executable. This is needed for the
|
||||
// readlink("/proc/self/exe") interception. We use the file descriptor to get the
|
||||
// authoritative path.
|
||||
var self_buf: [128]u8 = undefined;
|
||||
const fd_path = try std.fmt.bufPrint(&self_buf, "/proc/self/fd/{d}", .{file.handle});
|
||||
Patcher.target_exec_path = try std.fs.readLinkAbsolute(fd_path, &Patcher.target_exec_path_buf);
|
||||
log.debug("Resolved target executable path: {s}", .{Patcher.target_exec_path});
|
||||
}
|
||||
|
||||
// Map file into memory
|
||||
const file = try lookupFile(mem.sliceTo(std.os.argv[arg_index], 0));
|
||||
var file_buffer: [128]u8 = undefined;
|
||||
var file_reader = file.reader(&file_buffer);
|
||||
log.info("--- Loading executable: {s} ---", .{std.os.argv[arg_index]});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const std = @import("std");
|
||||
const linux = std.os.linux;
|
||||
const Patcher = @import("Patcher.zig");
|
||||
|
||||
/// Represents the stack layout pushed by `syscall_entry` before calling the handler.
|
||||
pub const UserRegs = extern struct {
|
||||
@@ -40,6 +41,31 @@ export fn syscall_handler(regs: *UserRegs) void {
|
||||
const arg5 = regs.r8;
|
||||
const arg6 = regs.r9;
|
||||
|
||||
switch (sys) {
|
||||
.readlink => {
|
||||
// readlink(const char *path, char *buf, size_t bufsiz)
|
||||
const path_ptr = @as([*:0]const u8, @ptrFromInt(regs.rdi));
|
||||
// TODO: handle relative paths with cwd
|
||||
if (isProcSelfExe(path_ptr)) {
|
||||
handleReadlink(regs.rsi, regs.rdx, regs);
|
||||
return;
|
||||
}
|
||||
},
|
||||
.readlinkat => {
|
||||
// readlinkat(int dirfd, const char *pathname, char *buf, size_t bufsiz)
|
||||
// We only intercept if pathname is absolute "/proc/self/exe".
|
||||
// TODO: handle relative paths with dirfd pointing to /proc/self
|
||||
// TODO: handle relative paths with dirfd == AT_FDCWD (like readlink)
|
||||
// TODO: handle empty pathname
|
||||
const path_ptr = @as([*:0]const u8, @ptrFromInt(regs.rsi));
|
||||
if (isProcSelfExe(path_ptr)) {
|
||||
handleReadlink(regs.rdx, regs.r10, regs);
|
||||
return;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
std.debug.print("Got syscall {s}\n", .{@tagName(sys)});
|
||||
// For now, we just pass through everything.
|
||||
// In the future, we will switch on `sys` to handle mmap, mprotect, etc.
|
||||
@@ -49,6 +75,25 @@ export fn syscall_handler(regs: *UserRegs) void {
|
||||
regs.rax = result;
|
||||
}
|
||||
|
||||
fn isProcSelfExe(path: [*:0]const u8) bool {
|
||||
const needle = "/proc/self/exe";
|
||||
var i: usize = 0;
|
||||
while (i < needle.len) : (i += 1) {
|
||||
if (path[i] != needle[i]) return false;
|
||||
}
|
||||
return path[i] == 0;
|
||||
}
|
||||
|
||||
fn handleReadlink(buf_addr: u64, buf_size: u64, regs: *UserRegs) void {
|
||||
const target = Patcher.target_exec_path;
|
||||
const len = @min(target.len, buf_size);
|
||||
const dest = @as([*]u8, @ptrFromInt(buf_addr));
|
||||
@memcpy(dest[0..len], target[0..len]);
|
||||
|
||||
// readlink does not null-terminate if the buffer is full, it just returns length.
|
||||
regs.rax = len;
|
||||
}
|
||||
|
||||
/// Assembly trampoline that saves state and calls the Zig handler.
|
||||
pub fn syscall_entry() callconv(.naked) void {
|
||||
asm volatile (
|
||||
|
||||
Reference in New Issue
Block a user