From 403301a06e1972b3147ba81be88debc298d2cd83 Mon Sep 17 00:00:00 2001 From: Pascal Zittlau Date: Mon, 15 Dec 2025 15:10:42 +0100 Subject: [PATCH] clone with fork-like behaviour --- src/Patcher.zig | 7 +++++- src/main.zig | 13 ++++++++++ src/syscalls.zig | 62 +++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/Patcher.zig b/src/Patcher.zig index 669baf5..428777d 100644 --- a/src/Patcher.zig +++ b/src/Patcher.zig @@ -68,7 +68,12 @@ pub fn init() !void { page_size / @sizeOf(Flicken), ); flicken_templates.putAssumeCapacity("nop", .{ .name = "nop", .bytes = &.{} }); - mem.writeInt(u64, syscall_flicken_bytes[4..][0..8], @intFromPtr(&syscalls.syscall_entry), .little); + mem.writeInt( + u64, + syscall_flicken_bytes[2..][0..8], + @intFromPtr(&syscalls.syscall_entry), + .little, + ); flicken_templates.putAssumeCapacity("syscall", .{ .name = "syscall", .bytes = &syscall_flicken_bytes }); { diff --git a/src/main.zig b/src/main.zig index cb9a1d1..d0b16e6 100644 --- a/src/main.zig +++ b/src/main.zig @@ -339,6 +339,19 @@ test "nolibc_pie_clone_raw" { ); } +test "nolibc_nopie_clone_no_new_stack" { + try testHelper( + &.{ flicker_path, getTestExePath("nolibc_nopie_clone_no_new_stack") }, + "Child: Hello\nParent: Goodbye\n", + ); +} +test "nolibc_pie_clone_no_new_stack" { + try testHelper( + &.{ flicker_path, getTestExePath("nolibc_pie_clone_no_new_stack") }, + "Child: Hello\nParent: Goodbye\n", + ); +} + test "echo" { try testHelper(&.{ "echo", "Hello", "There" }, "Hello There\n"); } diff --git a/src/syscalls.zig b/src/syscalls.zig index 5e16105..1e055b5 100644 --- a/src/syscalls.zig +++ b/src/syscalls.zig @@ -1,6 +1,7 @@ const std = @import("std"); const linux = std.os.linux; const Patcher = @import("Patcher.zig"); +const assert = std.debug.assert; /// Represents the stack layout pushed by `syscall_entry` before calling the handler. pub const UserRegs = extern struct { @@ -32,8 +33,6 @@ pub const UserRegs = extern struct { /// It effectively emulates the syscall instruction while allowing for interception. export fn syscall_handler(regs: *UserRegs) callconv(.c) void { // TODO: Handle signals (masking) to prevent re-entrancy issues if we touch global state. - // TODO: Handle `clone` specially because the child thread wakes up with a fresh stack - // and cannot pop the registers we saved here. const sys: linux.SYS = @enumFromInt(regs.rax); @@ -60,7 +59,8 @@ export fn syscall_handler(regs: *UserRegs) callconv(.c) void { } }, .clone, .clone3 => { - @panic("Clone is not supported yet"); + handleClone(regs); + return; }, .fork, .vfork => { // fork/vfork duplicate the stack (or share it until exec), so the return path via @@ -184,3 +184,59 @@ fn handleReadlink(buf_addr: u64, buf_size: u64, regs: *UserRegs) void { // readlink does not null-terminate if the buffer is full, it just returns length. regs.rax = len; } + +const CloneArgs = extern struct { + flags: u64, + pidfd: u64, + child_tid: u64, + parent_tid: u64, + exit_signal: u64, + stack: u64, + stack_size: u64, + tls: u64, + set_tid: u64, + set_tid_size: u64, + cgroup: u64, +}; + +fn handleClone(regs: *UserRegs) void { + const sys: linux.syscalls.X64 = @enumFromInt(regs.rax); + std.debug.print("got: {}\n", .{sys}); + var child_stack: u64 = 0; + + // Determine stack + if (sys == .clone) { + // clone(flags, stack, ...) + child_stack = regs.rsi; + } else { + // clone3(struct clone_args *args, size_t size) + const args = @as(*const CloneArgs, @ptrFromInt(regs.rdi)); + if (args.stack != 0) { + child_stack = args.stack + args.stack_size; + } + } + std.debug.print("child_stack: {x}\n", .{child_stack}); + + // If no new stack, just execute (like fork) + if (child_stack == 0) { + regs.rax = executeSyscall(regs); + if (regs.rax == 0) { + postCloneChild(regs); + } else { + assert(regs.rax > 0); // TODO:: error handling + postCloneParent(regs); + } + return; + } + + @panic("case with a different stack is not handled yet"); +} + +fn postCloneChild(regs: *UserRegs) void { + _ = regs; + std.debug.print("Child: post clone\n", .{}); +} + +fn postCloneParent(regs: *UserRegs) void { + std.debug.print("Parent: post clone; Child PID: {}\n", .{regs.rax}); +}