clone with fork-like behaviour
This commit is contained in:
@@ -68,7 +68,12 @@ pub fn init() !void {
|
|||||||
page_size / @sizeOf(Flicken),
|
page_size / @sizeOf(Flicken),
|
||||||
);
|
);
|
||||||
flicken_templates.putAssumeCapacity("nop", .{ .name = "nop", .bytes = &.{} });
|
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 });
|
flicken_templates.putAssumeCapacity("syscall", .{ .name = "syscall", .bytes = &syscall_flicken_bytes });
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
13
src/main.zig
13
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" {
|
test "echo" {
|
||||||
try testHelper(&.{ "echo", "Hello", "There" }, "Hello There\n");
|
try testHelper(&.{ "echo", "Hello", "There" }, "Hello There\n");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const linux = std.os.linux;
|
const linux = std.os.linux;
|
||||||
const Patcher = @import("Patcher.zig");
|
const Patcher = @import("Patcher.zig");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
/// 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 {
|
||||||
@@ -32,8 +33,6 @@ pub const UserRegs = extern struct {
|
|||||||
/// It effectively emulates the syscall instruction while allowing for interception.
|
/// It effectively emulates the syscall instruction while allowing for interception.
|
||||||
export fn syscall_handler(regs: *UserRegs) callconv(.c) void {
|
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 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);
|
const sys: linux.SYS = @enumFromInt(regs.rax);
|
||||||
|
|
||||||
@@ -60,7 +59,8 @@ export fn syscall_handler(regs: *UserRegs) callconv(.c) void {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
.clone, .clone3 => {
|
.clone, .clone3 => {
|
||||||
@panic("Clone is not supported yet");
|
handleClone(regs);
|
||||||
|
return;
|
||||||
},
|
},
|
||||||
.fork, .vfork => {
|
.fork, .vfork => {
|
||||||
// fork/vfork duplicate the stack (or share it until exec), so the return path via
|
// 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.
|
// readlink does not null-terminate if the buffer is full, it just returns length.
|
||||||
regs.rax = len;
|
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});
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user