Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [SECURITY] mm: mremap/remap_file_pages skip fsnotify pre-content hook
@ 2026-11-10 11:44 n3rd
  2026-11-10 12:10 ` Lorenzo Stoakes (ARM)
  2026-11-10 13:18 ` Jan Kara
  0 siblings, 2 replies; 3+ messages in thread
From: n3rd @ 2026-11-10 11:44 UTC (permalink / raw)
  To: linux-mm@kvack.org, linux-kernel@vger.kernel.org
  Cc: akpm@linux-foundation.org, ljs@kernel.org, Liam R. Howlett,
	Jan Kara, Amir Goldstein, security@kernel.org

[-- Attachment #1: Type: text/plain, Size: 8000 bytes --]



Hello,


I am reporting a security bug in the Linux kernel.


1. Affected kernel versions
Linux v7.2.9 (stable) and v7.3-rc7 (mainline, tested as 7.3.0-rc7) are affected and were reproduced on both, each on a freshly booted guest. v7.3-rc6 has the same code shape and was verified by source inspection only. Versions older than v7.2.9 were not tested and were not bisected, so no claim is made about them.


2. Problem description
The pre-content (HSM) event contract, as stated when the feature was introduced, is that for every accessed file range an event covering at least this range is generated at least once before the file data is accessed. The only hook that emits that event before a mapping is established is fsnotify_mmap_perm() in include/linux/fsnotify.h, and its only caller in the whole tree is vm_mmap_pgoff() in mm/util.c, i.e. the mmap(2) syscall entry. The page-fault level pre-content hook was reverted upstream, so the syscall entry hook is the only remaining gate.


Two paths turn new file ranges into accessible mappings without ever reaching that gate. First, the mremap(2) EXPAND path: in-place growth goes through expand_vma() and expand_vma_in_place() in mm/mremap.c, and move growth through move_vma() and copy_vma() in mm/vma.c; neither mm/mremap.c nor mm/vma.c contains any fsnotify_mmap_perm() or security_mmap_file() call. Second, the remap_file_pages(2) emulation in mm/mmap.c calls security_mmap_file() and then do_mmap(MAP_POPULATE) directly, without fsnotify_mmap_perm().


The impact is an enforcement bypass, not memory corruption. A privileged pre-content daemon (HSM, anti-malware, DLP) that uses fanotify_init(FAN_CLASS_PRE_CONTENT) plus fanotify_mark(FAN_PRE_ACCESS) to make range-level ALLOW/DENY decisions never gets consulted for the newly exposed range, so an unprivileged local user reads file data that the daemon intended to gate. When the daemon uses the event to fill or redact content, the attacker reads the local page cache contents from before that fill or redaction.


Dynamic differential on a freshly booted guest, same daemon, same policy, same files: under a deny policy (FAN_DENY for any event with offset >= 4096), a direct mmap of [4096,8192) and of [8192,12288) both fail with EPERM, proving the gate is active; an mremap(2) in-place expansion from [0,4096) to [0,8192) followed by reading offset 4096 succeeds and returns the byte 'B'; a remap_file_pages(2) remap of [8192,12288) followed by reading offset 8192 succeeds and returns 'C'. In both cases no event covering the newly exposed range was delivered. Controls: mprotect(2) is covered by design because the mmap-time event covers the whole range regardless of prot, and a MAP_GROWSDOWN file mapping is rejected by mmap with EINVAL, so that path is not reachable.


No kernel memory corruption, no out-of-bounds access, no use-after-free, no information disclosure primitive and no privilege escalation: KASAN, Oops, general protection fault and WARNING counts are all zero across two fresh boots. This is a security-feature enforcement bypass.


3. Reproducer
A C reproducer drives a real fanotify pre-content group and a real attacker on a freshly formatted ext4 image. The complete reproducer source, the static binary, the lab scripts and the full console logs are available on request; I am not posting the reproducer publicly.


In outline: a 32 MB ext4 image is formatted and presented to the guest as a virtio-blk disk, because a FAN_PRE_ACCESS mark is rejected with EOPNOTSUPP unless the superblock has SB_I_ALLOW_HSM and only ext4, xfs and btrfs set that flag. A three page file is created with the bytes A, B and C at offsets 0, 4096 and 8192. A root process opens a FAN_CLASS_PRE_CONTENT group and adds FAN_PRE_ACCESS inode marks on the file and on a writable copy of it. For each operation the tool forks a child that drops to uid 65534 and performs the operation while the parent services events in the same session, first with an allow-all policy to inventory events, then with a deny policy for any event with offset >= 4096 to show the enforcement differential. Per operation it reports the newly exposed range, the events that were delivered and whether any single event covered the new range.


4. Conditions
Attacker: unauthenticated local user. mremap(2) needs only read permission on the target file; remap_file_pages(2) additionally needs write permission, because do_mmap() clears VMA_SHARED_BIT for a MAP_SHARED mapping whose file lacks FMODE_WRITE and the emulation entry requires that bit. The O_RDONLY variant returns EINVAL, which was measured.


Victim: a privileged pre-content daemon with CAP_SYS_ADMIN that uses fanotify_init(FAN_CLASS_PRE_CONTENT) and fanotify_mark(FAN_PRE_ACCESS) for range-level ALLOW/DENY decisions, on a filesystem that supports SB_I_ALLOW_HSM (ext4, xfs or btrfs).


Kernel configuration: CONFIG_FANOTIFY=y and CONFIG_FANOTIFY_ACCESS_PERMISSIONS=y; the whole pre-content event path is compiled out without them.


Ordering: the pre-content mark must exist before the attacker opens the file, because FMODE_FSNOTIFY_HSM is computed at open time from whether the superblock has a pre-content priority watcher. In a real deployment the daemon is already running, so this holds naturally.


Test guest: QEMU 6.2.0 under TCG, 2 vCPU, 4096 MB, initramfs root plus a freshly formatted 32 MB ext4 data disk. The kernels were built from the v7.3-rc7 and v7.2.9 sources in this workspace.


5. Suspected location
include/linux/fsnotify.h: fsnotify_mmap_perm(), whose only caller in the whole tree is vm_mmap_pgoff() in mm/util.c; mm/mremap.c: the mremap EXPAND path (expand_vma(), expand_vma_in_place(), vma_merge_extend(), move_vma(), copy_vma_and_data()), which contains no fsnotify or security_mmap_file call; mm/vma.c: copy_vma(), likewise with no fsnotify or security_mmap_file call; mm/mmap.c: the remap_file_pages() emulation, which calls security_mmap_file() but not fsnotify_mmap_perm(); include/linux/fs/super_types.h: SB_I_ALLOW_HSM, set only by ext4, xfs and btrfs in their super.c; fs/notify/fanotify/fanotify_user.c: fanotify_events_supported(), where a FAN_PRE_ACCESS mark requires SB_I_ALLOW_HSM.


6. Suggested fix
Add a fsnotify_mmap_perm() call for the newly exposed file range in the mremap EXPAND path, after the EXPAND decision and before the VMA is grown or copied (that is, in mm/mremap.c before expand_vma_in_place() and move_vma() are entered, or inside copy_vma() in mm/vma.c), and roll the expansion back when the hook denies. Add the same hook to the remap_file_pages() emulation in mm/mmap.c right after security_mmap_file(), and to expand_downwards() for the range grown downwards. The semantics should match vm_mmap_pgoff(). A selftest would be useful: mmap one page of a file, mremap it to two pages, read the second page, and assert that the watcher received an event covering the new range.


7. Mitigation
On the daemon side, do not treat the FAN_PRE_ACCESS event as the only enforcement point: cross-check the caller's mapping range independently before answering, and include ranges mapped through mremap or remap_file_pages in a periodic re-check. On the system side, limiting which filesystems hold sensitive content and tightening ordinary users' write access to protected directories reduces the reachability of the remap_file_pages variant, but does not remove the bypass.


8. Disclosure
Public. This report is being sent to the public mm and LKML lists because it was prepared with the help of an AI coding assistant, and the kernel security process requires AI-assisted reports to be treated as public; no embargo is requested. The reproducer is not posted publicly and is available on request. No CVE has been requested.


9. AI-assistance note
This report was prepared with the help of an AI coding assistant, including source analysis, the reproducer and the lab automation. Per the kernel security process I treat the issue as public and I am not posting the reproducer publicly.

[-- Attachment #2: Type: text/html, Size: 9212 bytes --]

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-11-10 13:18 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-10 11:44 [SECURITY] mm: mremap/remap_file_pages skip fsnotify pre-content hook n3rd
2026-11-10 12:10 ` Lorenzo Stoakes (ARM)
2026-11-10 13:18 ` Jan Kara

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox