Load Address, ASLR, and Relocation

July 16, 2026 ยท View on GitHub

The loader decides where an image lands, randomizes that base for a position-independent image, maps its segments, applies the relative relocations a PIE needs, and computes the relocated entry point. This page documents that orchestration. The code is under src/elf/loader/core/loader/.

The load orchestration

load_entry_into (src/elf/loader/core/loader/load_entry_into.rs:20) is the whole trusted load into a target address space, in order:

  load_entry_into(elf_data, target_asid):
      header    = parse_elf_header(elf_data)
      validate_elf(header)                          // the header checks
      ph_count  = program_header_bounds(...)         // bounds-checked table
      base_addr = load_base(header, aslr_manager)    // where it lands
      for each program header:
          if PT_LOAD:  load_segment(...)             // W^X gate + map + zero-fill
      apply_relative_relocations(elf_data, header, ph_count, base_addr, target_asid)
      return entry_point(header, base_addr)

Validation precedes any mapping, the base is chosen once, every loadable segment is mapped through the segment path, and only then are relocations applied and the entry point returned. The function takes a target_asid, so it maps into a specific address space, the freshly created one for the capsule being spawned, not the current one.

The load base and ASLR

load_base (loader/base_addr.rs:19) chooses the base from whether the image is position-independent:

  load_base(header, aslr):
      if header.is_pie():  aslr.randomize_base(DEFAULT_PIE_BASE)
      else:                DEFAULT_STATIC_BASE

A PIE (an ET_DYN image, which is what capsules typically are) is loaded at a randomized base; a non-PIE executable is loaded at its fixed base. randomize_base (aslr/manager/randomize.rs:24) adds a random offset within the executable randomization range to the preferred base and masks it back to a page boundary:

  randomize_base(preferred):
      if executable_randomization:  (preferred + random_offset(RANGE)) & !0xFFF
      else:                         preferred

Randomization is on by default (aslr/manager/settings.rs), and each load uses the loader's ASLR manager, so a capsule's text and data land at an address an attacker cannot predict from the image alone. The randomization is page-aligned so it does not disturb segment alignment.

Relocations and the entry point

A PIE's absolute references are fixed up after mapping by apply_relative_relocations, which walks the relocation entries and applies the relative (R_X86_64_RELATIVE) fixups against the chosen base into the target address space. This is the relocation half of the capsule RELRO posture: the global offset table is relocated at load, and the paging layer write-protects it afterward. Finally entry_point (loader/base_addr.rs:27) returns the entry, relocated by the base for a PIE and taken absolute for a non-PIE:

  entry_point(header, base):
      if header.is_pie():  base + header.e_entry
      else:                header.e_entry

The returned entry is what the spawn path installs as the capsule's initial instruction pointer.

Security analysis

This is the orchestration layer, so its properties are about ordering and about not leaking a half-loaded image into a live address space. Three hold.

Validation strictly precedes mapping. load_entry_into (src/elf/loader/core/loader/load_entry_into.rs:20) runs parse_elf_header, validate_elf, and program_header_bounds before it maps a single segment, so no frame is allocated and no page is installed for an image that fails a header or bounds check. A malformed image is refused up front rather than partway through mapping, which is what lets the caller treat a load failure as a clean no-op with nothing to unwind at this level. The header and bounds checks themselves are on the validation page; the point here is that the orchestration calls them first, every time.

The load targets a specific ASID, never the current one. load_entry_into takes a target_asid and threads it through load_segment and apply_relative_relocations, so every page lands in the freshly created address space for the capsule being spawned. The loader maps into that ASID directly, so the capsule's text and data are never briefly visible in the kernel's mapping or another capsule's while the load is in progress. That is what keeps a capsule's memory private from the moment it exists.

ASLR is on by default and page-aligned. A position-independent image (an ET_DYN PIE, which is what capsules typically are) is loaded at a randomized base: load_base (loader/base_addr.rs:19) calls randomize_base (aslr/manager/randomize.rs:24), which adds a random offset within the executable randomization range and masks the result back to a page boundary, and randomization is enabled by default (aslr/manager/settings.rs). So a capsule's segments land at an address an attacker cannot predict from the image alone, and the mask keeps the base page-aligned so it does not disturb segment alignment. The honest boundary is that the relocation pass, apply_relative_relocations, only applies the relative (R_X86_64_RELATIVE) fixups a PIE needs against the chosen base; it is not a general dynamic linker, and an image needing other relocation types is not what this path is for. The relocated GOT it produces is write-protected afterward by the paging layer, which is the capsule RELRO posture.

Debugging the load orchestration

Because the orchestration calls the header, segment, and relocation stages in order, a failure at any stage returns that stage's ElfError (src/elf/errors/types/state.rs:17) straight up, and the spawn path prints it after the capsule's debug tag (.../install/load_elf_into_pid.rs:27). Reading the printed string against the order in load_entry_into localises the failure to a stage:

  Invalid ELF magic / class / type ...   header stage        validate_elf refused the header
  Program headers out of bounds          bounds stage        the program-header table did not fit
  Segment ... / W^X / alignment          segment stage       a PT_LOAD segment was rejected or could not map
  Relocation processing failed           relocation stage    a relative fixup could not be applied
  Unsupported relocation type            relocation stage    a non-RELATIVE relocation was present

The value of the ordering is that the stage tells you how far the load got. A header-stage error means nothing was mapped. A segment-stage error means some earlier segments in the same image may have been mapped into target_asid before the failing one, but since the whole address space belongs to a capsule that will not be scheduled, the caller discards the ASID rather than unmapping page by page. A relocation-stage error, RelocationFailed or UnsupportedRelocation, means every segment mapped cleanly and only the fixup pass failed, which points at a linker producing relocations this loader does not apply rather than at a corrupt or oversized image. As everywhere on this path, the top-level spawn log collapses all of these to reason=elf_load (.../from_vfs/load.rs:99); the preceding ElfError string is the one that says which stage.

Source map

  src/elf/loader/core/loader/load_entry_into.rs   the ordered load orchestration
  src/elf/loader/core/loader/base_addr.rs          load_base, entry_point
  src/elf/aslr/manager/randomize.rs                randomize_base
  src/elf/aslr/manager/settings.rs                 ASLR defaults
  src/elf/loader/core/relocate/                    the relative relocation pass
  src/elf/errors/types/state.rs                    the ElfError stages surface here

Every reference above is verified against those trees. The header and bounds checks this orchestration runs first are on the validation page, the per-segment W^X and zero-fill it drives are on the segment loading page, and the verify-then-load gate that calls this whole path is on the integration page.