#ffi #c #randomize-layout #interoperation

macro randstruct

This crate implements a subset of the features of the GCC randomize_layout plugin

2 stable releases

1.0.2 Oct 27, 2024

#468 in Procedural macros

GPL-2.0 license

20KB
359 lines

Randstruct

This crate implements a subset of the features of the GCC randomize_layout plugin.


[dependencies]
randstruct = "1.0"

Background

The randomize_layout plugin is used by software to harden structs that contain sensitive/privileged data. One notable use case is a hardened Linux Kernel which uses it to remove predictable offsets of sensitive data in it's structures.

Consider the following as a trivial example:

struct TestStruct {
    const char    *elem0;
    const char    *elem1;
    unsigned int   elem3;
    unsigned short elem4;
} __attribute__((randomize_layout));

Which may produce the following:

struct TestStruct {
    const char    *elem1;
    unsigned short elem4;
    const char    *elem0;
    unsigned int   elem3;
} __attribute__((randomize_layout));

As is evident in the above example, the offsets of all the members have been changed.

The Problem

Rust uses #[repr(C)] to force the underlying C layout of a particular structure. In the above example, a Rust equivalent structure would be:

#[repr(C)]
struct TestStruct {
    elem0: *const core::ffi::c_char,
    elem1: *const core::ffi::c_char,
    elem2: core::ffi::c_uint,
    elem3: core::ffi::c_ushort,
}

These Rust structures are typically either programmatically generated through a tool like bindgen or manually created by hand.

The generation of these structures is source-based; however, the GCC randomize_layout attribute will randomize at build time. Meaning, the source-level representation of the structure is not an accurate depiction of the structure once compiled.

This presents an issue in FFI when you want to process a C structure or pass a Rust structure through C code.

The Solution

To replicate the exact structure member shuffling that randomize_layout achieves, this crate reimplements the logic to operate on Rust proc-macro TokenStreams instead of the GCC AST values.

How To Use

Continuing the example above, we have a C structure that uses the randomize_layout plugin.

struct TestStruct {
    const char    *elem0;
    const char    *elem1;
    unsigned int   elem3;
    unsigned short elem4;
} __attribute__((randomize_layout));

The equivalent Rust structure (with this crate) looks like the following:

#[repr(C)]
#[randomize_layout]
struct TestStruct {
    elem0: *const core::ffi::c_char,
    elem1: *const core::ffi::c_char,
    elem2: core::ffi::c_uint,
    elem3: core::ffi::c_ushort,
}

That is it. That is all that is required. The C and Rust can be compiled as separate compilation units, and they will be able to interoperate safely.

Bulding With This Crate

You need to tell the crate where the header file is that contains the randstruct seed through the SEED_HEADER_FILE environment variable. For example, the below will suffice:

SEED_HEADER_FILE=`pwd`/seed.h cargo run

Where seed.h should contain a string called randstruct_seed, this header should be the same header as the one used by the GCC plugin. An exemplar header file is shown below:

const char *randstruct_seed = "123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234";

No runtime deps

~0–2MB
~38K SLoC