Summary
Bottlerocket 2026.8.24 (kernel 6.12.100) includes 82544d36b172 ("selinux: fix overlayfs mmap() and mprotect() access checks", CVE-2026-46054). That commit adds a second FILE__EXECMOD check in selinux_file_mprotect(), evaluated against the overlay mounter's credentials and the lower inode rather than the task's.
On Bottlerocket the mounter is containerd, system_u:system_r:runtime_t:s0:c0.c1023. The shipped policy grants:
allow runtime_s global:file { execute execute_no_trans }; # no execmod
allow container_s global:file execmod; # container_s = {container_t, control_t, super_t}
runtime_t is not in container_s and holds no execmod against any type, so this check fails unconditionally — for every container, for every file in every image, independent of the file's label. Any workload that mmaps a file shipped in its image, dirties it via COW, then mprotects it executable now gets EACCES.
policy/mcs and policy/mls place no constraint on execmod, and rules.cil carries (neverallow host_s global (files (relax))), so the grant cannot be added by a downstream policy module — host_s includes runtime_t.
Affected
- Affected:
2026.8.24 (aws-k8s-1.33-standard), kernel 6.12.100.
- Not affected:
2026.8.10, kernel 6.12.95 — that build does not contain the commit. selinux_mmap_backing_file and selinux_backing_file_alloc are absent from its /proc/kallsyms and present on 2026.8.24, which is a version-independent way to test a given build.
The revert of this patch was added in bottlerocket-kernel-kit#496 ("Undo patches that break dotnet workloads") and deleted in bottlerocket-kernel-kit#526 while bumping 6.12.95 → 6.12.100. The 6.12.97 follow-up 9fe595fad54d narrows the mounter path for the process-class EXECMEM check — which is why the dotnet symptom in #496 went away — but leaves the file-class EXECMOD check untouched.
Reproducer
A static binary in a container with a default container_t label, run against a file baked into the image:
int fd = open(argv[1], O_RDONLY);
size_t n = 65536;
char *p = mmap(NULL, n, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
p[0] ^= 1; /* COW fault -> vma->anon_vma set */
if (mprotect(p, n, PROT_READ|PROT_EXEC) != 0) perror("mprotect"); /* EACCES on 6.12.100 */
Controls that still pass on 6.12.100, isolating the case:
| probe |
6.12.95 |
6.12.100 |
anonymous, dirtied, then PROT_EXEC |
ok |
ok |
same file mapped clean, then PROT_EXEC |
ok |
ok |
same file mapped PROT_EXEC at mmap() time |
ok |
ok |
memfd, dirtied, then PROT_EXEC |
ok |
ok |
file mapped MAP_PRIVATE, dirtied, then PROT_EXEC |
ok |
EACCES |
The failure appears only when the file is on an overlayfs lower layer. Copying it to an emptyDir and repeating the sequence succeeds.
Denial:
avc: denied { execmod } for pid=642369 comm="riza"
path="/root/.cache/.../rizapy-0.2.0.cwasm" dev="nvme0n1p1" ino=218528986
scontext=system_u:system_r:runtime_t:s0:c0.c1023
tcontext=system_u:object_r:cache_t:s0 tclass=file permissive=0
Note the shape: a container-visible path= with an scontext of containerd and a dev=/ino= on the host data volume — the signature of the backing-file check.
Impact
Silent, image-independent breakage of any runtime that patches mapped code in place: wasmtime AOT (.cwasm) loading, ELF objects with DT_TEXTREL, .NET ReadyToRun. JIT engines that compile into anonymous memory (V8, JVM, LuaJIT) are unaffected, as is ordinary ld.so loading, since runtime_s does hold execute.
The workload sees only EACCES from mprotect. Nothing in Kubernetes surfaces the AVC, and there is no action a workload can take from inside the container to fix it. In our case a code-execution service crashlooped on every node the new AMI reached; the only workable mitigation was moving the affected file onto a non-overlay volume.
We could not find this documented in the 2026.8.24 release notes or an existing issue.
Requested
Grant execmod to the container runtime domain in the Bottlerocket policy, mirroring the existing container_s grant, so the mounter check can pass where the task check already does. Failing that, document it as a breaking change in the release notes along with the volume workaround.
Separately, and for upstream rather than here: it may be worth asking whether FILE__EXECMOD belongs under the mounter check at all. 9fe595fad54d removed PROCESS__EXECMEM from that path on the reasoning that it "doesn't pertain to the file itself". For a private, COW-dirtied mapping the modified pages are anonymous and grant the mounter no additional access to the file's contents, so the same argument appears to apply.
Summary
Bottlerocket 2026.8.24 (kernel 6.12.100) includes
82544d36b172("selinux: fix overlayfs mmap() and mprotect() access checks", CVE-2026-46054). That commit adds a secondFILE__EXECMODcheck inselinux_file_mprotect(), evaluated against the overlay mounter's credentials and the lower inode rather than the task's.On Bottlerocket the mounter is containerd,
system_u:system_r:runtime_t:s0:c0.c1023. The shipped policy grants:runtime_tis not incontainer_sand holds noexecmodagainst any type, so this check fails unconditionally — for every container, for every file in every image, independent of the file's label. Any workload that mmaps a file shipped in its image, dirties it via COW, thenmprotects it executable now getsEACCES.policy/mcsandpolicy/mlsplace no constraint onexecmod, andrules.cilcarries(neverallow host_s global (files (relax))), so the grant cannot be added by a downstream policy module —host_sincludesruntime_t.Affected
2026.8.24 (aws-k8s-1.33-standard), kernel 6.12.100.2026.8.10, kernel 6.12.95 — that build does not contain the commit.selinux_mmap_backing_fileandselinux_backing_file_allocare absent from its/proc/kallsymsand present on 2026.8.24, which is a version-independent way to test a given build.The revert of this patch was added in bottlerocket-kernel-kit#496 ("Undo patches that break dotnet workloads") and deleted in bottlerocket-kernel-kit#526 while bumping 6.12.95 → 6.12.100. The 6.12.97 follow-up
9fe595fad54dnarrows the mounter path for the process-classEXECMEMcheck — which is why the dotnet symptom in #496 went away — but leaves the file-classEXECMODcheck untouched.Reproducer
A static binary in a container with a default
container_tlabel, run against a file baked into the image:Controls that still pass on 6.12.100, isolating the case:
PROT_EXECPROT_EXECPROT_EXECatmmap()timememfd, dirtied, thenPROT_EXECMAP_PRIVATE, dirtied, thenPROT_EXECThe failure appears only when the file is on an overlayfs lower layer. Copying it to an
emptyDirand repeating the sequence succeeds.Denial:
Note the shape: a container-visible
path=with anscontextof containerd and adev=/ino=on the host data volume — the signature of the backing-file check.Impact
Silent, image-independent breakage of any runtime that patches mapped code in place: wasmtime AOT (
.cwasm) loading, ELF objects withDT_TEXTREL, .NET ReadyToRun. JIT engines that compile into anonymous memory (V8, JVM, LuaJIT) are unaffected, as is ordinaryld.soloading, sinceruntime_sdoes holdexecute.The workload sees only
EACCESfrommprotect. Nothing in Kubernetes surfaces the AVC, and there is no action a workload can take from inside the container to fix it. In our case a code-execution service crashlooped on every node the new AMI reached; the only workable mitigation was moving the affected file onto a non-overlay volume.We could not find this documented in the 2026.8.24 release notes or an existing issue.
Requested
Grant
execmodto the container runtime domain in the Bottlerocket policy, mirroring the existingcontainer_sgrant, so the mounter check can pass where the task check already does. Failing that, document it as a breaking change in the release notes along with the volume workaround.Separately, and for upstream rather than here: it may be worth asking whether
FILE__EXECMODbelongs under the mounter check at all.9fe595fad54dremovedPROCESS__EXECMEMfrom that path on the reasoning that it "doesn't pertain to the file itself". For a private, COW-dirtied mapping the modified pages are anonymous and grant the mounter no additional access to the file's contents, so the same argument appears to apply.