Skip to content

Move the executable heap flag into the PEB and make it a runtime setting - #2026

Open
ludfjig wants to merge 2 commits into
hyperlight-dev:mainfrom
ludfjig:ll/heap-executable-peb
Open

ludfjig wants to merge 2 commits into
hyperlight-dev:mainfrom
ludfjig:ll/heap-executable-peb

Conversation

@ludfjig

@ludfjig ludfjig commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

The host writes the executable heap flag to scratch-top metadata on every restore. Restoring a snapshot on a host with a different executable_heap feature mixes executable and non-executable heap pages.

This PR moves the flag into the PEB. The guest copies it at init, and snapshots keep it.

It also adds SandboxConfiguration::set_executable_heap and SandboxBuilder::executable_heap. The cargo feature applies to every sandbox in a process, while the setting lets each sandbox choose. The feature sets the default, and it can be removed later without losing the option. Consider removing executable_heap host cargo feature (#2003) tracks that.

Breaking: snapshot ABI 10.

Follows up on this review discussion.

@ludfjig ludfjig added kind/refactor For PRs that restructure or remove code without adding new functionality. area/API Related to the API or public interface regen-goldens Regenerate snapshot golden fixtures labels Nov 4, 2026
@hyperlight-gh-bot

This comment has been minimized.

The host wrote the flag to scratch-top metadata on every restore. A
snapshot restored by a host with a different executable_heap feature
mixed executable and non-executable heap pages. The guest copies the
flag from the PEB at init, and snapshots keep it. Bump the snapshot ABI
to 10.

Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
Add SandboxConfiguration::set_executable_heap and
SandboxBuilder::executable_heap. The executable_heap feature sets the
default.

Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
@ludfjig
ludfjig force-pushed the ll/heap-executable-peb branch from 05d320c to 682e2cb Compare November 4, 2026 22:38
@hyperlight-gh-bot

Copy link
Copy Markdown

Benchmark Results

Measured commit: 682e2cb2bbc3
Baseline commit: a03088470d82

kvm / amd (Linux) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

decode_vec_bytes_copy encode_control
374.72 µs (➖ 1.00x slower)
byte_chunks 757.08 ns (➖ 1.01x slower)
vec_bytes 588.13 ns (➖ 1.01x slower)

payload_allocation

slot_pool_segmented
65536 141.90 ns (➖ 1.05x faster)
262144 541.30 ns (➖ 1.04x slower)

sandboxes

create_initialized_and_drop
medium 13.42 ms (➖ 1.00x faster)

slot_pool

alloc_dealloc_4096 alloc_dealloc_128 alloc_dealloc_1500
7.67 ns (➖ 1.01x faster) 7.68 ns (➖ 1.08x faster) 7.81 ns (➖ 1.02x slower)

snapshot_files

load_snapshot_unverified
small 99.57 µs (➖ 1.02x slower)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
65536 1.93 µs (➖ 1.08x faster) 2.04 µs (➖ 1.06x slower)
262144 7.38 µs (➖ 1.05x slower) 7.07 µs (➖ 1.01x slower)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
8192 1.03 µs (➖ 1.01x slower) 1.04 µs (➖ 1.01x slower)
65536 6.17 µs (➖ 1.02x faster) 6.19 µs (➖ 1.03x faster)
262144 27.22 µs (➖ 1.01x faster)
kvm / intel (Linux) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

decode_vec_bytes_copy encode_control
681.49 µs (➖ 1.03x faster)
byte_chunks 694.65 ns (➖ 1.01x faster)
vec_bytes 535.67 ns (➖ 1.02x slower)

payload_allocation

slot_pool_segmented
65536 165.80 ns (➖ 1.02x slower)
262144 571.50 ns (➖ 1.03x slower)

sandboxes

create_initialized_and_drop
medium 15.06 ms (➖ 1.03x faster)

slot_pool

alloc_dealloc_4096 alloc_dealloc_128 alloc_dealloc_1500
7.17 ns (➖ 1.01x slower) 7.16 ns (➖ 1.01x slower) 7.12 ns (➖ 1.01x faster)

snapshot_files

load_snapshot_unverified
small 51.72 µs (➖ 1.02x faster)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
65536 2.28 µs (➖ 1.01x slower) 2.22 µs (➖ 1.04x faster)
262144 7.88 µs (➖ 1.03x faster) 7.91 µs (➖ 1.02x faster)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
8192 753.68 ns (➖ 1.03x faster) 756.14 ns (➖ 1.04x faster)
65536 7.34 µs (➖ 1.05x faster) 7.51 µs (➖ 1.02x faster)
262144 30.46 µs (➖ 1.03x faster)
mshv3 / amd (Linux) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

decode_vec_bytes_copy encode_control
314.99 µs (➖ 1.00x faster)
byte_chunks 920.25 ns (➖ 1.00x slower)
vec_bytes 704.52 ns (➖ 1.01x slower)

payload_allocation

slot_pool_segmented
65536 182.36 ns (➖ 1.00x slower)
262144 671.08 ns (➖ 1.00x faster)

sandboxes

create_initialized_and_drop
medium 64.80 ms (➖ 1.04x faster)

slot_pool

alloc_dealloc_4096 alloc_dealloc_128 alloc_dealloc_1500
9.87 ns (➖ 1.00x faster) 9.87 ns (➖ 1.00x faster) 9.87 ns (➖ 1.00x faster)

snapshot_files

load_snapshot_unverified
small 108.04 µs (➖ 1.01x slower)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
65536 2.45 µs (➖ 1.01x slower) 2.60 µs (➖ 1.02x faster)
262144 9.10 µs (➖ 1.10x faster) 9.24 µs (➖ 1.01x slower)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
8192 1.25 µs (➖ 1.02x faster) 1.28 µs (➖ 1.00x slower)
65536 7.96 µs (➖ 1.04x faster) 8.07 µs (➖ 1.02x faster)
262144 34.55 µs (➖ 1.01x faster)
mshv3 / intel (Linux) (✅ 1.65x)

Top improvements

  • sandboxes/create_initialized_and_drop/medium — ✅ 1.65x faster

Benchmark Results

function_call_codec

decode_vec_bytes_copy encode_control
685.74 µs (➖ 1.01x faster)
byte_chunks 921.56 ns (➖ 1.02x slower)
vec_bytes 666.44 ns (➖ 1.20x faster)

payload_allocation

slot_pool_segmented
65536 279.48 ns (➖ 1.09x slower)
262144 698.00 ns (➖ 1.08x slower)

sandboxes

create_initialized_and_drop
medium 47.72 ms (✅ 1.65x faster)

slot_pool

alloc_dealloc_4096 alloc_dealloc_128 alloc_dealloc_1500
8.67 ns (➖ 1.00x faster) 8.69 ns (➖ 1.00x faster) 8.68 ns (➖ 1.01x faster)

snapshot_files

load_snapshot_unverified
small 52.96 µs (➖ 1.00x slower)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
65536 2.28 µs (➖ 1.00x slower) 2.29 µs (➖ 1.00x faster)
262144 7.96 µs (➖ 1.00x slower) 7.97 µs (➖ 1.01x slower)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
8192 883.05 ns (➖ 1.02x faster) 901.54 ns (➖ 1.01x faster)
65536 7.41 µs (➖ 1.00x faster) 7.53 µs (➖ 1.00x slower)
262144 41.72 µs (➖ 1.03x slower)
hyperv-ws2025 / amd (Windows) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

decode_vec_bytes_copy encode_control
2.19 ms (➖ 1.02x slower)
byte_chunks 1.15 µs (➖ 1.02x slower)
vec_bytes 754.16 ns (➖ 1.05x faster)

payload_allocation

slot_pool_segmented
65536 230.19 ns (➖ 1.00x faster)
262144 792.08 ns (➖ 1.00x slower)

sandboxes

create_initialized_and_drop
medium 11.86 ms (➖ 1.07x faster)

slot_pool

alloc_dealloc_4096 alloc_dealloc_128 alloc_dealloc_1500
10.57 ns (➖ 1.00x slower) 10.65 ns (➖ 1.01x slower) 10.58 ns (➖ 1.00x faster)

snapshot_files

load_snapshot_unverified
small 847.64 µs (➖ 1.08x slower)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
65536 2.58 µs (➖ 1.02x slower) 2.52 µs (➖ 1.02x faster)
262144 8.61 µs (➖ 1.09x faster) 8.59 µs (➖ 1.00x faster)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
8192 1.35 µs (➖ 1.04x faster) 1.34 µs (➖ 1.02x faster)
65536 8.88 µs (➖ 1.01x faster) 9.10 µs (➖ 1.06x faster)
262144 38.08 µs (➖ 1.03x faster)
hyperv-ws2025 / intel (Windows) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

decode_vec_bytes_copy encode_control
2.99 ms (➖ 1.03x faster)
byte_chunks 1.28 µs (➖ 1.00x slower)
vec_bytes 796.42 ns (➖ 1.01x faster)

payload_allocation

slot_pool_segmented
65536 207.63 ns (➖ 1.02x faster)
262144 746.82 ns (➖ 1.01x faster)

sandboxes

create_initialized_and_drop
medium 15.64 ms (➖ 1.09x faster)

slot_pool

alloc_dealloc_4096 alloc_dealloc_128 alloc_dealloc_1500
10.18 ns (➖ 1.01x faster) 9.92 ns (➖ 1.03x slower) 9.91 ns (➖ 1.03x slower)

snapshot_files

load_snapshot_unverified
small 700.99 µs (➖ 1.05x slower)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
65536 2.45 µs (➖ 1.06x slower) 2.29 µs (➖ 1.01x faster)
262144 7.47 µs (➖ 1.02x faster) 7.67 µs (➖ 1.02x slower)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
8192 1.08 µs (➖ 1.01x slower) 1.22 µs (➖ 1.15x slower)
65536 8.35 µs (➖ 1.01x faster) 8.16 µs (➖ 1.04x faster)
262144 49.81 µs (➖ 1.02x faster)

Reported by cargo ci bench-report --candidate run:41927117832 --config-file bench_report.toml.

@ludfjig
ludfjig marked this pull request as ready for review November 5, 2026 19:47
Copilot AI balanced review requested due to automatic review settings November 5, 2026 19:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The heap-execution test can reuse an already executable page, allowing it to pass despite incorrect permissions on newly faulted pages after restore.

1 open issue
What changed in this PR

This PR makes heap execution a per-sandbox setting and preserves it across snapshots. The guest reads the setting from the process environment block (PEB) at initialization, keeping heap permissions independent of the restoring host’s feature configuration.

Changes:

  • Adds runtime configuration APIs, with the Cargo feature supplying the default.
  • Stores the flag in the PEB and uses guest-owned state when mapping heap pages.
  • Advances snapshot ABI to 10 and updates tests, version pins, and documentation.
File Description
src/​hyperlight_host/​tests/​snapshot_goldens/​goldens_version.rs Pins goldens to v10.0.
src/​hyperlight_host/​tests/​integration_test.rs Tests runtime settings and snapshot preservation.
src/​hyperlight_host/​src/​sandbox/​snapshot/​tripwires.rs Pins expected ABI to 10.
src/​hyperlight_host/​src/​sandbox/​snapshot/​mod.rs Passes the configured flag into PEB creation.
src/​hyperlight_host/​src/​sandbox/​snapshot/​file/​media_types.rs Sets snapshot ABI to 10.
src/​hyperlight_host/​src/​sandbox/​snapshot/​file/​config.rs Updates serialized ABI test fixtures.
src/​hyperlight_host/​src/​sandbox/​snapshot/​file_tests.rs Updates ABI assertions and rejection expectations.
src/​hyperlight_host/​src/​sandbox/​config.rs Defines the runtime setting and feature-based default.
src/​hyperlight_host/​src/​sandbox/​builder.rs Exposes the builder setting and documents snapshot behavior.
src/​hyperlight_host/​src/​mem/​mgr.rs Removes restore-time writes of the heap flag.
src/​hyperlight_host/​src/​mem/​layout.rs Writes the configured flag into the PEB.
src/​hyperlight_host/​src/​hypervisor/​hyperlight_vm/​x86_64.rs Supplies the flag when constructing test memory.
src/​hyperlight_guest/​src/​layout.rs Removes the scratch-top flag accessor.
src/​hyperlight_guest_bin/​src/​lib.rs Copies the PEB flag into guest state before heap initialization.
src/​hyperlight_guest_bin/​src/​arch/​amd64/​exception/​handle.rs Reads guest-owned permissions for heap faults.
src/​hyperlight_guest_bin/​src/​arch/​aarch64/​exception/​handle.rs Reads guest-owned permissions for heap faults.
src/​hyperlight_common/​src/​mem.rs Adds the heap flag to the shared PEB structure.
src/​hyperlight_common/​src/​layout.rs Preserves scratch metadata alignment with padding.
Justfile Clarifies the feature-enabled test command’s comment.
docs/​snapshot-versioning.md Documents ABI 10 and snapshot regeneration.
CHANGELOG.md Records the runtime APIs and snapshot compatibility break.

Review effort: Balanced

.executable_heap(false)
.build()
.unwrap();
let result: String = restored.call("ExecuteOnHeap", ()).unwrap();
@andreiltd

andreiltd commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

I was hoping we could remove the PEB entirely. See #2030

@syntactically

Copy link
Copy Markdown
Member

I agree it would be nice to remove the PEB, but it seems like the right lifecycle for this. Maybe we can just add this as an extra argument to initialise and have it stashed in a different global or something?

@ludfjig

ludfjig commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

I agree it would be nice to remove the PEB, but it seems like the right lifecycle for this. Maybe we can just add this as an extra argument to initialise and have it stashed in a different global or something?

sounds good to me

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/API Related to the API or public interface kind/refactor For PRs that restructure or remove code without adding new functionality. regen-goldens Regenerate snapshot golden fixtures

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants