[PATCH] x86/its: Make ITS thunk pages read-only without the ROX execmem cache
From: Frédéric MARIE-JOSEPH
Date: Thu Oct 01 2026 - 09:47:53 EST
Hello to list,
That's my first post so I hope I do things right. I found what seems to me like a bug, and worked with Claude to find a patch. Hope it will be usefull.
With CONFIG_MODULES=n there is no STRICT_MODULE_RWX, so x86 does not
select ARCH_HAS_EXECMEM_ROX and execmem_restore_rox() is the stub that
returns 0. its_pages_protect() relies on it alone, so the dynamic ITS
thunk pages, made executable by set_memory_x() in its_alloc(), stay
writable and executable for the life of the kernel. CONFIG_DEBUG_WX
reports them on every boot:
x86/mm: Checked W+X mappings: FAILED, 7 W+X pages found.
The attached patch makes the pages read-only with set_memory_ro() when
the ROX cache is not built; when it is, nothing changes.
Tested on a CONFIG_MODULES=n x86_64 build of 6.18.53 booted under
QEMU/KVM: with the change, the boots report "x86/mm: Checked W+X
mappings: passed, no W+X pages found." and no warning. On mainline
(551c722f4), whose its_pages_protect() is identical,
arch/x86/kernel/alternative.o builds without warnings with that
configuration and with x86_64_defconfig; mainline itself was not booted.
The bug was found, and the fix and its message written, with the help
of an AI coding assistant (Claude, Anthropic), and reviewed by me.
The patch is attached in git format-patch form (my mail client would
damage it inline); it applies with "git am". I can resend it inline
with git send-email if you prefer.
Fixes: a82b26451de1 ("x86/its: explicitly manage permissions for ITS pages")
Regards,
Frederic MARIE-JOSEPH
Frédéric MARIE-JOSEPH
N° TVA intracommunautaire FR78788461903
N° SIRET 78846190300015/78846190300023
From: Frederic MARIE-JOSEPH <fredericmariejoseph@xxxxxxxxx>
Date: Thu, 1 Oct 2026 15:00:00 +0200
Subject: [PATCH] x86/its: Make ITS thunk pages read-only without the ROX
execmem cache
With CONFIG_MODULES=n there is no STRICT_MODULE_RWX, so x86 does not
select ARCH_HAS_EXECMEM_ROX and execmem_restore_rox() is the stub that
returns 0. its_pages_protect() relies on it alone, so the dynamic ITS
thunk pages, made executable by set_memory_x() in its_alloc(), stay
writable and executable for the life of the kernel. CONFIG_DEBUG_WX
reports them on every boot:
x86/mm: Checked W+X mappings: FAILED, 7 W+X pages found.
Make the pages read-only with set_memory_ro() when the ROX cache is not
built. When it is, nothing changes.
Found by the W+X check of a CONFIG_MODULES=n x86_64 build of 6.18.53
booted under QEMU/KVM. Tested on that build only: with the change, both
boots of the test report "x86/mm: Checked W+X mappings: passed, no W+X
pages found." and no warning. On mainline, whose its_pages_protect() is
identical, arch/x86/kernel/alternative.o builds without warnings with that
CONFIG_MODULES=n configuration and with x86_64_defconfig (modules and the
ROX cache on, where the IS_ENABLED() branch keeps the current call);
mainline itself was not booted.
The bug was found, and the fix and this message written, with the help
of an AI coding assistant (Claude, Anthropic), and reviewed by me.
Fixes: a82b26451de1 ("x86/its: explicitly manage permissions for ITS pages")
Assisted-by: LLM
Signed-off-by: Frederic MARIE-JOSEPH <fredericmariejoseph@xxxxxxxxx>
---
arch/x86/kernel/alternative.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c
index 582c6d830..ad08ac9b1 100644
--- a/arch/x86/kernel/alternative.c
+++ b/arch/x86/kernel/alternative.c
@@ -168,7 +168,15 @@ static void its_pages_protect(struct its_array *pages)
{
for (int i = 0; i < pages->num; i++) {
void *page = pages->pages[i];
- execmem_restore_rox(page, PAGE_SIZE);
+ /*
+ * Without the ROX execmem cache (no STRICT_MODULE_RWX, so no
+ * modules) execmem_restore_rox() is a stub and the thunk pages
+ * would stay writable and executable: make them read-only.
+ */
+ if (IS_ENABLED(CONFIG_ARCH_HAS_EXECMEM_ROX))
+ execmem_restore_rox(page, PAGE_SIZE);
+ else
+ set_memory_ro((unsigned long)page, 1);
}
}