Getting Started

Selfpatch-SLR

Selfpatch-SLR (SPSLR) implements runtime structure layout randomization for C programs on x86_64 Linux.

Current status: SPSLR is a research prototype for experimentation and evaluation, but with the ultimate goal of evolving into an upstreamable feature.

Documentation

The SPSLR documentation is organized into the following sections:

Getting Started

Introduction to SPSLR, core concepts, build integration, runtime API usage, and practical integration guidance.

Architecture

Internal SPSLR design, metadata format, pinpoint implementation details, and selfpatch internals.

Bootpatch-SLR

Information on the Linux kernel implementation of Selfpatch-SLR.

Discussion

Collection of feedback, mailing list archive links, frequently asked questions and outstanding issues.

Motivation

Structure layout randomization is intended to make memory corruption exploitation more difficult by changing the in-memory placement of structure members. Many exploits rely on corrupting a specific field inside a target object, for example function pointers, reference counters, credentials, length fields, or linked-list pointers. Those attacks often depend on the attacker knowing the relative offsets of members within the structure. By randomizing structure layouts, those offsets can be obfuscated. A fixed-offset overwrite that successfully corrupts a useful target member in one layout may instead corrupt an unrelated field in another layout, causing the exploit to fail or become unreliable.

Doing this at compile-time produces one random but fixed set of layouts per compiled binary. Every deployed instance of that binary shares the same layouts. Thus, an attacker that learns the structure layouts of one deployed instance can produce static exploits based on overwrite primitives that remain valid across all identical instances.

SPSLR investigates a different point in the design space. Instead of treating the randomized layouts as a fixed compile-time property, it moves the final layout decision to runtime. To make that possible, the compiler must expose the places where generated code or static data relies on structure offsets, and the runtime must patch those places after choosing the randomized layout.

Concepts

The following general terms are used throughout the SPSLR documentation.

Subject

A linked program image that carries SPSLR metadata and can be patched by the SPSLR runtime component. Executables and shared libraries are both subjects.

Host subject

The main executable. It owns the randomized layouts and patches itself before patching modules.

Module subject

A shared library loaded by the host. It carries its own SPSLR metadata but is patched using the layouts selected by the host.

Target

A structure type which was marked for layout randomization.

Build Integration

SPSLR requires a custom toolchain, currently based on GCC 16. Information on building it, the SPSLR components themselves, and the example host and module subjects can be found in the project's README. This section focuses on applying SPSLR to your project rather than building SPSLR itself.

SPSLR requires only minor intervention in the normal build flow. All subject source files should be compiled with the pinpoint GCC plugin so their generated object files contain the required SPSLR metadata. This applies to both host subjects and module subjects.

The selfpatch runtime itself should be compiled without pinpoint. It is then linked into host subjects together with the instrumented subject objects. Module subjects carry their own SPSLR metadata, but do not link their own copy of the selfpatch runtime; they are patched by the host after loading.

SPSLR build integration: hover over edge labels to see example commands.
C files Object files Subject binaries selfpatch.c main.c foo.c bar.c module.c baz.c selfpatch.o main.o foo.o bar.o module.o baz.o host executable module.so shared library gcc gcc + pinpoint gcc + pinpoint gcc + pinpoint gcc + pinpoint gcc + pinpoint ld ld

Every SPSLR image, including shared modules, must retain all SPSLR metadata sections and define start and stop symbols for the unit and target metadata ranges. Depending on the toolchain and image type, these symbols may be generated automatically. For example, host subjects are often able to use the linker-generated SPSLR boundary symbols directly, whereas shared modules may require a custom linker script to define them explicitly.

If the required symbols are not available by default, a custom linker script must be used to preserve SPSLR metadata sections and define the required metadata boundaries, for example as follows:

SECTIONS
{
  .spslr_units : {
    __start_spslr_units = .;
    KEEP(*(spslr_units))
    __stop_spslr_units = .;
  }

  .spslr_targets : {
    __start_spslr_targets = .;
    KEEP(*(spslr_targets))
    __stop_spslr_targets = .;
  }

  .spslr_target_layouts : { KEEP(*(spslr_target_layouts)) }
  .spslr_cu_target_refs : { KEEP(*(spslr_cu_target_refs)) }
  .spslr_ipins : { KEEP(*(spslr_ipins)) }
  .spslr_dpins : { KEEP(*(spslr_dpins)) }
  .spslr_strtab : { KEEP(*(spslr_strtab)) }
}
INSERT AFTER .data;

Source Integration

Source integration consists of two parts: marking the structure types that should participate in layout randomization and calling the selfpatch runtime. Target annotations tell pinpoint which structures and fields require metadata, while the runtime API lets the host subject initialize layouts, patch itself, and patch modules after loading.

Target Selection

While the exact annotation interface may evolve, a typical target declaration looks like this:

struct example_target {
    int flags;
    void *ptr __attribute__((spslr_field_fixed));
    unsigned long counter;
} __attribute__((spslr));

Most code should treat randomized target structures as opaque. However, some fields may need to remain at fixed offsets due to ABI requirements or other layout-sensitive interfaces. Such fields can be marked as fixed to exclude them from SPSLR randomization while allowing the remainder of the target structure to be randomized.

Examples of code that may require fixed fields include:

  • casting a structure pointer to a pointer to its first member,
  • assumptions about field ordering or contiguous placement,
  • serialization or ABI logic that depends on a stable in-memory layout.
WARNING: Structure layout randomization fundamentally conflicts with assumptions made by the C language and common low-level programming idioms. In standard C, structure layouts are fixed and observable. Structure layout randomization, at compile- or runtime, intentionally violates these assumptions. As a result, some otherwise-valid code constructs are incompatible with SLR.

Runtime API

After target structures have been annotated and compiled with pinpoint, the remaining integration work happens through the selfpatch runtime API. Host subjects use the API to select randomized layouts and patch themselves during startup. They also use it to patch SPSLR-enabled modules after loading.

Include the public API with:

#include <spslr.h>

The following excerpt shows the public interface most applications interact with. The complete API is declared in <spslr.h>.

#define SPSLR_START_UNITS_SYM   __start_spslr_units
#define SPSLR_STOP_UNITS_SYM    __stop_spslr_units
#define SPSLR_START_TARGETS_SYM __start_spslr_targets
#define SPSLR_STOP_TARGETS_SYM  __stop_spslr_targets

enum spslr_viability {
    SPSLR_VIABLE,
    SPSLR_NONVIABLE
};

enum spslr_error {
    SPSLR_OK,
    ...
}

struct spslr_status {
    enum spslr_viability viability;
    enum spslr_error error;
};

struct spslr_entry {
    const void *start_units;
    const void *stop_units;
    const void *start_targets;
    const void *stop_targets;
};

struct spslr_ctx {
    struct spslr_entry entry;
    void *workspace;
};

struct spslr_status spslr_init(void);
struct spslr_status spslr_selfpatch(void);
unsigned long spslr_workspace_size(const struct spslr_entry *entry);
struct spslr_status spslr_patch_module(const struct spslr_ctx *m);

Host startup begins with spslr_init() and spslr_selfpatch(). During initialization, SPSLR chooses randomized layouts for all discovered targets. On selfpatch, those layouts are then applied to the host subject by patching every code and data location described by the generated metadata.

A typical host subject initializing SPSLR and patching itself, at the beginning of main(), looks like this:

int main(int argc, char **argv) {
    struct spslr_status st;

    /* Randomize layouts */
    st = spslr_init();
    if (st.error != SPSLR_OK)
        error;

    /* Patch host image in-memory */
    st = spslr_selfpatch();
    if (st.error != SPSLR_OK)
        error;

    /* Normal program execution begins here */
    ...
}

Under contextual constraints, the image selfpatch step may be deferred. Dynamically created target instances may exist before spslr_selfpatch() only if they are fully dead before the patching boundary. They must not be used after patching, because stack and heap instances are not tracked by SPSLR metadata and therefore are not retrospectively transformed into the randomized layouts. Dynamically allocating target instances after patching is perfectly fine, of course.

Static target instances are different. Because they are part of the program image, SPSLR reorders their fields during selfpatch. Reads and writes performed before and after patching are therefore permitted, provided that no pointers materialized from compile-time field offsets are preserved and used across the patching boundary:

static struct target obj;

int main(void) {
    spslr_init();

    /* Ok: static target instances may be accessed before patching. */
    obj.fieldA = 42;

    /* Ok: pointer usage does not cross patching boundary. */
    int *fieldB_ptr = &obj.fieldB;
    *fieldB_ptr = 41;

    spslr_selfpatch();

    /* Bad: fieldB_ptr still points to the field's pre-patch location. */
    *fieldB_ptr = 42;

    /* Ok: pointer assignment now goes through patched code and randomized offsets. */
    fieldB_ptr = &obj.fieldB;
    *fieldB_ptr = 42;
}

Once the host has been patched, it can load and patch SPSLR-enabled modules. Module subjects do not select their own layouts. Instead, they reuse the layouts already chosen by the host so that randomized targets remain compatible across subject boundaries.

Before patching can begin, the module must have been fully loaded and relocated. In a typical userspace environment this means calling dlopen() first and only invoking SPSLR once the loader has completed its relocation work.

The host then locates the module metadata and stores the resulting boundary addresses in an spslr_entry, which describes the metadata ranges of the subject being patched. Next, it allocates a temporary workspace buffer of at least spslr_workspace_size(&entry) bytes, constructs an spslr_ctx, and invokes spslr_patch_module().

#include <dlfcn.h>
#include <stdlib.h>
#include <spslr.h>

#define XSTR(a) STR(a)
#define STR(a) #a

int patch_loaded_module(void *handle)
{
    struct spslr_ctx ctx;

    ctx.entry.start_units =
        dlsym(handle, XSTR(SPSLR_START_UNITS_SYM));
    ctx.entry.stop_units =
        dlsym(handle, XSTR(SPSLR_STOP_UNITS_SYM));
    ctx.entry.start_targets =
        dlsym(handle, XSTR(SPSLR_START_TARGETS_SYM));
    ctx.entry.stop_targets =
        dlsym(handle, XSTR(SPSLR_STOP_TARGETS_SYM));

    ctx.workspace =
        malloc(spslr_workspace_size(&ctx.entry));

    struct spslr_status st =
        spslr_patch_module(&ctx);

    free(ctx.workspace);

    return st.error != SPSLR_OK;
}

The workspace only needs to remain valid for the duration of the patching call and may be freed after spslr_patch_module() returns.

Before calling spslr_patch_module(): do not execute code from the module, do not allocate module-owned randomized target objects, and do not persist pointers to randomized module fields.

Besides an error code, the struct spslr_status also reports whether the corresponding subject remains viable.

A successful operation leaves the subject fully patched and viable. However, failures that occur after patching has already begun may leave a subject in a partially patched state. Such subjects must be considered unusable.

For host subjects, a non-viable result from spslr_selfpatch() should be treated as fatal. For module subjects, a non-viable result from spslr_patch_module() means the module must not be executed and should be unloaded.