lib/sys/src/process/tracing/syscall.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const builtin = @import("builtin");
  3 const linux = std.os.linux;
  4 
  5 pub const Operation = enum { open, openat, openat2, creat, rename, renameat, renameat2 };
  6 pub const FileCall = struct {
  7     operation: Operation,
  8     path: u64,
  9     path2: u64 = 0,
 10     directory: i32 = -100,
 11     directory2: i32 = -100,
 12     flags: ?u64 = null,
 13 };
 14 pub const Phase = union(enum) { entry: ?FileCall, exit: i64 };
 15 const Info = extern struct {
 16     op: u8,
 17     pad: [3]u8,
 18     arch: u32,
 19     ip: u64,
 20     sp: u64,
 21     data: extern union {
 22         entry: extern struct { nr: u64, args: [6]u64 },
 23         exit: extern struct { value: i64, failed: u8 },
 24     },
 25 };
 26 
 27 pub fn inspect(pid: i32) !Phase {
 28     var info: Info = std.mem.zeroes(Info);
 29     const result = linux.ptrace(
 30         linux.PTRACE.GET_SYSCALL_INFO,
 31         pid,
 32         @sizeOf(Info),
 33         @intFromPtr(&info),
 34         0,
 35     );
 36     if (linux.errno(result) != .SUCCESS) return error.SyscallObservationUnavailable;
 37     const arch: u32 = switch (builtin.cpu.arch) {
 38         .x86_64 => 0xc000003e,
 39         .aarch64 => 0xc00000b7,
 40         else => return error.UnsupportedSyscallArchitecture,
 41     };
 42     if (info.arch != arch) return error.UnsupportedTraceeArchitecture;
 43     if (info.op == 1 and result < 80) return error.IncompleteSyscallObservation;
 44     if (info.op == 2 and result < 33) return error.IncompleteSyscallObservation;
 45     if (builtin.cpu.arch == .x86_64 and info.op == 1 and
 46         (info.data.entry.nr & 0x40000000) != 0) return error.UnsupportedTraceeArchitecture;
 47     return switch (info.op) {
 48         1 => .{ .entry = decode(info.data.entry.nr, info.data.entry.args) },
 49         2 => .{ .exit = info.data.exit.value },
 50         else => error.IncompleteSyscallObservation,
 51     };
 52 }
 53 
 54 fn number(comptime name: []const u8, nr: u64) bool {
 55     if (comptime @hasField(linux.SYS, name)) return nr == @backingInt(@field(linux.SYS, name));
 56     return false;
 57 }
 58 
 59 fn directory(value: u64) i32 {
 60     return @bitCast(@as(u32, @truncate(value)));
 61 }
 62 
 63 fn decode(nr: u64, args: [6]u64) ?FileCall {
 64     if (number("open", nr)) return .{ .operation = .open, .path = args[0], .flags = args[1] };
 65     if (number("creat", nr)) return .{ .operation = .creat, .path = args[0], .flags = 0x241 };
 66     if (number("openat", nr) or number("openat2", nr)) return .{
 67         .operation = if (number("openat", nr)) .openat else .openat2,
 68         .directory = directory(args[0]),
 69         .path = args[1],
 70         .flags = if (number("openat", nr)) args[2] else null,
 71     };
 72     if (number("rename", nr)) return .{ .operation = .rename, .path = args[0], .path2 = args[1] };
 73     if (number("renameat", nr) or number("renameat2", nr)) return .{
 74         .operation = if (number("renameat", nr)) .renameat else .renameat2,
 75         .directory = directory(args[0]),
 76         .path = args[1],
 77         .directory2 = directory(args[2]),
 78         .path2 = args[3],
 79         .flags = if (number("renameat2", nr)) args[4] else 0,
 80     };
 81     return null;
 82 }
 83 
 84 /// Turns a path argument at a syscall stop into bytes a recorder can write down
 85 /// by copying the path out of a stopped tracee's memory at `address` into
 86 /// `buffer`, and then returning the bytes before the terminator. A zero or
 87 /// out-of-range address, a failed read, and a copy that holds no terminator
 88 /// each return null, and the last marks a path longer than the buffer.
 89 pub fn path(pid: i32, address: u64, buffer: []u8) ?[]const u8 {
 90     if (address == 0 or address > std.math.maxInt(usize)) return null;
 91     const local = [_]std.posix.iovec{.{ .base = buffer.ptr, .len = buffer.len }};
 92     const remote = [_]std.posix.iovec_const{.{
 93         .base = @ptrFromInt(@as(usize, @intCast(address))),
 94         .len = buffer.len,
 95     }};
 96     const result = linux.process_vm_readv(pid, &local, &remote, 0);
 97     if (linux.errno(result) != .SUCCESS) return null;
 98     const end = std.mem.indexOfScalar(u8, buffer[0..result], 0) orelse return null;
 99     return buffer[0..end];
100 }
101 
102 test "file syscall decoding retains directory descriptors and write intent" {
103     const value = decode(@backingInt(linux.SYS.openat), .{
104         @as(u64, @bitCast(@as(i64, -100))), 1234, 0x241, 0, 0, 0,
105     }).?;
106     try std.testing.expectEqual(@as(i32, -100), value.directory);
107     try std.testing.expectEqual(@as(?u64, 0x241), value.flags);
108     try std.testing.expectEqual(@as(u64, 1234), value.path);
109     try std.testing.expect(decode(@backingInt(linux.SYS.getpid), @splat(0)) == null);
110 }