On this page
Pointer Patching
Some kernel interfaces pass structs that contain pointers to other buffers. For example, an access check syscall might take a struct with a pointer to a security descriptor, which itself contains pointers to SID buffers. You cannot embed a host-side pointer in a Lua string — the guest has its own address space.
Provium's pointer patching solves this. You provide the struct data with placeholder zeros where the pointers go, describe where the pointers are and what they should point to, and the guest agent allocates memory, patches the addresses, and executes the syscall.
syscall_ptr
vm:syscall_ptr(nr, bufs_table, ptrs_table, arg1, ...) executes a syscall with buffer arguments and embedded pointer patching:
local ret, buf_out, ptr_out = vm:syscall_ptr(nr,
bufs_table, -- buffer arguments (same as syscall_bufs)
ptrs_table, -- pointer specs (new)
arg1, arg2, ...
)
Buffer arguments
The bufs_table works exactly like vm:syscall_bufs() — it maps argument indices to buffer data:
{[0] = struct_data, [2] = extra_data}
Pointer specs
The ptrs_table is a list of tables, each describing one embedded pointer within a buffer argument:
{
{buf_idx = 0, ptr_offset = 8, data_len = 128, output = true},
{buf_idx = 0, ptr_offset = 16, data_len = 64, data = input_bytes},
}
| Field | Type | Description |
|---|---|---|
buf_idx |
number | Which buffer argument contains this pointer (matches a key in bufs_table) |
ptr_offset |
number | Byte offset of the pointer field within the buffer |
data_len |
number | Size of the sub-buffer to allocate in the guest |
output |
boolean | If true, the sub-buffer contents are returned after the syscall |
data |
string | Input data to populate the sub-buffer (for input pointers) |
The agent:
- Allocates
data_lenbytes of guest memory for each spec - Copies
datainto the allocation (if provided) - Writes the allocation's guest address into the buffer at
ptr_offset - Executes the syscall
- Returns output sub-buffer contents (for specs with
output = true)
Return values
syscall_ptr returns:
| Return | Type | Description |
|---|---|---|
result |
number | Syscall return value |
buf1, buf2, ... |
strings | Output buffer contents (from bufs_table, in index order) |
ptr1, ptr2, ... |
strings | Output sub-buffer contents (from ptrs_table, in spec order, only for output = true) |
When to use pointer patching
Use syscall_ptr when:
- A buffer argument contains a pointer field that the kernel dereferences
- You need to pass nested data structures (struct within struct)
- The kernel writes into a sub-buffer and you need to read the result
For most simple cases, syscall_buf or syscall_bufs is sufficient. Pointer patching is only needed when the kernel follows pointers embedded within your struct.
Worked example
Consider a syscall that takes a struct containing a pointer to a security descriptor:
struct access_check_args {
uint32_t size; // offset 0
int32_t token_fd; // offset 4
void *sd_ptr; // offset 8 ← embedded pointer
uint32_t sd_len; // offset 16
uint32_t desired; // offset 20
// ... more fields
};
In Lua:
-- Build the struct with a placeholder zero at the pointer offset
local args = provium.pack("u32 i32 u64 u32 u32",
96, -- size
token_fd, -- token_fd
0, -- sd_ptr (placeholder — will be patched)
#sd, -- sd_len
desired) -- desired access
-- Call with pointer patching
local ret, args_out, sd_out = vm:syscall_ptr(1023,
{[0] = args}, -- buffer args
{{buf_idx = 0, ptr_offset = 8, data_len = #sd, data = sd}}, -- pointer specs
0) -- remaining int args
assert(ret >= 0, "access check failed: " .. ret)
The agent allocates guest memory for the security descriptor, copies sd into it, writes the guest address into args at byte offset 8, then calls syscall(1023, args_ptr, 0).
ioctl_buf as an alternative
For ioctls (rather than syscalls), vm:ioctl_buf() provides the same pointer-patching capability with a simpler interface. See Ioctl for details.
The key difference: ioctl_buf patches pointers within the ioctl struct itself, while syscall_ptr patches pointers within syscall buffer arguments.