Compare commits
9 Commits
1557b82c1d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d25cf59380 | |||
| d52cf8aaaf | |||
| eea0e6204d | |||
| 403fd6031b | |||
| de10ce58e2 | |||
| 3d7532c906 | |||
| 5d146140b9 | |||
| 7161b6d1a2 | |||
| 7eb5601eb6 |
11
docs/TODO.md
11
docs/TODO.md
@@ -18,10 +18,11 @@ IvyBridge(2012) and AMD Zen 2 Family 17H(2019) and Linux 5.9(2020).
|
|||||||
- [x] `rt_sigreturn`: we can't use the normal `syscall` interception because we push something onto
|
- [x] `rt_sigreturn`: we can't use the normal `syscall` interception because we push something onto
|
||||||
the stack, so `ucontext` isn't on top anymore.
|
the stack, so `ucontext` isn't on top anymore.
|
||||||
- [x] `/proc/self/exe`: intercept calls to `readlink`/`readlinkat` with that as argument
|
- [x] `/proc/self/exe`: intercept calls to `readlink`/`readlinkat` with that as argument
|
||||||
- [ ] `auxv`: check if that is setup correctly and completely
|
- [x] `auxv`: check if that is setup correctly and completely
|
||||||
- [ ] JIT support: intercept `mmap`, `mprotect` and `mremap` that change pages to be executable
|
- [x] JIT support: intercept `mmap`, `mprotect` and `mremap` that change pages to be executable
|
||||||
- [ ] `SIGILL` patching fallback
|
- [ ] `SIGILL` patching fallback
|
||||||
- [ ] `vdso` handling
|
- [x] `vdso` handling
|
||||||
|
- [x] check why the libc tests are flaky
|
||||||
|
|
||||||
## Minor things
|
## Minor things
|
||||||
|
|
||||||
@@ -30,6 +31,8 @@ IvyBridge(2012) and AMD Zen 2 Family 17H(2019) and Linux 5.9(2020).
|
|||||||
- [ ] Ghost page edge case: In all patch strategies, if a range spans multiple pages and we `mmap`
|
- [ ] Ghost page edge case: In all patch strategies, if a range spans multiple pages and we `mmap`
|
||||||
the first one but can't `mmap` the second one we just let the first one mapped. It would be better
|
the first one but can't `mmap` the second one we just let the first one mapped. It would be better
|
||||||
to unmap them
|
to unmap them
|
||||||
|
- [ ] Right now when patching we mmap a page and may not use it, but we still leave it mapped. This
|
||||||
|
leaks memory. If we fix this correctly the Ghost page issue is also fixed
|
||||||
- [ ] Re-entrancy for `patchRegion`
|
- [ ] Re-entrancy for `patchRegion`
|
||||||
- when a signal comes, while we are in that function, and we need to patch something due to the
|
- when a signal comes, while we are in that function, and we need to patch something due to the
|
||||||
signal we will deadlock
|
signal we will deadlock
|
||||||
@@ -45,3 +48,5 @@ IvyBridge(2012) and AMD Zen 2 Family 17H(2019) and Linux 5.9(2020).
|
|||||||
- [ ] `modify_ldt`: check what we need to intercept and change
|
- [ ] `modify_ldt`: check what we need to intercept and change
|
||||||
- [ ] `set_tid_address`: check what we need to intercept and change
|
- [ ] `set_tid_address`: check what we need to intercept and change
|
||||||
- [ ] performance optimizations for patched code? Peephole might be possible
|
- [ ] performance optimizations for patched code? Peephole might be possible
|
||||||
|
- [ ] maybe add a way to run something after the client is finished
|
||||||
|
- could be useful for statistics, cleanup(if necessary), or notifying of suppressed warnings
|
||||||
|
|||||||
@@ -209,9 +209,13 @@ pub const Statistics = struct {
|
|||||||
/// Scans a memory region for instructions that require patching and applies the patches
|
/// Scans a memory region for instructions that require patching and applies the patches
|
||||||
/// using a hierarchy of tactics (Direct/Punning -> Successor Eviction -> Neighbor Eviction).
|
/// using a hierarchy of tactics (Direct/Punning -> Successor Eviction -> Neighbor Eviction).
|
||||||
///
|
///
|
||||||
/// The region is processed Back-to-Front to ensure that modifications (punning) only
|
/// NOTE: This function leaves the region as R|W and the caller is responsible for changing it to
|
||||||
/// constrain instructions that have already been processed or are locked.
|
/// the desired protection
|
||||||
pub fn patchRegion(region: []align(page_size) u8) !void {
|
pub fn patchRegion(region: []align(page_size) u8) !void {
|
||||||
|
log.info(
|
||||||
|
"Patching region: 0x{x} - 0x{x}",
|
||||||
|
.{ @intFromPtr(region.ptr), @intFromPtr(®ion[region.len - 1]) },
|
||||||
|
);
|
||||||
// For now just do a coarse lock.
|
// For now just do a coarse lock.
|
||||||
// TODO: should we make this more fine grained?
|
// TODO: should we make this more fine grained?
|
||||||
mutex.lock();
|
mutex.lock();
|
||||||
@@ -296,8 +300,6 @@ pub fn patchRegion(region: []align(page_size) u8) !void {
|
|||||||
{
|
{
|
||||||
// Apply patches.
|
// Apply patches.
|
||||||
try posix.mprotect(region, posix.PROT.READ | posix.PROT.WRITE);
|
try posix.mprotect(region, posix.PROT.READ | posix.PROT.WRITE);
|
||||||
defer posix.mprotect(region, posix.PROT.READ | posix.PROT.EXEC) catch
|
|
||||||
@panic("patchRegion: mprotect back to R|X failed. Can't continue");
|
|
||||||
|
|
||||||
var stats = Statistics.empty;
|
var stats = Statistics.empty;
|
||||||
// Used to track which bytes have been modified or used for constraints (punning),
|
// Used to track which bytes have been modified or used for constraints (punning),
|
||||||
@@ -854,7 +856,7 @@ fn ensureRangeWritable(
|
|||||||
const gop = try allocated_pages.getOrPut(gpa, page_addr);
|
const gop = try allocated_pages.getOrPut(gpa, page_addr);
|
||||||
if (gop.found_existing) {
|
if (gop.found_existing) {
|
||||||
const ptr: [*]align(page_size) u8 = @ptrFromInt(page_addr);
|
const ptr: [*]align(page_size) u8 = @ptrFromInt(page_addr);
|
||||||
try posix.mprotect(ptr[0..page_addr], protection);
|
try posix.mprotect(ptr[0..page_size], protection);
|
||||||
} else {
|
} else {
|
||||||
const addr = posix.mmap(
|
const addr = posix.mmap(
|
||||||
@ptrFromInt(page_addr),
|
@ptrFromInt(page_addr),
|
||||||
|
|||||||
@@ -6,10 +6,14 @@ const log = std.log.scoped(.disassembler);
|
|||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
pub const InstructionIterator = struct {
|
pub const InstructionIterator = struct {
|
||||||
|
/// Maximum number of warnings to print per iterator before suppressing.
|
||||||
|
pub var max_warnings: u64 = 3;
|
||||||
|
|
||||||
decoder: zydis.ZydisDecoder,
|
decoder: zydis.ZydisDecoder,
|
||||||
bytes: []const u8,
|
bytes: []const u8,
|
||||||
instruction: zydis.ZydisDecodedInstruction,
|
instruction: zydis.ZydisDecodedInstruction,
|
||||||
operands: [zydis.ZYDIS_MAX_OPERAND_COUNT]zydis.ZydisDecodedOperand,
|
operands: [zydis.ZYDIS_MAX_OPERAND_COUNT]zydis.ZydisDecodedOperand,
|
||||||
|
warnings: usize = 0,
|
||||||
|
|
||||||
pub fn init(bytes: []const u8) InstructionIterator {
|
pub fn init(bytes: []const u8) InstructionIterator {
|
||||||
var decoder: zydis.ZydisDecoder = undefined;
|
var decoder: zydis.ZydisDecoder = undefined;
|
||||||
@@ -38,27 +42,33 @@ pub const InstructionIterator = struct {
|
|||||||
var address: u64 = @intFromPtr(iterator.bytes.ptr);
|
var address: u64 = @intFromPtr(iterator.bytes.ptr);
|
||||||
|
|
||||||
while (!zydis.ZYAN_SUCCESS(status)) {
|
while (!zydis.ZYAN_SUCCESS(status)) {
|
||||||
// TODO: handle common padding bytes
|
if (status == zydis.ZYDIS_STATUS_NO_MORE_DATA) {
|
||||||
switch (status) {
|
log.debug("next: Got status: NO_MORE_DATA. Iterator completed.", .{});
|
||||||
zydis.ZYDIS_STATUS_NO_MORE_DATA => {
|
return null;
|
||||||
log.info("next: Got status: NO_MORE_DATA. Iterator completed.", .{});
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
zydis.ZYDIS_STATUS_ILLEGAL_LOCK => log.warn("next: Got status: ILLEGAL_LOCK. " ++
|
|
||||||
"Byte stepping, to find next valid instruction begin", .{}),
|
|
||||||
zydis.ZYDIS_STATUS_DECODING_ERROR => log.warn("next: Got status: DECODING_ERROR. " ++
|
|
||||||
"Byte stepping, to find next valid instruction begin", .{}),
|
|
||||||
else => log.warn("next: Got unknown status: 0x{x}. Byte stepping, to find next " ++
|
|
||||||
"valid instruction begin", .{status}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: handle common padding bytes
|
||||||
// TODO: add a flag to instead return an error
|
// TODO: add a flag to instead return an error
|
||||||
|
iterator.warnings += 1;
|
||||||
|
if (iterator.warnings <= max_warnings) {
|
||||||
|
const err_desc = switch (status) {
|
||||||
|
zydis.ZYDIS_STATUS_ILLEGAL_LOCK => "ILLEGAL_LOCK",
|
||||||
|
zydis.ZYDIS_STATUS_DECODING_ERROR => "DECODING_ERROR",
|
||||||
|
zydis.ZYDIS_STATUS_INVALID_MAP => "INVALID_MAP",
|
||||||
|
else => "UNKNOWN",
|
||||||
|
};
|
||||||
|
log.warn(
|
||||||
|
"next: Got status: {s} (0x{x}). Byte stepping, for next instruction begin",
|
||||||
|
.{ err_desc, status },
|
||||||
|
);
|
||||||
|
if (iterator.warnings == max_warnings) {
|
||||||
|
log.warn("next: Suppressing further warnings for this disassembly.", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.debug(
|
log.debug(
|
||||||
"next: instruction length: {}, address: 0x{x}, bytes: 0x{x}",
|
"next: skipping byte at address: 0x{x}, byte: 0x{x}",
|
||||||
.{
|
.{ address, iterator.bytes[0] },
|
||||||
iterator.instruction.length,
|
|
||||||
address,
|
|
||||||
iterator.bytes[0..iterator.instruction.length],
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
iterator.bytes = iterator.bytes[1..];
|
iterator.bytes = iterator.bytes[1..];
|
||||||
|
|||||||
123
src/main.zig
123
src/main.zig
@@ -71,6 +71,7 @@ pub fn main() !void {
|
|||||||
const base = try loadStaticElf(ehdr, &file_reader);
|
const base = try loadStaticElf(ehdr, &file_reader);
|
||||||
const entry = ehdr.entry + if (ehdr.type == .DYN) base else 0;
|
const entry = ehdr.entry + if (ehdr.type == .DYN) base else 0;
|
||||||
log.info("Executable loaded: base=0x{x}, entry=0x{x}", .{ base, entry });
|
log.info("Executable loaded: base=0x{x}, entry=0x{x}", .{ base, entry });
|
||||||
|
try patchLoadedElf(base);
|
||||||
|
|
||||||
// Check for dynamic linker
|
// Check for dynamic linker
|
||||||
var maybe_interp_base: ?usize = null;
|
var maybe_interp_base: ?usize = null;
|
||||||
@@ -102,13 +103,13 @@ pub fn main() !void {
|
|||||||
"Interpreter loaded: base=0x{x}, entry=0x{x}",
|
"Interpreter loaded: base=0x{x}, entry=0x{x}",
|
||||||
.{ interp_base, maybe_interp_entry.? },
|
.{ interp_base, maybe_interp_entry.? },
|
||||||
);
|
);
|
||||||
|
try patchLoadedElf(interp_base);
|
||||||
interp.close();
|
interp.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
const auxv = std.os.linux.elf_aux_maybe.?;
|
const auxv = std.os.linux.elf_aux_maybe.?;
|
||||||
while (auxv[i].a_type != elf.AT_NULL) : (i += 1) {
|
while (auxv[i].a_type != elf.AT_NULL) : (i += 1) {
|
||||||
// TODO: look at other auxv types and check if we need to change them.
|
|
||||||
auxv[i].a_un.a_val = switch (auxv[i].a_type) {
|
auxv[i].a_un.a_val = switch (auxv[i].a_type) {
|
||||||
elf.AT_PHDR => base + ehdr.phoff,
|
elf.AT_PHDR => base + ehdr.phoff,
|
||||||
elf.AT_PHENT => ehdr.phentsize,
|
elf.AT_PHENT => ehdr.phentsize,
|
||||||
@@ -116,6 +117,21 @@ pub fn main() !void {
|
|||||||
elf.AT_BASE => maybe_interp_base orelse auxv[i].a_un.a_val,
|
elf.AT_BASE => maybe_interp_base orelse auxv[i].a_un.a_val,
|
||||||
elf.AT_ENTRY => entry,
|
elf.AT_ENTRY => entry,
|
||||||
elf.AT_EXECFN => @intFromPtr(std.os.argv[arg_index]),
|
elf.AT_EXECFN => @intFromPtr(std.os.argv[arg_index]),
|
||||||
|
elf.AT_SYSINFO_EHDR => blk: {
|
||||||
|
log.info("Found vDSO at 0x{x}", .{auxv[i].a_un.a_val});
|
||||||
|
try patchLoadedElf(auxv[i].a_un.a_val);
|
||||||
|
break :blk auxv[i].a_un.a_val;
|
||||||
|
},
|
||||||
|
elf.AT_EXECFD => {
|
||||||
|
@panic("Got AT_EXECFD auxv value");
|
||||||
|
// TODO: handle AT_EXECFD, when needed
|
||||||
|
// The SysV ABI Specification says:
|
||||||
|
// > At process creation the system may pass control to an interpreter program. When
|
||||||
|
// > this happens, the system places either an entry of type AT_EXECFD or one of
|
||||||
|
// > type AT_PHDR in the auxiliary vector. The entry for type AT_EXECFD uses the
|
||||||
|
// > a_val member to contain a file descriptor open to read the application
|
||||||
|
// > program’s object file.
|
||||||
|
},
|
||||||
else => auxv[i].a_un.a_val,
|
else => auxv[i].a_un.a_val,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -210,16 +226,45 @@ fn loadStaticElf(ehdr: elf.Header, file_reader: *std.fs.File.Reader) !usize {
|
|||||||
return UnfinishedReadError.UnfinishedRead;
|
return UnfinishedReadError.UnfinishedRead;
|
||||||
|
|
||||||
const protections = elfToMmapProt(phdr.p_flags);
|
const protections = elfToMmapProt(phdr.p_flags);
|
||||||
if (protections & posix.PROT.EXEC > 0) {
|
|
||||||
log.info("Patching executable segment", .{});
|
|
||||||
try Patcher.patchRegion(ptr);
|
|
||||||
}
|
|
||||||
try posix.mprotect(ptr, protections);
|
try posix.mprotect(ptr, protections);
|
||||||
}
|
}
|
||||||
log.debug("loadElf returning base: 0x{x}", .{@intFromPtr(base.ptr)});
|
log.debug("loadElf returning base: 0x{x}", .{@intFromPtr(base.ptr)});
|
||||||
return @intFromPtr(base.ptr);
|
return @intFromPtr(base.ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn patchLoadedElf(base: usize) !void {
|
||||||
|
const ehdr = @as(*const elf.Ehdr, @ptrFromInt(base));
|
||||||
|
if (!mem.eql(u8, ehdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
|
||||||
|
|
||||||
|
const phoff = ehdr.e_phoff;
|
||||||
|
const phnum = ehdr.e_phnum;
|
||||||
|
const phentsize = ehdr.e_phentsize;
|
||||||
|
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < phnum) : (i += 1) {
|
||||||
|
const phdr_ptr = base + phoff + (i * phentsize);
|
||||||
|
const phdr = @as(*const elf.Phdr, @ptrFromInt(phdr_ptr));
|
||||||
|
|
||||||
|
if (phdr.p_type != elf.PT_LOAD) continue;
|
||||||
|
if ((phdr.p_flags & elf.PF_X) == 0) continue;
|
||||||
|
|
||||||
|
// Determine VMA
|
||||||
|
// For ET_EXEC, p_vaddr is absolute.
|
||||||
|
// For ET_DYN, p_vaddr is offset from base.
|
||||||
|
const vaddr = if (ehdr.e_type == elf.ET.DYN) base + phdr.p_vaddr else phdr.p_vaddr;
|
||||||
|
const memsz = phdr.p_memsz;
|
||||||
|
|
||||||
|
const page_start = mem.alignBackward(usize, vaddr, page_size);
|
||||||
|
const page_end = mem.alignForward(usize, vaddr + memsz, page_size);
|
||||||
|
const size = page_end - page_start;
|
||||||
|
|
||||||
|
const region = @as([*]align(page_size) u8, @ptrFromInt(page_start))[0..size];
|
||||||
|
|
||||||
|
try Patcher.patchRegion(region);
|
||||||
|
try posix.mprotect(region, elfToMmapProt(phdr.p_flags));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Converts ELF program header protection flags to mmap protection flags.
|
/// Converts ELF program header protection flags to mmap protection flags.
|
||||||
fn elfToMmapProt(elf_prot: u64) u32 {
|
fn elfToMmapProt(elf_prot: u64) u32 {
|
||||||
var result: u32 = posix.PROT.NONE;
|
var result: u32 = posix.PROT.NONE;
|
||||||
@@ -288,10 +333,9 @@ test "nolibc_nopie_exit" {
|
|||||||
test "nolibc_pie_exit" {
|
test "nolibc_pie_exit" {
|
||||||
try testHelper(&.{ flicker_path, getTestExePath("nolibc_pie_exit") }, "");
|
try testHelper(&.{ flicker_path, getTestExePath("nolibc_pie_exit") }, "");
|
||||||
}
|
}
|
||||||
// BUG: This one is flaky
|
test "libc_pie_exit" {
|
||||||
// test "libc_pie_exit" {
|
try testHelper(&.{ flicker_path, getTestExePath("libc_pie_exit") }, "");
|
||||||
// try testHelper(&.{ flicker_path, getTestExePath("libc_pie_exit") }, "");
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
test "nolibc_nopie_helloWorld" {
|
test "nolibc_nopie_helloWorld" {
|
||||||
try testHelper(&.{ flicker_path, getTestExePath("nolibc_nopie_helloWorld") }, "Hello World!\n");
|
try testHelper(&.{ flicker_path, getTestExePath("nolibc_nopie_helloWorld") }, "Hello World!\n");
|
||||||
@@ -299,10 +343,9 @@ test "nolibc_nopie_helloWorld" {
|
|||||||
test "nolibc_pie_helloWorld" {
|
test "nolibc_pie_helloWorld" {
|
||||||
try testHelper(&.{ flicker_path, getTestExePath("nolibc_pie_helloWorld") }, "Hello World!\n");
|
try testHelper(&.{ flicker_path, getTestExePath("nolibc_pie_helloWorld") }, "Hello World!\n");
|
||||||
}
|
}
|
||||||
// BUG: This one is flaky
|
test "libc_pie_helloWorld" {
|
||||||
// test "libc_pie_helloWorld" {
|
try testHelper(&.{ flicker_path, getTestExePath("libc_pie_helloWorld") }, "Hello World!\n");
|
||||||
// try testHelper(&.{ flicker_path, getTestExePath("libc_pie_helloWorld") }, "Hello World!\n");
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
test "nolibc_nopie_printArgs" {
|
test "nolibc_nopie_printArgs" {
|
||||||
try testPrintArgs("nolibc_nopie_printArgs");
|
try testPrintArgs("nolibc_nopie_printArgs");
|
||||||
@@ -310,10 +353,9 @@ test "nolibc_nopie_printArgs" {
|
|||||||
test "nolibc_pie_printArgs" {
|
test "nolibc_pie_printArgs" {
|
||||||
try testPrintArgs("nolibc_pie_printArgs");
|
try testPrintArgs("nolibc_pie_printArgs");
|
||||||
}
|
}
|
||||||
// BUG: This one is flaky
|
test "libc_pie_printArgs" {
|
||||||
// test "libc_pie_printArgs" {
|
try testPrintArgs("libc_pie_printArgs");
|
||||||
// try testPrintArgs("libc_pie_printArgs");
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
test "nolibc_nopie_readlink" {
|
test "nolibc_nopie_readlink" {
|
||||||
try testReadlink("nolibc_nopie_readlink");
|
try testReadlink("nolibc_nopie_readlink");
|
||||||
@@ -321,10 +363,9 @@ test "nolibc_nopie_readlink" {
|
|||||||
test "nolibc_pie_readlink" {
|
test "nolibc_pie_readlink" {
|
||||||
try testReadlink("nolibc_pie_readlink");
|
try testReadlink("nolibc_pie_readlink");
|
||||||
}
|
}
|
||||||
// BUG: This one just outputs the path to the flicker executable and is likely also flaky
|
test "libc_pie_readlink" {
|
||||||
// test "libc_pie_readlink" {
|
try testReadlink("libc_pie_readlink");
|
||||||
// try testReadlink("libc_pie_readlink");
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
test "nolibc_nopie_clone_raw" {
|
test "nolibc_nopie_clone_raw" {
|
||||||
try testHelper(
|
try testHelper(
|
||||||
@@ -352,10 +393,6 @@ test "nolibc_pie_clone_no_new_stack" {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "echo" {
|
|
||||||
try testHelper(&.{ "echo", "Hello", "There" }, "Hello There\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
test "nolibc_nopie_fork" {
|
test "nolibc_nopie_fork" {
|
||||||
try testHelper(
|
try testHelper(
|
||||||
&.{ flicker_path, getTestExePath("nolibc_nopie_fork") },
|
&.{ flicker_path, getTestExePath("nolibc_nopie_fork") },
|
||||||
@@ -368,13 +405,12 @@ test "nolibc_pie_fork" {
|
|||||||
"Child: I'm alive!\nParent: Child died.\n",
|
"Child: I'm alive!\nParent: Child died.\n",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// BUG: This one is flaky
|
test "libc_pie_fork" {
|
||||||
// test "libc_pie_fork" {
|
try testHelper(
|
||||||
// try testHelper(
|
&.{ flicker_path, getTestExePath("libc_pie_fork") },
|
||||||
// &.{ flicker_path, getTestExePath("libc_pie_fork") },
|
"Child: I'm alive!\nParent: Child died.\n",
|
||||||
// "Child: I'm alive!\nParent: Child died.\n",
|
);
|
||||||
// );
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
test "nolibc_nopie_signal_handler" {
|
test "nolibc_nopie_signal_handler" {
|
||||||
try testHelper(
|
try testHelper(
|
||||||
@@ -389,6 +425,29 @@ test "nolibc_pie_signal_handler" {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "nolibc_nopie_vdso_clock" {
|
||||||
|
try testHelper(
|
||||||
|
&.{ flicker_path, getTestExePath("nolibc_nopie_vdso_clock") },
|
||||||
|
"Time gotten\n",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
test "nolibc_pie_vdso_clock" {
|
||||||
|
try testHelper(
|
||||||
|
&.{ flicker_path, getTestExePath("nolibc_pie_vdso_clock") },
|
||||||
|
"Time gotten\n",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
test "libc_pie_vdso_clock" {
|
||||||
|
try testHelper(
|
||||||
|
&.{ flicker_path, getTestExePath("libc_pie_vdso_clock") },
|
||||||
|
"Time gotten\n",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "echo" {
|
||||||
|
try testHelper(&.{ "echo", "Hello", "There" }, "Hello There\n");
|
||||||
|
}
|
||||||
|
|
||||||
fn testPrintArgs(comptime name: []const u8) !void {
|
fn testPrintArgs(comptime name: []const u8) !void {
|
||||||
const exe_path = getTestExePath(name);
|
const exe_path = getTestExePath(name);
|
||||||
const loader_argv: []const []const u8 = &.{ flicker_path, exe_path, "foo", "bar", "baz hi" };
|
const loader_argv: []const []const u8 = &.{ flicker_path, exe_path, "foo", "bar", "baz hi" };
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const linux = std.os.linux;
|
const linux = std.os.linux;
|
||||||
|
const posix = std.posix;
|
||||||
const Patcher = @import("Patcher.zig");
|
const Patcher = @import("Patcher.zig");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
|
const page_size = std.heap.pageSize();
|
||||||
|
|
||||||
|
const log = std.log.scoped(.syscalls);
|
||||||
|
|
||||||
/// Represents the stack layout pushed by `syscallEntry` before calling the handler.
|
/// Represents the stack layout pushed by `syscallEntry` before calling the handler.
|
||||||
pub const SavedContext = extern struct {
|
pub const SavedContext = extern struct {
|
||||||
padding: u64, // Result of `sub $8, %rsp` for alignment
|
padding: u64, // Result of `sub $8, %rsp` for alignment
|
||||||
@@ -74,38 +79,93 @@ export fn syscall_handler(ctx: *SavedContext) callconv(.c) void {
|
|||||||
asm volatile (
|
asm volatile (
|
||||||
\\ mov %[rsp], %%rsp
|
\\ mov %[rsp], %%rsp
|
||||||
\\ syscall
|
\\ syscall
|
||||||
\\ ud2
|
|
||||||
:
|
:
|
||||||
: [rsp] "r" (rsp_orig),
|
: [rsp] "r" (rsp_orig),
|
||||||
[number] "{rax}" (ctx.rax),
|
[number] "{rax}" (ctx.rax),
|
||||||
: .{ .memory = true }
|
: .{ .memory = true });
|
||||||
);
|
|
||||||
unreachable;
|
unreachable;
|
||||||
},
|
},
|
||||||
.execve, .execveat => |s| {
|
.mmap => {
|
||||||
// TODO: option to persist across new processes
|
// mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
|
||||||
std.debug.print("syscall {} called\n", .{s});
|
|
||||||
|
const prot: u32 = @intCast(ctx.rdx);
|
||||||
|
// Execute the syscall first to get the address (rax)
|
||||||
|
ctx.rax = executeSyscall(ctx);
|
||||||
|
const addr = ctx.rax;
|
||||||
|
var len = ctx.rsi;
|
||||||
|
const flags: linux.MAP = @bitCast(@as(u32, @intCast(ctx.r10)));
|
||||||
|
const fd: linux.fd_t = @bitCast(@as(u32, @truncate(ctx.r8)));
|
||||||
|
const offset = ctx.r9;
|
||||||
|
|
||||||
|
const is_error = @as(i64, @bitCast(ctx.rax)) < 0;
|
||||||
|
if (is_error) return;
|
||||||
|
if ((prot & posix.PROT.EXEC) == 0) return;
|
||||||
|
|
||||||
|
// If file-backed (not anonymous), clamp len to file size to avoid SIGBUS
|
||||||
|
if (!flags.ANONYMOUS) {
|
||||||
|
var stat: linux.Stat = undefined;
|
||||||
|
if (0 == linux.fstat(fd, &stat) and linux.S.ISREG(stat.mode)) {
|
||||||
|
const file_size: u64 = @intCast(stat.size);
|
||||||
|
len = if (offset >= file_size) 0 else @min(len, file_size - offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len <= 0) return;
|
||||||
|
// mmap addresses are always page aligned
|
||||||
|
const ptr = @as([*]align(page_size) u8, @ptrFromInt(addr));
|
||||||
|
// Check if we can patch it
|
||||||
|
Patcher.patchRegion(ptr[0..len]) catch |err| {
|
||||||
|
std.log.warn("JIT Patching failed: {}", .{err});
|
||||||
|
};
|
||||||
|
|
||||||
|
// patchRegion leaves it as RW. We need to restore to requested prot.
|
||||||
|
_ = linux.syscall3(.mprotect, addr, len, prot);
|
||||||
|
return;
|
||||||
},
|
},
|
||||||
.prctl, .arch_prctl, .set_tid_address => |s| {
|
.mprotect => {
|
||||||
|
// mprotect(void *addr, size_t len, int prot)
|
||||||
|
// TODO: cleanup trampolines, when removing X
|
||||||
|
const prot: u32 = @intCast(ctx.rdx);
|
||||||
|
if ((prot & posix.PROT.EXEC) != 0) {
|
||||||
|
const addr = ctx.rdi;
|
||||||
|
const len = ctx.rsi;
|
||||||
|
// mprotect requires addr to be page aligned.
|
||||||
|
if (len > 0 and std.mem.isAligned(addr, page_size)) {
|
||||||
|
const ptr = @as([*]align(page_size) u8, @ptrFromInt(addr));
|
||||||
|
Patcher.patchRegion(ptr[0..len]) catch |err| {
|
||||||
|
std.log.warn("mprotect Patching failed: {}", .{err});
|
||||||
|
};
|
||||||
|
// patchRegion leaves it R|W.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.rax = executeSyscall(ctx);
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
.execve, .execveat => {
|
||||||
|
// TODO: option to persist across new processes
|
||||||
|
ctx.rax = executeSyscall(ctx);
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
.prctl, .arch_prctl, .set_tid_address => {
|
||||||
// TODO: what do we need to handle from these?
|
// TODO: what do we need to handle from these?
|
||||||
// process name
|
// process name
|
||||||
// fs base(gs?)
|
// fs base(gs?)
|
||||||
// thread id pointers
|
// thread id pointers
|
||||||
std.debug.print("syscall {} called\n", .{s});
|
ctx.rax = executeSyscall(ctx);
|
||||||
},
|
return;
|
||||||
.mmap, .mprotect => {
|
|
||||||
// TODO: JIT support
|
|
||||||
// TODO: cleanup
|
|
||||||
},
|
},
|
||||||
.munmap, .mremap => {
|
.munmap, .mremap => {
|
||||||
// TODO: cleanup
|
// TODO: cleanup
|
||||||
|
ctx.rax = executeSyscall(ctx);
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
// Write result back to the saved RAX so it is restored to the application.
|
||||||
|
ctx.rax = executeSyscall(ctx);
|
||||||
|
return;
|
||||||
},
|
},
|
||||||
|
|
||||||
else => {},
|
|
||||||
}
|
}
|
||||||
|
unreachable;
|
||||||
// Write result back to the saved RAX so it is restored to the application.
|
|
||||||
ctx.rax = executeSyscall(ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fn executeSyscall(ctx: *SavedContext) u64 {
|
inline fn executeSyscall(ctx: *SavedContext) u64 {
|
||||||
|
|||||||
@@ -30,9 +30,6 @@ pub fn main() !void {
|
|||||||
\\ mov $60, %%rax # SYS_exit
|
\\ mov $60, %%rax # SYS_exit
|
||||||
\\ syscall
|
\\ syscall
|
||||||
\\
|
\\
|
||||||
\\ # Should not be reached
|
|
||||||
\\ ud2
|
|
||||||
\\
|
|
||||||
\\ 1:
|
\\ 1:
|
||||||
\\ # Parent Path continues
|
\\ # Parent Path continues
|
||||||
: [ret] "={rax}" (-> usize),
|
: [ret] "={rax}" (-> usize),
|
||||||
|
|||||||
@@ -34,9 +34,6 @@ pub fn main() !void {
|
|||||||
\\ mov $60, %%rax # SYS_exit
|
\\ mov $60, %%rax # SYS_exit
|
||||||
\\ syscall
|
\\ syscall
|
||||||
\\
|
\\
|
||||||
\\ # Should not be reached
|
|
||||||
\\ ud2
|
|
||||||
\\
|
|
||||||
\\ 1:
|
\\ 1:
|
||||||
\\ # Parent Path continues
|
\\ # Parent Path continues
|
||||||
: [ret] "={rax}" (-> usize),
|
: [ret] "={rax}" (-> usize),
|
||||||
|
|||||||
8
src/test/vdso_clock.zig
Normal file
8
src/test/vdso_clock.zig
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
_ = try std.posix.clock_gettime(std.posix.CLOCK.MONOTONIC);
|
||||||
|
|
||||||
|
const msg = "Time gotten\n";
|
||||||
|
_ = try std.posix.write(1, msg);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user