Compare commits
4 Commits
1557b82c1d
...
3d7532c906
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d7532c906 | |||
| 5d146140b9 | |||
| 7161b6d1a2 | |||
| 7eb5601eb6 |
@@ -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
|
||||
the stack, so `ucontext` isn't on top anymore.
|
||||
- [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
|
||||
- [ ] `SIGILL` patching fallback
|
||||
- [ ] `vdso` handling
|
||||
- [x] `vdso` handling
|
||||
- [ ] check why the libc tests are flaky
|
||||
|
||||
## Minor things
|
||||
|
||||
|
||||
@@ -6,10 +6,14 @@ const log = std.log.scoped(.disassembler);
|
||||
const assert = std.debug.assert;
|
||||
|
||||
pub const InstructionIterator = struct {
|
||||
/// Maximum number of warnings to print per iterator before suppressing.
|
||||
pub var max_warnings: u64 = 3;
|
||||
|
||||
decoder: zydis.ZydisDecoder,
|
||||
bytes: []const u8,
|
||||
instruction: zydis.ZydisDecodedInstruction,
|
||||
operands: [zydis.ZYDIS_MAX_OPERAND_COUNT]zydis.ZydisDecodedOperand,
|
||||
warnings: usize = 0,
|
||||
|
||||
pub fn init(bytes: []const u8) InstructionIterator {
|
||||
var decoder: zydis.ZydisDecoder = undefined;
|
||||
@@ -38,27 +42,33 @@ pub const InstructionIterator = struct {
|
||||
var address: u64 = @intFromPtr(iterator.bytes.ptr);
|
||||
|
||||
while (!zydis.ZYAN_SUCCESS(status)) {
|
||||
// TODO: handle common padding bytes
|
||||
switch (status) {
|
||||
zydis.ZYDIS_STATUS_NO_MORE_DATA => {
|
||||
log.info("next: Got status: NO_MORE_DATA. Iterator completed.", .{});
|
||||
if (status == zydis.ZYDIS_STATUS_NO_MORE_DATA) {
|
||||
log.debug("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
|
||||
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(
|
||||
"next: instruction length: {}, address: 0x{x}, bytes: 0x{x}",
|
||||
.{
|
||||
iterator.instruction.length,
|
||||
address,
|
||||
iterator.bytes[0..iterator.instruction.length],
|
||||
},
|
||||
"next: skipping byte at address: 0x{x}, byte: 0x{x}",
|
||||
.{ address, iterator.bytes[0] },
|
||||
);
|
||||
|
||||
iterator.bytes = iterator.bytes[1..];
|
||||
|
||||
74
src/main.zig
74
src/main.zig
@@ -71,6 +71,7 @@ pub fn main() !void {
|
||||
const base = try loadStaticElf(ehdr, &file_reader);
|
||||
const entry = ehdr.entry + if (ehdr.type == .DYN) base else 0;
|
||||
log.info("Executable loaded: base=0x{x}, entry=0x{x}", .{ base, entry });
|
||||
try patchLoadedElf(base);
|
||||
|
||||
// Check for dynamic linker
|
||||
var maybe_interp_base: ?usize = null;
|
||||
@@ -102,13 +103,13 @@ pub fn main() !void {
|
||||
"Interpreter loaded: base=0x{x}, entry=0x{x}",
|
||||
.{ interp_base, maybe_interp_entry.? },
|
||||
);
|
||||
try patchLoadedElf(interp_base);
|
||||
interp.close();
|
||||
}
|
||||
|
||||
var i: usize = 0;
|
||||
const auxv = std.os.linux.elf_aux_maybe.?;
|
||||
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) {
|
||||
elf.AT_PHDR => base + ehdr.phoff,
|
||||
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_ENTRY => entry,
|
||||
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,
|
||||
};
|
||||
}
|
||||
@@ -210,16 +226,44 @@ fn loadStaticElf(ehdr: elf.Header, file_reader: *std.fs.File.Reader) !usize {
|
||||
return UnfinishedReadError.UnfinishedRead;
|
||||
|
||||
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);
|
||||
}
|
||||
log.debug("loadElf returning base: 0x{x}", .{@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 region = @as([*]align(page_size) u8, @ptrFromInt(page_start))[0 .. page_end - page_start];
|
||||
|
||||
log.info("Patching segment: 0x{x} - 0x{x}", .{ page_start, page_end });
|
||||
try Patcher.patchRegion(region);
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts ELF program header protection flags to mmap protection flags.
|
||||
fn elfToMmapProt(elf_prot: u64) u32 {
|
||||
var result: u32 = posix.PROT.NONE;
|
||||
@@ -389,6 +433,26 @@ 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",
|
||||
);
|
||||
}
|
||||
// BUG: This one is flaky
|
||||
// test "libc_pie_vdso_clock" {
|
||||
// try testHelper(
|
||||
// &.{ flicker_path, getTestExePath("libc_pie_vdso_clock") },
|
||||
// "Time gotten\n",
|
||||
// );
|
||||
// }
|
||||
|
||||
fn testPrintArgs(comptime name: []const u8) !void {
|
||||
const exe_path = getTestExePath(name);
|
||||
const loader_argv: []const []const u8 = &.{ flicker_path, exe_path, "foo", "bar", "baz hi" };
|
||||
|
||||
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