alexcrichton opened issue #9402:
Today Cranelift's constant-pools are located in the .text section of the executable, typically located after the function itself. While convenient for code generation this exposes a possible attack vector in Wasmtime where it's trivial to put a "gadget" somewhere in memory. For example using a sequence of
v128.constit would be pretty easy to assemble "machine code" at the end of a function. In the face of a bug in Cranelift this could make it possibly easier to amplify into a sandbox escape perhaps.As a defense-in-depth measure we should try to move the constant pools out of the .text section and into a .data or otherwise read-only section. (not writable or executable). This won't be trivial to do due to the fact that relocations from the text section point at the data section and the relocation range may not always be large enough for the entire text section. Regardless though I wanted to file an issue about this idea.
cfallin commented on issue #9402:
It would definitely be nice to have support for this -- in principle we could return two blobs of bytes as the result of per-function compilation instead of one, and have a relocation type that is "offset from start of this function's code to start of this function's constants".
Out of curiosity, do you happen to know how
ldhandles .rodata references today for very large aarch64/riscv64/... binaries? I wonder if it uses its support for relaxation (assuming most pessimistic range sequence then shrinking if able) -- it'd be unfortunate to have to useadrp/adr/ldrrather than the immediate-pcrel form ofldrfor every constant. I'm not able to find anything on this at the moment...
alexcrichton commented on issue #9402:
That's an excellent question, and one I don't know the answer to myself. I can try to play around with an assembler though and see what happens perhaps!
cfallin commented on issue #9402:
I tried briefly to trigger something interesting, but got stuck at trying to get clang (on macOS/aarch64) to use the short-form LDR-with-immediate instruction; for any load from rodata it seems to use an
adrp/adrpair.For example with (separate files to avoid a neat optimization where clang const-folds the load of constant data):
% cat test.c extern const char* s; int foo() { return *((int*)s); } % cat data.c const char* s = "1234"; % cat main.c #include <stdio.h> extern int foo(); int main() { printf("%d\n", foo()); }I see
_foo's body as0000000100003f44 <_foo>: 100003f44: b0000028 adrp x8, 0x100008000 <_s> 100003f48: 91000108 add x8, x8, #0x0 100003f4c: f9400108 ldr x8, [x8] 100003f50: b9400100 ldr w0, [x8] 100003f54: d65f03c0 retMaybe it wouldn't be so bad to unconditionally emit that form actually; loads from constant pools will be relatively rare. It does burn a register though to compute the address.
alexcrichton commented on issue #9402:
Good point!
Looks like
#[no_mangle] pub extern "C" fn foo() ->f64 { 1.3484 }.LCPI0_0: .xword 0x3ff5930be0ded289 foo: adrp x8, .LCPI0_0 ldr d0, [x8, :lo12:.LCPI0_0] retso yeah it looks like we may want to be a tiny bit clever (don't always "just" materialize the address) but otherwise looks like solving this issue would involve always doing
adrpon aarch64 and the equivalent on riscv64
alexcrichton added the cranelift:area:security label to Issue #9402.
ilyas-mallah commented on issue #9402:
I'd like to work on this. What I have in mind follows @cfallin's idea of returning the constants as a second blob.
MachBufferwould get an option to hand back a function's constants separately instead of emitting them after the code, with every constant use becoming a relocation into that blob. Wasmtime then places the blobs in a read-only section after.text, page-aligned so no constant shares an executable page with code.x64, riscv64 and s390x already reach far enough. AArch64 is the hard part, since
ldrliteral would have to become anadrp-based load, which costs an instruction and, for float and vector constants, a GPR. I'd benchmark the options with Sightglass before picking one.With every function's constants in one section, identical constants could also be coalesced across functions (the part @fitzgen moved here from #1385), and constants would no longer ride along in islands, which was part of what made #12968 tricky.
I'd land it as a few small PRs, starting with the buffer support, then a PR each for the x64, AArch64, riscv64 and s390x backends, and finally turning it on in Wasmtime.
A few things I'm unsure about. Should this be a Cranelift setting or something Wasmtime always enables? Would you prefer a separate section or a page-aligned tail of
.text? And would you want an RFC first?
alexcrichton commented on issue #9402:
It'd be awesome if you're willing to pick this up @ilyas-mallah! As a heads up we as maintainers have a lot of issues in flight right now so we may be a bit slower to respond here or on PRs, but it'd be great to resolve this still. What you describe all sounds reasonable to me, and IIRC some time ago we were thinking of changing all data relocations to
adrp+addor similar for aarch64, although maybe that was in relation to this issue as well... A sightglass run would be definitely helpful to evaluate!As for landing, piecemeal is totally fine. I wouldn't necessarily go out of your way for a Cranelift setting for this, but if it's natural to implement then it seems reasonable to have and would pave the path for testing this easily without committing to it just yet. No need for an RFC on this one, good to just work on and have PRs!
One thing of note -- Wasmtime at least already has some page-aligned stuff after the
.textsection, for example wasm data segments and such. That should be relatively easy to piggy-back on. Wasmtime also puts the.eh_framesection (IIRC) in a page-aligned section after.textso it's not executable as well, which could perhaps show inspiration for how to do this since that also has to do with relocations across the sections.
ilyas-mallah commented on issue #9402:
Thanks, that's really helpful, and no worries about review speed. Good pointers on the data segments and .eh_frame, I'll look at how they handle relocations across sections first, and I'll include Sightglass numbers with the PRs. I'm planning to start early next year.
ilyas-mallah commented on issue #9402:
I'd like to split this into PRs this way, following my earlier comment:
- Cranelift:
MachBuffersupport for the separate constant blob, off by default.- x64: RIP-relative constant references become relocations into the blob.
- AArch64:
adrp-based loads in place ofldrliteral, using whichever sequence Sightglass favors.- riscv64:
auipcplus a load into the blob.- s390x: 32-bit PC-relative references into the blob.
- Winch, since it registers its constants in the same
MachBuffer. Pulley bytecode isn't run as machine code, so it stays as it is.- Wasmtime: emit the new section following the
.eh_framemodel and turn it on by default. This covers serialized modules, a test that no executable page holds constants, and an update todocs/security.md.I'd leave coalescing identical constants across functions for later, it's easier once all of this has landed.
I'd also like to take on #11181, opting
wasmtimemodules into the unsafe Clippy lints one PR at a time. Each PR adds# Safetydocs andSAFETYcomments, with a safe wrapper where a contract can be checked locally. The modules in order arevm/cow.rswithpooling/decommit_queue.rs,vm/mmap.rswithvm/sys/unix/mmap.rs,vm/libcalls.rs,vm/component/libcalls.rs,vm/table.rs,vm/traphandlers.rswith itsbacktrace.rs, andvm/sys/unix/signals.rs.@alexcrichton you mentioned moving all AArch64 data relocations to
adrp+add. Should the AArch64 PR handleLoadExtNameFartoo (it puts an 8-byte address inline in.text), or just the constant pools? And would you rather the last PR turn this on by default, or add aConfigoption that stays off for now?
Last updated: Oct 11 2026 at 04:10 UTC