diff --git a/src/syscall/internal.h b/src/syscall/internal.h index dc59b4dc..3bf1f09e 100644 --- a/src/syscall/internal.h +++ b/src/syscall/internal.h @@ -952,7 +952,7 @@ static inline fd_block_state_t fd_block_state_of(const fd_entry_t *e) return (fd_block_state_t) { .type = e->type, .generation = e->generation, - .seals = e->seals, + .seals = (unsigned int) e->seals, .can_block = e->can_block, .nonblock_owned = e->nonblock_owned, .guest_nonblock = (e->linux_flags & LINUX_O_NONBLOCK) != 0, diff --git a/src/syscall/mem.c b/src/syscall/mem.c index 68f41596..e4b0db68 100644 --- a/src/syscall/mem.c +++ b/src/syscall/mem.c @@ -4540,6 +4540,12 @@ int64_t sys_mprotect(guest_t *g, uint64_t addr, uint64_t length, int prot) return -LINUX_ENOMEM; } guest_region_set_prot(g, addr, mprot_end, prot); + if (prot & LINUX_PROT_EXEC) { + char *host_start = (char *) host_ptr_for_gpa(g, addr); + if (host_start) { + __builtin___clear_cache(host_start, host_start + length); + } + } return 0; } uint64_t mprot_off = addr - g->ipa_base; @@ -4578,6 +4584,10 @@ int64_t sys_mprotect(guest_t *g, uint64_t addr, uint64_t length, int prot) return -LINUX_ENOMEM; } guest_region_set_prot(g, mprot_off, mprot_end, prot); + if (prot & LINUX_PROT_EXEC) { + char *host_start = (char *) g->host_base + mprot_off; + __builtin___clear_cache(host_start, host_start + length); + } } } return 0; diff --git a/tests/manifest.txt b/tests/manifest.txt index cdb75501..8249ba38 100644 --- a/tests/manifest.txt +++ b/tests/manifest.txt @@ -63,6 +63,7 @@ test-shim-data-el1 test-shim-urandom-smp test-shim-urandom-toctou test-shim-urandom-wrap +test-jit-icache [section] I/O subsystem tests test-eventfd diff --git a/tests/test-jit-icache.c b/tests/test-jit-icache.c new file mode 100644 index 00000000..a360ded7 --- /dev/null +++ b/tests/test-jit-icache.c @@ -0,0 +1,134 @@ +/* + * Self-modifying JIT instruction cache invalidation test + * + * Copyright 2026 elfuse contributors + * SPDX-License-Identifier: Apache-2.0 + * + * Tests: mprotect(PROT_READ | PROT_EXEC) invalidates instruction cache + * when guest JIT code is updated in place. + * + * Syscalls exercised: mmap(222), mprotect(226) + */ + +#include +#include +#include +#include +#include +#include + +#include "test-harness.h" + +int passes = 0, fails = 0; + +typedef int (*jit_fn_t)(void); + +static void test_jit_icache_flush(void) +{ + TEST("JIT mprotect instruction cache flush"); + + uint32_t *buf = mmap(NULL, 4096, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (buf == MAP_FAILED) { + FAIL("mmap failed"); + return; + } + +#if defined(__aarch64__) + /* Write 1st version: return 1 0x52800020 : mov w0, #1 0xd65f03c0 : ret */ + buf[0] = 0x52800020u; + buf[1] = 0xd65f03c0u; + + if (mprotect(buf, 4096, PROT_READ | PROT_EXEC) != 0) { + FAIL("mprotect RX failed"); + munmap(buf, 4096); + return; + } + + jit_fn_t fn = (jit_fn_t) buf; + int res1 = fn(); + if (res1 != 1) { + FAIL("initial JIT execution returned wrong value"); + munmap(buf, 4096); + return; + } + + /* Change permissions back to RW to rewrite code */ + if (mprotect(buf, 4096, PROT_READ | PROT_WRITE) != 0) { + FAIL("mprotect RW failed"); + munmap(buf, 4096); + return; + } + + /* Write 2nd version: return 42 0x52800540 : mov w0, #42 0xd65f03c0 : ret */ + buf[0] = 0x52800540u; + buf[1] = 0xd65f03c0u; + + /* Change permissions to RX (must invalidate instruction cache) */ + if (mprotect(buf, 4096, PROT_READ | PROT_EXEC) != 0) { + FAIL("mprotect RX 2nd failed"); + munmap(buf, 4096); + return; + } + + int res2 = fn(); + if (res2 != 42) { + FAIL("stale instruction cache detected (JIT rewrite failed)"); + munmap(buf, 4096); + return; + } +#elif defined(__x86_64__) + uint8_t *b = (uint8_t *) buf; + b[0] = 0xb8; + b[1] = 0x01; + b[2] = 0x00; + b[3] = 0x00; + b[4] = 0x00; + b[5] = 0xc3; + + if (mprotect(buf, 4096, PROT_READ | PROT_EXEC) != 0) { + FAIL("mprotect RX failed"); + munmap(buf, 4096); + return; + } + + jit_fn_t fn = (jit_fn_t) buf; + int res1 = fn(); + if (res1 != 1) { + FAIL("initial JIT execution returned wrong value"); + munmap(buf, 4096); + return; + } + + if (mprotect(buf, 4096, PROT_READ | PROT_WRITE) != 0) { + FAIL("mprotect RW failed"); + munmap(buf, 4096); + return; + } + + b[1] = 0x2a; + + if (mprotect(buf, 4096, PROT_READ | PROT_EXEC) != 0) { + FAIL("mprotect RX 2nd failed"); + munmap(buf, 4096); + return; + } + + int res2 = fn(); + if (res2 != 42) { + FAIL("stale instruction cache detected (JIT rewrite failed)"); + munmap(buf, 4096); + return; + } +#endif + + PASS(); + munmap(buf, 4096); +} + +int main(void) +{ + test_jit_icache_flush(); + SUMMARY("test-jit-icache"); + return fails == 0 ? 0 : 1; +}