/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 allocated_pages: std.AutoHashMapUnmanaged(u64, void) = .empty;
|
||||||
pub var mutex: std.Thread.Mutex = .{};
|
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.
|
/// Initialize the patcher.
|
||||||
/// NOTE: This should only be called **once**.
|
/// NOTE: This should only be called **once**.
|
||||||
pub fn init() !void {
|
pub fn init() !void {
|
||||||
|
|||||||
12
src/main.zig
12
src/main.zig
@@ -49,11 +49,21 @@ pub fn main() !void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const file = try lookupFile(mem.sliceTo(std.os.argv[arg_index], 0));
|
||||||
|
|
||||||
|
{
|
||||||
// Initialize patcher
|
// Initialize patcher
|
||||||
try Patcher.init();
|
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
|
// Map file into memory
|
||||||
const file = try lookupFile(mem.sliceTo(std.os.argv[arg_index], 0));
|
|
||||||
var file_buffer: [128]u8 = undefined;
|
var file_buffer: [128]u8 = undefined;
|
||||||
var file_reader = file.reader(&file_buffer);
|
var file_reader = file.reader(&file_buffer);
|
||||||
log.info("--- Loading executable: {s} ---", .{std.os.argv[arg_index]});
|
log.info("--- Loading executable: {s} ---", .{std.os.argv[arg_index]});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const linux = std.os.linux;
|
const linux = std.os.linux;
|
||||||
|
const Patcher = @import("Patcher.zig");
|
||||||
|
|
||||||
/// Represents the stack layout pushed by `syscall_entry` before calling the handler.
|
/// Represents the stack layout pushed by `syscall_entry` before calling the handler.
|
||||||
pub const UserRegs = extern struct {
|
pub const UserRegs = extern struct {
|
||||||
@@ -40,6 +41,31 @@ export fn syscall_handler(regs: *UserRegs) void {
|
|||||||
const arg5 = regs.r8;
|
const arg5 = regs.r8;
|
||||||
const arg6 = regs.r9;
|
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)});
|
std.debug.print("Got syscall {s}\n", .{@tagName(sys)});
|
||||||
// For now, we just pass through everything.
|
// For now, we just pass through everything.
|
||||||
// In the future, we will switch on `sys` to handle mmap, mprotect, etc.
|
// 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;
|
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.
|
/// Assembly trampoline that saves state and calls the Zig handler.
|
||||||
pub fn syscall_entry() callconv(.naked) void {
|
pub fn syscall_entry() callconv(.naked) void {
|
||||||
asm volatile (
|
asm volatile (
|
||||||
|
|||||||
Reference in New Issue
Block a user