These docs are under active development and cover the v0.20 Kobicha security model.
On this page
Concept 3 min read

Binary Packing

Kernel interfaces work with binary structs — fixed layouts of integers at specific byte offsets. Provium provides pack and unpack functions for building and parsing these structs directly in Lua.

Packing

provium.pack(fmt, ...) converts values into a binary string according to a format specifier:

-- A struct with two u32 fields and one u64 field
local data = provium.pack("u32 u32 u64", 1, 42, 0)
-- data is a 16-byte string: [01 00 00 00] [2A 00 00 00] [00 00 00 00 00 00 00 00]

The format string is a space-separated list of type specifiers. Each specifier consumes one value argument.

Format types

Specifier Size Range
u8 1 byte 0 to 255
u16 2 bytes 0 to 65535
i16 2 bytes -32768 to 32767
u32 4 bytes 0 to 4294967295
i32 4 bytes -2147483648 to 2147483647
u64 8 bytes 0 to 2^64-1
i64 8 bytes -2^63 to 2^63-1

All values are encoded in little-endian byte order.

Unpacking

provium.unpack(fmt, data) extracts values from a binary string:

local a, b, c = provium.unpack("u32 u32 u64", data)

The format string works the same as pack. Each specifier produces one return value. The data must be long enough to satisfy all specifiers — if it is too short, an error is raised.

Concatenation

Packed strings are regular Lua strings. You can concatenate them to build complex structures piece by piece:

-- Build a SID: revision, sub-authority count, authority, sub-authorities
local sid = provium.pack("u8 u8 u8 u8 u8 u8 u8 u8",
    1, count, 0, 0, 0, 0, 0, authority)
for _, sa in ipairs(sub_authorities) do
    sid = sid .. provium.pack("u32", sa)
end

This is how the KACS test helper library builds SIDs, ACEs, ACLs, and security descriptors.

Practical examples

Building a SID

A Windows-compatible Security Identifier (SID):

function build_sid(authority, sub_authorities)
    local count = #sub_authorities
    local sid = provium.pack("u8 u8 u8 u8 u8 u8 u8 u8",
        1, count, 0, 0, 0, 0, 0, authority)
    for _, sa in ipairs(sub_authorities) do
        sid = sid .. provium.pack("u32", sa)
    end
    return sid
end

local SID_SYSTEM = build_sid(5, {18})          -- S-1-5-18
local SID_EVERYONE = build_sid(1, {0})         -- S-1-1-0
local SID_ADMINS = build_sid(5, {32, 544})     -- S-1-5-32-544

Building an ACE

An Access Control Entry with a header and a SID:

function build_allow_ace(mask, sid)
    local ace_size = 4 + 4 + #sid
    -- Pad to 4-byte alignment
    while ace_size % 4 ~= 0 do ace_size = ace_size + 1 end
    local ace = provium.pack("u8 u8 u16 u32",
        0x00,       -- type: ACCESS_ALLOWED
        0x00,       -- flags
        ace_size,   -- size
        mask)       -- access mask
        .. sid
    while #ace < ace_size do ace = ace .. "\0" end
    return ace
end

Building an ACL

An Access Control List containing ACEs:

function build_acl(aces)
    local ace_data = ""
    for _, ace in ipairs(aces) do
        ace_data = ace_data .. ace
    end
    local acl_size = 8 + #ace_data
    return provium.pack("u8 u8 u16 u16 u16",
        2,           -- revision
        0,           -- pad
        acl_size,    -- size
        #aces,       -- ace count
        0)           -- pad
        .. ace_data
end

Parsing a kernel response

After a syscall or ioctl returns output data:

local ret, out = vm:ioctl(fd, KACS_IOC_QUERY, args)
assert(ret == 0)

local class, buf_len = provium.unpack("u32 u32", out)

Precision note

Lua uses double-precision floating point for all numbers. This gives exact integer representation up to 2^53. Values above this — such as u64 privilege bitmasks with high bits set — may lose precision. The KACS test helpers work around this by combining privileges using addition rather than bitwise OR, and by avoiding the highest bits in privilege constants where possible.