| 0/0 |
2026/08/11 00:56 |
flow |
assessment-security |
52m
Results: map[DenialOfService:true Explanation:The bug is a `BUG_ON(folio_mapped(folio))` in `folio_unmap_invalidate()`. This
function is called to invalidate a folio from the page cache (e.g., during an
`O_DIRECT` write or file truncation). It first attempts to unmap the folio from
any user page tables by calling `unmap_mapping_folio()`. If the folio is still
reported as mapped after this operation, it triggers the `BUG_ON`.
This indicates a severe memory management inconsistency, which is typically
caused by one of two scenarios:
1. **Mapcount Leak**: A reference counting bug where the folio's `_mapcount` was
incremented but not properly decremented (e.g., due to an error path bug, or a
race condition during page faults, `mremap`, or `munmap`). When
`unmap_mapping_folio()` clears all actual page table entries, the leaked
mapcount remains, causing `folio_mapped()` to incorrectly return true.
2. **VMA Tracking Bug**: The folio is mapped in a VMA that is not properly
linked to the file's `i_mmap` interval tree. Consequently,
`unmap_mapping_folio()` fails to find the VMA and does not clear the page table
entries, leaving the folio genuinely mapped.
**Security Impact:**
* **Exploitable**: Yes. A mapcount leak can theoretically be exploited by
overflowing the 32-bit `_mapcount` counter (by triggering the leak 2^32 times).
This causes the mapcount to wrap around, potentially leading to a Use-After-Free
(UAF) of the page, which can be leveraged for privilege escalation. A VMA
tracking bug can similarly lead to UAF or page table corruption.
* **Denial of Service**: Yes. The `BUG_ON` immediately halts the kernel,
allowing an attacker to easily crash the system.
* **Unprivileged Access**: Yes. While the stack trace shows the bug being
triggered via `blkdev_direct_write()` (writing to a block device), the
underlying invalidation path (`invalidate_inode_pages2_range()`) is also
reachable via `O_DIRECT` writes to normal files or other standard memory
management syscalls (`mmap`, `madvise`, etc.). Thus, an unprivileged user can
trigger the root cause and the subsequent crash. Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/08/11 00:56 |
action |
syz-repro-to-c-repro |
0m
Results:
|
| 2/1 |
2026/08/11 00:56 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/372d345ff176cc24833eddc5c254ca2d46535c5e]
|
| 3/1 |
2026/08/11 00:56 |
action |
kernel-builder |
26m
Results: map[KernelObj:/app/workdir/cache/build/0de4276a07ff6f59ced2c9c8d9e574253a90240e]
|
| 4/1 |
2026/08/11 01:22 |
action |
codesearch-prepare |
16m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/08/11 01:39 |
agent |
expert |
9m
Model:
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
Instruction: You are an experienced Linux kernel security engineer. Your task is to analyze given kernel bug report
and determine its security impact based on the following dimensions.
Use the provided tools to examine the source code, check for capability checks (e.g., capable(), ns_capable()),
and understand the nature of the bug. Analyze the given kernel build and configuration.
You can check the kernel config by grepping ".config" file; you can check kernel cmdline by grepping
".config" file for "CONFIG_CMDLINE=". Assume sysctl parameters have default values.
But analyze for the corresponding production build w/o debugging tools enabled (like KASAN, KMSAN, UBSAN).
Try different strategies when analyzing the bug:
- think of ways in which the vulnerable code is unreachable
- or the other way around: try to come up with different ideas of how an unprivileged user can reach the bug
If still unsure err on the side of the bug being non-exploitable/not-accessible.
In the final reply, provide a reasoning for your assessment.
Analysis dimensions:
* Exploitable:
Determine if the bug can result in memory corruption, elevated privileges, or an information leak.
Memory safety issues are almost always exploitable (KASAN or UBSAN reports for use-after-free, out-of-bounds;
refcounting issues, corrupted lists, etc). When kernel is crashing on a completely wild pointer access
(e.g. user-space address, or non-canonical address, but not on NULL or address corresponding to KASAN shadow
for NULL address), including both data accesses and control transfers, that also usually implies possibility
of exploitation. Such reports usually say "unable to handle kernel paging request".
Uses of uninitialized values detected by KMSAN may be exploitable b/c attacker frequently can affect uninit
values with spraying techniques. However, for these exploitability depends on how exactly the uninit value
is used in the code, and what it affects.
Information leaks are exploitable on their own and should be classified as such. A bug that copies kernel
memory contents to userspace (e.g. an out-of-bounds read whose result is returned to the caller, or
uninitialized stack/heap bytes written to a user buffer) is exploitable: it can reveal kernel pointer
values and defeat KASLR, expose sensitive data such as cryptographic keys or other processes' memory, and
serves as a necessary building block in most modern kernel privilege-escalation exploit chains. Do not classify
an information leak as non-exploitable solely because it does not directly cause a memory write or control-flow
hijack; the leak itself is the exploit primitive.
Think of what happens after the bug is triggered. Some bugs cause kernel panic and halt execution,
they are harder to exploit. For example, BUG reports halts the kernel. However, WARNING reports don't halt
execution in production builds. Debug bug detection tools (like KASAN, KMSAN, KCSAN, UBSAN) are also not enabled
in production builds, so attacker can freely exploit these bugs w/o being detected by these tools.
If you see an integer overflow, think how the overflowed value used later (if it's used as allocation size,
or an array index). If you see an out-of-bounds read, think if it's followed by an out-of-bounds write as well.
Some KCSAN data-races may be exploitable by skilled attackers as well. Think what data structures got corrupted
as the result of data races and how. However, note that kernel has lots of "benign" data races that don't lead
to any runtime misbehavior at all.
* Denial Of Service:
Determine if the bug can result in denial-of-service. Most bugs can, since they cause system crash,
hangs, deadlocks, or resource leaks. This is mostly applicable to WARNING bugs that won't cause system crash
in production. For these think what will be consequences of the violation of the kernel assumptions flagged
by the WARNING. In some cases the unexpected condition is also properly handled by the normal control flow
(e.g. with "if (WARN_ON(...))"), these won't cause denial-of-service. If the condition is not handled,
then it may or may not cause denial-of-service.
* Accessible From Unprivileged Processes:
Determine if the bug can be reached from a typical (non-root) user process that does NOT have any special capabilities
(like CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON) or access to device nodes restricted to root.
Assume that unprivileged_bpf_disabled=1, that is eBPF loading is not accessible. However, cBPF (classical BPF)
is still accessible to non-root processes.
Assume that user namespaces are not accessible, that is, the process cannot get the mentioned capabilities even
within a new user namespace (checked by ns_capable() function in the kernel sources).
* Accessible From User Namespaces:
Determine if the bug can be reached within a user-namespace where the process has all capabilities
(including CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON). Such capabilities are checked with ns_capable()
function in the kernel sources.
* VM Guest Trigger:
Determine if the bug can be triggered from the context of a typical KVM guest (e.g., set up by a QEMU VMM).
Consider accesses to standard Linux host paravirtualized features (virtio-blk, virtio-net, etc.),
and handling of VM exits in the KVM code.
* VM Host Trigger in The Confidential Computing Context:
Determine if the bug can be triggered in a confidential computing guest kernel from the context of a KVM host.
Consider access to standard Linux guest paravirtualized features (virtio-blk, virtio-net, etc.).
* Ethernet Network Trigger:
Determine if the bug can be triggered by processing ingress network Ethernet traffic, either directly (network stack)
or via drivers exposed to network data.
* Other Remote Trigger:
Determine if the bug can be triggered by processing remote traffic other than Ethernet (Wifi, Bluetooth, NFC, etc).
* Peripheral Trigger:
Determine if the bug can be triggered via an untrusted peripheral device that can be physically plugged
into a system, such as a USB device or a niche hardware driver handling external hardware inputs.
This is particularly important for mobile and desktop environments where users can plug in unknown devices.
* Malicious Filesystem Trigger:
Determine if the bug can be triggered by the kernel mounting and parsing a malicious filesystem image.
This is highly critical for Desktop and Mobile environments where external media or downloaded images
might be auto-mounted.
Don't make assumptions about the kernel source code (it may be different from what you assume it is).
Extensively use the provided code access tools (codesearch-*, git-*, grepper, etc)
to examine the actual source code, and confirm any assumptions.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The kernel bug report is:
------------[ cut here ]------------
kernel BUG at mm/truncate.c:632!
Oops: invalid opcode: 0000 [#1] SMP KASAN PTI
CPU: 0 UID: 0 PID: 13095 Comm: syz.4.2094 Not tainted syzkaller #0 PREEMPT_{RT,(full)}
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 07/24/2026
RIP: 0010:folio_unmap_invalidate+0x7ee/0x830 mm/truncate.c:632
Code: c6 c0 71 78 8b e8 a2 e9 1d ff 90 0f 0b e8 3a f4 bb ff 48 89 df 48 c7 c6 c0 71 78 8b e8 8b e9 1d ff 90 0f 0b e8 23 f4 bb ff 90 <0f> 0b e8 1b f4 bb ff 48 89 df 48 c7 c6 c0 71 78 8b e8 6c e9 1d ff
RSP: 0018:ffffc900064a7830 EFLAGS: 00010287
RAX: ffffffff8209d53d RBX: ffffea0000b443c0 RCX: 0000000000080000
RDX: ffffc90013401000 RSI: 0000000000038e38 RDI: 0000000000038e39
RBP: ffffea0000b443c8 R08: 0000000000000000 R09: 0000000000000000
R10: dffffc0000000000 R11: fffff9400016887f R12: 0000000000000001
R13: dffffc0000000000 R14: 1ffffd4000168879 R15: 1ffffd4000168878
FS: 00007f0ec44146c0(0000) GS:ffff888125c46000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f0ec3387ff8 CR3: 0000000061190000 CR4: 00000000003526f0
Call Trace:
<TASK>
invalidate_inode_pages2_range+0x62c/0xb30 mm/truncate.c:726
blkdev_direct_write+0x48/0x140 block/fops.c:690
blkdev_write_iter+0x530/0x700 block/fops.c:765
new_sync_write fs/read_write.c:595 [inline]
vfs_write+0x61e/0xbb0 fs/read_write.c:687
ksys_write+0x156/0x270 fs/read_write.c:739
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f0ec61fe0d9
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f0ec4414028 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007f0ec6486180 RCX: 00007f0ec61fe0d9
RDX: 00000000ffffffdb RSI: 0000000000000000 RDI: 0000000000000008
RBP: 00007f0ec6295024 R08: 0000000000000000 R09: 0000000000000000
R13: 00007f0ec6486218 R14: 00007f0ec6486180 R15: 00007ffc8fc250d8
</TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:folio_unmap_invalidate+0x7ee/0x830 mm/truncate.c:632
Code: c6 c0 71 78 8b e8 a2 e9 1d ff 90 0f 0b e8 3a f4 bb ff 48 89 df 48 c7 c6 c0 71 78 8b e8 8b e9 1d ff 90 0f 0b e8 23 f4 bb ff 90 <0f> 0b e8 1b f4 bb ff 48 89 df 48 c7 c6 c0 71 78 8b e8 6c e9 1d ff
RSP: 0018:ffffc900064a7830 EFLAGS: 00010287
RAX: ffffffff8209d53d RBX: ffffea0000b443c0 RCX: 0000000000080000
RDX: ffffc90013401000 RSI: 0000000000038e38 RDI: 0000000000038e39
RBP: ffffea0000b443c8 R08: 0000000000000000 R09: 0000000000000000
R10: dffffc0000000000 R11: fffff9400016887f R12: 0000000000000001
R13: dffffc0000000000 R14: 1ffffd4000168879 R15: 1ffffd4000168878
FS: 00007f0ec44146c0(0000) GS:ffff888125d46000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000000110c240003 CR3: 0000000061190000 CR4: 00000000003526f0
Reply: The bug is a `BUG_ON(folio_mapped(folio))` in `folio_unmap_invalidate()`. This function is called to invalidate a folio from the page cache (e.g., during an `O_DIRECT` write or file truncation). It first attempts to unmap the folio from any user page tables by calling `unmap_mapping_folio()`. If the folio is still reported as mapped after this operation, it triggers the `BUG_ON`.
This indicates a severe memory management inconsistency, which is typically caused by one of two scenarios:
1. **Mapcount Leak**: A reference counting bug where the folio's `_mapcount` was incremented but not properly decremented (e.g., due to an error path bug, or a race condition during page faults, `mremap`, or `munmap`). When `unmap_mapping_folio()` clears all actual page table entries, the leaked mapcount remains, causing `folio_mapped()` to incorrectly return true.
2. **VMA Tracking Bug**: The folio is mapped in a VMA that is not properly linked to the file's `i_mmap` interval tree. Consequently, `unmap_mapping_folio()` fails to find the VMA and does not clear the page table entries, leaving the folio genuinely mapped.
**Security Impact:**
* **Exploitable**: Yes. A mapcount leak can theoretically be exploited by overflowing the 32-bit `_mapcount` counter (by triggering the leak 2^32 times). This causes the mapcount to wrap around, potentially leading to a Use-After-Free (UAF) of the page, which can be leveraged for privilege escalation. A VMA tracking bug can similarly lead to UAF or page table corruption.
* **Denial of Service**: Yes. The `BUG_ON` immediately halts the kernel, allowing an attacker to easily crash the system.
* **Unprivileged Access**: Yes. While the stack trace shows the bug being triggered via `blkdev_direct_write()` (writing to a block device), the underlying invalidation path (`invalidate_inode_pages2_range()`) is also reachable via `O_DIRECT` writes to normal files or other standard memory management syscalls (`mmap`, `madvise`, etc.). Thus, an unprivileged user can trigger the root cause and the subsequent crash.
|
| 6/2 |
2026/08/11 01:39 |
llm |
expert |
0m
Model:
Tokens:
input: 7936
output: 33
thoughts: 53
Thoughts: Okay, let's see what we've got.
**Dissecting a Kernel Panic**
Alright, a kernel bug report has just landed on my desk: `kernel BUG at mm/truncate.c:632!` The specific function flagged is `folio_unmap_invalidate`. Right, `mm/truncate.c`... thatβs where the file truncation logic lives, which is always a fun area to debug. Let me jump right into that file and check out line 632. Time to dive in and see what's going wrong. My initial guess is that we have an issue with invalidating page mappings during a truncate, which is a common source of concurrency bugs, especially with complex filesystems. Let's get this looked at and see what we can find.
|
| 7/2 |
2026/08/11 01:39 |
tool |
read-file |
0m
Args: map[File:mm/truncate.c FirstLine:600 LineCount:50]
Results: map[Contents: 600: pgoff_t start, pgoff_t end)
601: {
602: return mapping_try_invalidate(mapping, start, end, NULL);
603: }
604: EXPORT_SYMBOL(invalidate_mapping_pages);
605:
606: static int folio_launder(struct address_space *mapping, struct folio *folio)
607: {
608: if (!folio_test_dirty(folio))
609: return 0;
610: if (folio->mapping != mapping || mapping->a_ops->launder_folio == NULL)
611: return 0;
612: return mapping->a_ops->launder_folio(folio);
613: }
614:
615: /*
616: * This is like mapping_evict_folio(), except it ignores the folio's
617: * refcount. We do this because invalidate_inode_pages2() needs stronger
618: * invalidation guarantees, and cannot afford to leave folios behind because
619: * shrink_folio_list() has a temp ref on them, or because they're transiently
620: * sitting in the folio_add_lru() caches.
621: */
622: int folio_unmap_invalidate(struct address_space *mapping, struct folio *folio,
623: gfp_t gfp)
624: {
625: void (*free_folio)(struct folio *);
626: int ret;
627:
628: VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio);
629:
630: if (folio_mapped(folio))
631: unmap_mapping_folio(folio);
632: BUG_ON(folio_mapped(folio));
633:
634: ret = folio_launder(mapping, folio);
635: if (ret)
636: return ret;
637: if (folio->mapping != mapping)
638: return -EBUSY;
639: if (!filemap_release_folio(folio, gfp))
640: return -EBUSY;
641:
642: spin_lock(&mapping->host->i_lock);
643: xa_lock_irq(&mapping->i_pages);
644: if (folio_test_dirty(folio))
645: goto failed;
646:
647: BUG_ON(folio_has_private(folio));
648: __filemap_remove_folio(folio, NULL);
649: xa_unlock_irq(&mapping->i_pages);
]
|
| 8/2 |
2026/08/11 01:39 |
llm |
expert |
0m
Model:
Tokens:
input: 8970
output: 33
thoughts: 242
Thoughts: **Debugging a Folio Unmapping Issue**
Okay, so I've hit a pretty nasty one. The kernel's triggering `BUG_ON(folio_mapped(folio))` right at `mm/truncate.c:632`. My initial thought is, this implies that `unmap_mapping_folio(folio)` isn't doing its job correctly; something's still mapped when it shouldn't be. That's a definite problem.
Time to trace this. Let's see... the call trace leads me through `invalidate_inode_pages2_range+0x62c/0xb30` in `mm/truncate.c:726`, then down to `blkdev_direct_write`, `blkdev_write_iter`, `new_sync_write`, `vfs_write` and finally `ksys_write`. Okay, so it seems like we have a direct I/O write path here with an issue in truncation. Not ideal, but at least I have a starting point.
Now, I'm going to have a closer look at that `mm/truncate.c:726` function. That's where the problem really starts. Let's see what's happening.
|
| 9/2 |
2026/08/11 01:39 |
tool |
read-file |
0m
Args: map[File:mm/truncate.c FirstLine:700 LineCount:50]
Results: map[Contents: 700:
701: if (xa_is_value(folio)) {
702: xa_has_values = true;
703: if (dax_mapping(mapping) &&
704: !dax_invalidate_mapping_entry_sync(mapping, indices[i]))
705: ret = -EBUSY;
706: continue;
707: }
708:
709: if (!did_range_unmap && folio_mapped(folio)) {
710: /*
711: * If folio is mapped, before taking its lock,
712: * zap the rest of the file in one hit.
713: */
714: unmap_mapping_pages(mapping, indices[i],
715: (1 + end - indices[i]), false);
716: did_range_unmap = 1;
717: }
718:
719: folio_lock(folio);
720: if (unlikely(folio->mapping != mapping)) {
721: folio_unlock(folio);
722: continue;
723: }
724: VM_BUG_ON_FOLIO(!folio_contains(folio, indices[i]), folio);
725: folio_wait_writeback(folio);
726: ret2 = folio_unmap_invalidate(mapping, folio, GFP_KERNEL);
727: if (ret2 < 0)
728: ret = ret2;
729: folio_unlock(folio);
730: }
731:
732: if (xa_has_values)
733: clear_shadow_entries(mapping, indices[0], indices[nr-1]);
734:
735: folio_batch_remove_exceptionals(&fbatch);
736: folio_batch_release(&fbatch);
737: cond_resched();
738: }
739: /*
740: * For DAX we invalidate page tables after invalidating page cache. We
741: * could invalidate page tables while invalidating each entry however
742: * that would be expensive. And doing range unmapping before doesn't
743: * work as we have no cheap way to find whether page cache entry didn't
744: * get remapped later.
745: */
746: if (dax_mapping(mapping)) {
747: unmap_mapping_pages(mapping, start, end - start + 1, false);
748: }
749: return ret;
]
|
| 10/2 |
2026/08/11 01:39 |
llm |
expert |
0m
Model:
Tokens:
input: 10258
output: 34
thoughts: 39
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm deep in the weeds of kernel filesystem code:
**Diving into Block Device Direct Writes**
Alright, so `invalidate_inode_pages2_range` is definitely getting called from `blkdev_direct_write`. That's interesting, but not entirely unexpected. Now, I need to see *why* and *how* this happens. Let's get that context. I need to understand the control flow here. Let me check the source code for the relevant function, `blkdev_direct_write`. Specifically, I'm thinking line 690 in `block/fops.c` should hold the key. That's usually where the meat of the function lies and where these page invalidation calls are triggered. From there, hopefully, I can reconstruct the whole picture of what's happening. I need to understand the relationship between direct I/O, page cache invalidation, and the block device layer in this particular scenario. Time to jump into the code and see what `block/fops.c:690` tells me.
|
| 11/2 |
2026/08/11 01:39 |
tool |
read-file |
0m
Args: map[File:block/fops.c FirstLine:670 LineCount:50]
Results: map[Contents: 670: filp->f_mode |= FMODE_HAS_METADATA;
671:
672: ret = bdev_open(bdev, mode, filp->private_data, NULL, filp);
673: if (ret)
674: blkdev_put_no_open(bdev);
675: return ret;
676: }
677:
678: static int blkdev_release(struct inode *inode, struct file *filp)
679: {
680: bdev_release(filp);
681: return 0;
682: }
683:
684: static ssize_t
685: blkdev_direct_write(struct kiocb *iocb, struct iov_iter *from)
686: {
687: size_t count = iov_iter_count(from);
688: ssize_t written;
689:
690: written = kiocb_invalidate_pages(iocb, count);
691: if (written) {
692: if (written == -EBUSY)
693: return 0;
694: return written;
695: }
696:
697: written = blkdev_direct_IO(iocb, from);
698: if (written > 0) {
699: kiocb_invalidate_post_direct_write(iocb, count);
700: iocb->ki_pos += written;
701: count -= written;
702: }
703: if (written != -EIOCBQUEUED)
704: iov_iter_revert(from, count - iov_iter_count(from));
705: return written;
706: }
707:
708: static ssize_t blkdev_buffered_write(struct kiocb *iocb, struct iov_iter *from)
709: {
710: return iomap_file_buffered_write(iocb, from, &blkdev_iomap_ops, NULL,
711: NULL);
712: }
713:
714: /*
715: * Write data to the block device. Only intended for the block device itself
716: * and the raw driver which basically is a fake block device.
717: *
718: * Does not take i_mutex for the write and thus is not for general purpose
719: * use.
]
|
| 12/2 |
2026/08/11 01:39 |
llm |
expert |
0m
Model:
Tokens:
input: 11310
output: 35
thoughts: 78
Thoughts: Okay, let's break this down.
**Investigating a Potential Page Invalidation Issue**
Right, so `kiocb_invalidate_pages` is calling `invalidate_inode_pages2_range`. Okay, standard procedure. The core of the problem seems to be this `BUG_ON(folio_mapped(folio))` check within `folio_unmap_invalidate`. That's where the kernel is asserting that a folio *shouldn't* be mapped at this point. The system's throwing a hard stop, meaning something fundamentally incorrect is happening in the page unmapping process.
The question then becomes: why would `unmap_mapping_folio` β the function responsible for actually unmapping the folio β *fail* to unmap the folio in the first place? It's supposed to handle this. So, the next logical step is to dive right into `unmap_mapping_folio` and dissect its logic. I need to understand the conditions under which it *doesn't* unmap the folio. What are the failure modes? What could be causing a folio to remain mapped even when the invalidation process is underway? Let's trace it and see where things might be going sideways.
|
| 13/2 |
2026/08/11 01:39 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/truncate.c Name:unmap_mapping_folio]
Results: map[SourceCode:4361: /**
4362: * unmap_mapping_folio() - Unmap single folio from processes.
4363: * @folio: The locked folio to be unmapped.
4364: *
4365: * Unmap this folio from any userspace process which still has it mmaped.
4366: * Typically, for efficiency, the range of nearby pages has already been
4367: * unmapped by unmap_mapping_pages() or unmap_mapping_range(). But once
4368: * truncation or invalidation holds the lock on a folio, it may find that
4369: * the page has been remapped again: and then uses unmap_mapping_folio()
4370: * to unmap it finally.
4371: */
4372: void unmap_mapping_folio(struct folio *folio)
4373: {
4374: struct address_space *mapping = folio->mapping;
4375: struct zap_details details = { };
4376: pgoff_t first_index;
4377: pgoff_t last_index;
4378:
4379: VM_BUG_ON(!folio_test_locked(folio));
4380:
4381: first_index = folio->index;
4382: last_index = folio_next_index(folio) - 1;
4383:
4384: details.skip_cows = true;
4385: details.single_folio = folio;
4386: details.zap_flags = ZAP_FLAG_DROP_MARKER;
4387:
4388: i_mmap_lock_read(mapping);
4389: if (unlikely(mapping_mapped(mapping)))
4390: unmap_mapping_range_tree(&mapping->i_mmap, first_index,
4391: last_index, &details);
4392: i_mmap_unlock_read(mapping);
4393: }
SourceFile:mm/memory.c]
|
| 14/2 |
2026/08/11 01:39 |
llm |
expert |
0m
Model:
Tokens:
input: 12249
output: 45
thoughts: 204
Thoughts: **Analyzing `unmap_mapping_folio` and Potential Mapping Persistence**
Okay, so I'm diving into `unmap_mapping_folio`. My understanding is that its primary function is to unmap a folio from any userspace process that currently has it memory-mapped. The core operation seems to be the call to `unmap_mapping_range_tree`, which I assume handles the granular unmapping within the tree structure.
Here's the puzzle: the code checks `folio_mapped(folio)` *after* this unmapping attempt. If this function is still true, the folio is still mapped, which shouldn't happen, or the unmapping failed. But wait, I need to remind myself that `unmap_mapping_folio` *specifically* targets the `mapping->i_mmap` area. Aha, thatβs key.
This means the folio could persist in another mapping entirely (perhaps due to page migration, or, crucially, an undiscovered bug in the mapping logic). Or, `unmap_mapping_range_tree` might simply not be able to find the folio due to an unexpected mapping mechanism.
I'm briefly considering the possibility of a long-term pinned page, but, that's accounted for because `folio_mapped` checks the _mapcount or _nr_pages_mapped fields, which cover the folio's mapping.
Given that these types of issues can be tricky, I'm going to do a quick search to see if there are any known bug reports or existing discussions related to this specific scenario. I want to check if this is a known issue before diving deeper into the source code.
|
| 15/2 |
2026/08/11 01:39 |
tool |
git-log |
0m
Args: map[CodeRegexp:BUG_ON\(folio_mapped\(folio\)\); Count:10 PathPrefix:mm/truncate.c]
Results: map[Output:4a9e23159fd3 mm/truncate: add folio_unmap_invalidate() helper
fae9bc4a9017 truncate: Convert invalidate_inode_pages2_range() to use a folio
]
|
| 16/2 |
2026/08/11 01:39 |
llm |
expert |
0m
Model:
Tokens:
input: 12910
output: 28
thoughts: 28
Thoughts: Okay, here's my take on those thoughts, framed as an expert diving into the subject matter:
**Delving into Memory Management Issues**
Alright, let's get down to brass tacks. I need to figure out what's going on with this memory mapping situation. The symptoms I'm seeing suggest there's a problem with how pages, specifically folios, are being unmapped and invalidated. I need to trace this specific issue, so the first thing to do is start hunting for relevant commits. I'll search the codebase for instances where we're touching `folio_unmap_invalidate`, as that's a direct signal of our target function being used. Then, because the core problem can also manifest, or be triggered, by other related behaviours, I should also look for places where `BUG_ON(folio_mapped(folio))` shows up. A `BUG_ON` is generally a sign of a critical check failing, and that specific check is a red flag, indicating an assertion violation related to whether a folio is mapped before we're doing something with it. I expect that a lot of those references will likely point directly at the problem, or at the code that is exposing the issue. With those two search queries, I should have a good starting point to reconstruct the series of events leading up to this. Then, I'll need to look at commit messages and diffs to understand the context and the fixes. Once I have the relevant commits, I can piece together the root cause.
|
| 17/2 |
2026/08/11 01:39 |
tool |
git-log |
0m
Args: map[Count:10 MessageRegexps:[folio_unmap_invalidate]]
Results: map[Output:ccaba7858219 Revert "f2fs: remove non-uptodate folio from the page cache in move_data_block"
615d9bb2ccad mm: call ->free_folio() directly in folio_unmap_invalidate()
25b065a744ff mm/filemap: use filemap_end_dropbehind() for read invalidation
095f627add86 mm/filemap: gate dropbehind invalidate on folio !dirty && !writeback
927289988068 mm/truncate: don't skip dirty page in folio_unmap_invalidate()
4a9e23159fd3 mm/truncate: add folio_unmap_invalidate() helper
]
|
| 18/2 |
2026/08/11 01:40 |
llm |
expert |
0m
Model:
Tokens:
input: 13470
output: 27
thoughts: 37
Thoughts: Here's a summary of my thinking about that kernel patch, framed as a first-person expert analysis:
**Analyzing a Kernel Page Invalidation Fix**
Okay, let's dive into this patch titled `927289988068 mm/truncate: don't skip dirty page in folio_unmap_invalidate()`. Immediately, the title hints at a potential correctness issue related to dirty page handling during file truncation, specifically within the `folio_unmap_invalidate()` function. That function is *crucial*, so this immediately grabs my attention. Itβs the linchpin for ensuring data consistency when a file's size changes significantly.
The concern likely revolves around a condition where the code might be *prematurely* skipping processing of a dirty page, potentially leading to data loss or corruption during truncation. Think about it: during a truncation operation, if a page is marked dirty (modified, but not yet written to disk) and the kernel prematurely skips invalidating and writing it back, the content modifications risk being lost. That's a classic race condition and potential data integrity violation.
I need to understand *why* this skip might be happening. What are the conditions that trigger the "don't skip dirty page" logic? Is it a misunderstanding of a flag or perhaps an optimization that backfired? Is it a subtle interaction with other memory management subsystems? My initial thought is it relates to a specific file system or a specific concurrency issue around writing. The commit message and surrounding code context will be critical to understanding the flaw here. I'm already mentally running through potential scenarios: the interplay of file mapping, page states, and the various locking mechanisms in the kernel. This is a critical area that deserves careful scrutiny, and I can't leave this unexamined. Let's see what the patch actually does.
|
| 19/2 |
2026/08/11 01:40 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 927289988068a65ccc168eda881ce60f8712707b
Author: Jingbo Xu <jefflexu@linux.alibaba.com>
Date: Tue Feb 18 20:02:09 2025 +0800
mm/truncate: don't skip dirty page in folio_unmap_invalidate()
... otherwise this is a behavior change for the previous callers of
invalidate_complete_folio2(), e.g. the page invalidation routine.
Fixes: 4a9e23159fd3 ("mm/truncate: add folio_unmap_invalidate() helper")
Signed-off-by: Jingbo Xu <jefflexu@linux.alibaba.com>
Link: https://lore.kernel.org/r/20250218120209.88093-3-jefflexu@linux.alibaba.com
Signed-off-by: Christian Brauner <brauner@kernel.org>
diff --git a/mm/truncate.c b/mm/truncate.c
index e2e115adfbc5..76d8fcd89bd0 100644
--- a/mm/truncate.c
+++ b/mm/truncate.c
@@ -548,8 +548,6 @@ int folio_unmap_invalidate(struct address_space *mapping, struct folio *folio,
VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio);
- if (folio_test_dirty(folio))
- return 0;
if (folio_mapped(folio))
unmap_mapping_folio(folio);
BUG_ON(folio_mapped(folio));
]
|
| 20/2 |
2026/08/11 01:40 |
llm |
expert |
0m
Model:
Tokens:
input: 14373
output: 26
thoughts: 43
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm the expert and considering how I'd approach it:
**Digging into a Potential Regression**
Right, let's see. My gut tells me something's gone wrong with `folio_unmap_invalidate`. It's recently introduced in `4a9e23159fd3`, so that's the obvious place to start. I need to take a look at that commit.
|
| 21/2 |
2026/08/11 01:40 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 4a9e23159fd37677efc0c2c53e3b45a5d260a90a
Author: Jens Axboe <axboe@kernel.dk>
Date: Fri Dec 20 08:47:44 2024 -0700
mm/truncate: add folio_unmap_invalidate() helper
Add a folio_unmap_invalidate() helper, which unmaps and invalidates a
given folio. The caller must already have locked the folio. Embed the
old invalidate_complete_folio2() helper in there as well, as nobody else
calls it.
Use this new helper in invalidate_inode_pages2_range(), rather than
duplicate the code there.
In preparation for using this elsewhere as well, have it take a gfp_t mask
rather than assume GFP_KERNEL is the right choice. This bubbles back to
invalidate_complete_folio2() as well.
Link: https://lkml.kernel.org/r/20241220154831.1086649-7-axboe@kernel.dk
Signed-off-by: Jens Axboe <axboe@kernel.dk>
Cc: Brian Foster <bfoster@redhat.com>
Cc: Chris Mason <clm@meta.com>
Cc: Christoph Hellwig <hch@lst.de>
Cc: Johannes Weiner <hannes@cmpxchg.org>
Cc: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
diff --git a/mm/internal.h b/mm/internal.h
index 4d4028d74e5d..109ef30fee11 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -392,6 +392,8 @@ void unmap_page_range(struct mmu_gather *tlb,
struct vm_area_struct *vma,
unsigned long addr, unsigned long end,
struct zap_details *details);
+int folio_unmap_invalidate(struct address_space *mapping, struct folio *folio,
+ gfp_t gfp);
void page_cache_ra_order(struct readahead_control *, struct file_ra_state *,
unsigned int order);
diff --git a/mm/truncate.c b/mm/truncate.c
index 7c304d2f0052..e2e115adfbc5 100644
--- a/mm/truncate.c
+++ b/mm/truncate.c
@@ -525,6 +525,15 @@ unsigned long invalidate_mapping_pages(struct address_space *mapping,
}
EXPORT_SYMBOL(invalidate_mapping_pages);
+static int folio_launder(struct address_space *mapping, struct folio *folio)
+{
+ if (!folio_test_dirty(folio))
+ return 0;
+ if (folio->mapping != mapping || mapping->a_ops->launder_folio == NULL)
+ return 0;
+ return mapping->a_ops->launder_folio(folio);
+}
+
/*
* This is like mapping_evict_folio(), except it ignores the folio's
* refcount. We do this because invalidate_inode_pages2() needs stronger
@@ -532,14 +541,26 @@ EXPORT_SYMBOL(invalidate_mapping_pages);
* shrink_folio_list() has a temp ref on them, or because they're transiently
* sitting in the folio_add_lru() caches.
*/
-static int invalidate_complete_folio2(struct address_space *mapping,
- struct folio *folio)
+int folio_unmap_invalidate(struct address_space *mapping, struct folio *folio,
+ gfp_t gfp)
{
- if (folio->mapping != mapping)
- return 0;
+ int ret;
+
+ VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio);
- if (!filemap_release_folio(folio, GFP_KERNEL))
+ if (folio_test_dirty(folio))
return 0;
+ if (folio_mapped(folio))
+ unmap_mapping_folio(folio);
+ BUG_ON(folio_mapped(folio));
+
+ ret = folio_launder(mapping, folio);
+ if (ret)
+ return ret;
+ if (folio->mapping != mapping)
+ return -EBUSY;
+ if (!filemap_release_folio(folio, gfp))
+ return -EBUSY;
spin_lock(&mapping->host->i_lock);
xa_lock_irq(&mapping->i_pages);
@@ -558,16 +579,7 @@ static int invalidate_complete_folio2(struct address_space *mapping,
failed:
xa_unlock_irq(&mapping->i_pages);
spin_unlock(&mapping->host->i_lock);
- return 0;
-}
-
-static int folio_launder(struct address_space *mapping, struct folio *folio)
-{
- if (!folio_test_dirty(folio))
- return 0;
- if (folio->mapping != mapping || mapping->a_ops->launder_folio == NULL)
- return 0;
- return mapping->a_ops->launder_folio(folio);
+ return -EBUSY;
}
/**
@@ -631,16 +643,7 @@ int invalidate_inode_pages2_range(struct address_space *mapping,
}
VM_BUG_ON_FOLIO(!folio_contains(folio, indices[i]), folio);
folio_wait_writeback(folio);
-
- if (folio_mapped(folio))
- unmap_mapping_folio(folio);
- BUG_ON(folio_mapped(folio));
-
- ret2 = folio_launder(mapping, folio);
- if (ret2 == 0) {
- if (!invalidate_complete_folio2(mapping, folio))
- ret2 = -EBUSY;
- }
+ ret2 = folio_unmap_invalidate(mapping, folio, GFP_KERNEL);
if (ret2 < 0)
ret = ret2;
folio_unlock(folio);
]
|
| 22/2 |
2026/08/11 01:40 |
llm |
expert |
0m
Model:
Tokens:
input: 16211
output: 34
thoughts: 442
Thoughts: **Analyzing a Recent Kernel Patch**
Okay, so `927289988068` just removed that `if (folio_test_dirty(folio)) return 0;` check from `folio_unmap_invalidate`. My initial reaction is, wait a second, isn't `folio_unmap_invalidate` supposed to `BUG_ON(folio_mapped(folio))`? And `unmap_mapping_folio` *should* only be unmapping from `mapping->i_mmap`. So what happens if the folio is mapped *elsewhere*?
Thinking about it, `unmap_mapping_folio` operates on `mapping->i_mmap`. If the folio is also mapped in another mapping, `folio_mapped` would still be true. But how could a folio be simultaneously mapped in two distinct mappings? Normally, that's a no-go, a folio lives under one mapping.
Aha, the direct I/O write scenario. What about `mmap`'ing a block device, and then writing directly to it? In that case, `folio->mapping` would point to the block device's mapping, not the file's. But what if the file is mapped, and the backing storage is a block device, and direct I/O updates the block device?
Right, `blkdev_direct_write` eventually calls `kiocb_invalidate_pages(iocb, count)`, which then calls `invalidate_inode_pages2_range` on the block device's mapping. Okay, so if the folio is *part* of the block device's mapping, `unmap_mapping_folio` would successfully unmap it there. But what about user-space interaction? What if a user process pins the folio somehow?
Wait, `folio_mapped` only cares about the page table mappings. Okay, so the user-space interaction is a bit of a red herring, at least directly related to this function.
Hmm, could `unmap_mapping_folio` somehow fail to unmap the folio from `mapping->i_mmap`? Time to re-examine that function...
|
| 23/2 |
2026/08/11 01:40 |
tool |
read-file |
0m
Args: map[File:mm/memory.c FirstLine:4360 LineCount:40]
Results: map[Contents:4360:
4361: /**
4362: * unmap_mapping_folio() - Unmap single folio from processes.
4363: * @folio: The locked folio to be unmapped.
4364: *
4365: * Unmap this folio from any userspace process which still has it mmaped.
4366: * Typically, for efficiency, the range of nearby pages has already been
4367: * unmapped by unmap_mapping_pages() or unmap_mapping_range(). But once
4368: * truncation or invalidation holds the lock on a folio, it may find that
4369: * the page has been remapped again: and then uses unmap_mapping_folio()
4370: * to unmap it finally.
4371: */
4372: void unmap_mapping_folio(struct folio *folio)
4373: {
4374: struct address_space *mapping = folio->mapping;
4375: struct zap_details details = { };
4376: pgoff_t first_index;
4377: pgoff_t last_index;
4378:
4379: VM_BUG_ON(!folio_test_locked(folio));
4380:
4381: first_index = folio->index;
4382: last_index = folio_next_index(folio) - 1;
4383:
4384: details.skip_cows = true;
4385: details.single_folio = folio;
4386: details.zap_flags = ZAP_FLAG_DROP_MARKER;
4387:
4388: i_mmap_lock_read(mapping);
4389: if (unlikely(mapping_mapped(mapping)))
4390: unmap_mapping_range_tree(&mapping->i_mmap, first_index,
4391: last_index, &details);
4392: i_mmap_unlock_read(mapping);
4393: }
4394:
4395: /**
4396: * unmap_mapping_pages() - Unmap pages from processes.
4397: * @mapping: The address space containing pages to be unmapped.
4398: * @start: Index of first page to be unmapped.
4399: * @nr: Number of pages to be unmapped. 0 to unmap to end of file.
]
|
| 24/2 |
2026/08/11 01:40 |
llm |
expert |
0m
Model:
Tokens:
input: 17816
output: 34
thoughts: 190
Thoughts: **Analyzing COW Page Handling in Memory Management**
Okay, so I'm digging into this code, specifically around how we're dealing with Copy-on-Write (COW) pages. If `details.skip_cows` is set to `true`, the intention is to skip COW pages. But, a nagging thought keeps popping up. COW pages *are* anonymous pages, they're not directly tied to the original `folio->mapping` anymore, are they? That initial connection is severed during the COW operation.
Then I remind myself: a process maps a file privately (`MAP_PRIVATE`) and writes to the memory. This creates a COW page. The new page is anonymous, yes. But the *original* file page, is that still in the page cache? And might other processes still have it mapped?
Now, if `details.skip_cows` causes `unmap_mapping_range_tree` to skip COW pages, then what *exactly* is being skipped? It can't be the original page, because the COW has already happened. The original page in the page cache is not the COW page. So, this `skip_cows` logic...is it actually skipping Page Table Entries (PTEs) that *don't* map to the original file page, instead?
To clarify my understanding, I need to zoom in and examine `zap_pte_range`. That function is where `details.single_folio` and, crucially, `details.skip_cows` are being used. Peeking at the context of `zap_pte_range` will give me the clarity I need.
|
| 25/2 |
2026/08/11 01:40 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/memory.c Name:zap_pte_range]
Results: map[SourceCode:1900: static unsigned long zap_pte_range(struct mmu_gather *tlb,
1901: struct vm_area_struct *vma, pmd_t *pmd,
1902: unsigned long addr, unsigned long end,
1903: struct zap_details *details)
1904: {
1905: bool can_reclaim_pt = pte_table_reclaim_possible(addr, end, details);
1906: bool force_flush = false, force_break = false;
1907: struct mm_struct *mm = tlb->mm;
1908: int rss[NR_MM_COUNTERS];
1909: spinlock_t *ptl;
1910: pte_t *start_pte;
1911: pte_t *pte;
1912: pmd_t pmdval;
1913: unsigned long start = addr;
1914: bool direct_reclaim = true;
1915: int nr;
1916:
1917: retry:
1918: tlb_change_page_size(tlb, PAGE_SIZE);
1919: init_rss_vec(rss);
1920: start_pte = pte = pte_offset_map_lock(mm, pmd, addr, &ptl);
1921: if (!pte)
1922: return addr;
1923:
1924: flush_tlb_batched_pending(mm);
1925: lazy_mmu_mode_enable();
1926: do {
1927: bool any_skipped = false;
1928:
1929: if (need_resched()) {
1930: direct_reclaim = false;
1931: break;
1932: }
1933:
1934: nr = do_zap_pte_range(tlb, vma, pte, addr, end, details, rss,
1935: &force_flush, &force_break, &any_skipped);
1936: if (any_skipped)
1937: can_reclaim_pt = false;
1938: if (unlikely(force_break)) {
1939: addr += nr * PAGE_SIZE;
1940: direct_reclaim = false;
1941: break;
1942: }
1943: } while (pte += nr, addr += PAGE_SIZE * nr, addr != end);
1944:
1945: /*
1946: * Fast path: try to hold the pmd lock and unmap the PTE page.
1947: *
1948: * If the pte lock was released midway (retry case), or if the attempt
1949: * to hold the pmd lock failed, then we need to recheck all pte entries
1950: * to ensure they are still none, thereby preventing the pte entries
1951: * from being repopulated by another thread.
1952: */
1953: if (can_reclaim_pt && direct_reclaim && addr == end)
1954: direct_reclaim = zap_empty_pte_table(mm, pmd, ptl, &pmdval);
1955:
1956: add_mm_rss_vec(mm, rss);
1957: lazy_mmu_mode_disable();
1958:
1959: /* Do the actual TLB flush before dropping ptl */
1960: if (force_flush) {
1961: tlb_flush_mmu_tlbonly(tlb);
1962: tlb_flush_rmaps(tlb, vma);
1963: }
1964: pte_unmap_unlock(start_pte, ptl);
1965:
1966: /*
1967: * If we forced a TLB flush (either due to running out of
1968: * batch buffers or because we needed to flush dirty TLB
1969: * entries before releasing the ptl), free the batched
1970: * memory too. Come back again if we didn't do everything.
1971: */
1972: if (force_flush)
1973: tlb_flush_mmu(tlb);
1974:
1975: if (addr != end) {
1976: cond_resched();
1977: force_flush = false;
1978: force_break = false;
1979: goto retry;
1980: }
1981:
1982: if (can_reclaim_pt) {
1983: if (direct_reclaim || zap_pte_table_if_empty(mm, pmd, start, &pmdval)) {
1984: pte_free_tlb(tlb, pmd_pgtable(pmdval), start);
1985: mm_dec_nr_ptes(mm);
1986: }
1987: }
1988:
1989: return addr;
1990: }
SourceFile:mm/memory.c]
|
| 26/2 |
2026/08/11 01:40 |
llm |
expert |
0m
Model:
Tokens:
input: 19808
output: 36
thoughts: 13
Thoughts: Okay, here's my attempt at summarizing the thought process, tailored for an expert audience, and written in the first person:
**Diving into `do_zap_pte_range`**
Alright, time to dig into this `do_zap_pte_range` function. Immediately, my mind is racing; I need to quickly grasp the core purpose here. From its name, I'm assuming this is the workhorse for unmapping a range of Page Table Entries (PTEs). Probably involves invalidating cached entries, clearing permissions, and potentially signaling the hardware. The details are, of course, where the devils lie!
My first instincts are to think about the typical scenarios this is used in, how its related functions are used (particularly `zap_pte_range`), and its connections to the VM infrastructure. Is this for a specific MMU or architecture? What about NUMA considerations or memory hotplug? Is it locking-heavy, or are there careful optimizations to avoid contention during range clearing? I'll probably start by looking at where it's called from to establish a good context for its purpose. What other functions does it depend on?
Next up, I'll need to figure out what data structures it directly touches, such as page tables, virtual memory areas, and process control structures. The critical path of this code will determine the performance implications. The implementation will likely involve a loop iterating through the range, and it might have conditional logic based on various flags or memory attributes. I need to understand its handling of present bits, dirty bits, accessed bits, and any potential side effects of modifying these fields. Itβs also important that I get the meaning of each parameter passed to this function.
Ultimately, I want to understand how it ensures memory safety and consistency, especially in a multithreaded environment. How is it protecting against race conditions with other threads that may be accessing the same memory region? Does it employ a specific memory barrier model?
I'm ready to dive into the code!
|
| 27/2 |
2026/08/11 01:40 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/memory.c Name:do_zap_pte_range]
Results: map[SourceCode:1806: static inline int do_zap_pte_range(struct mmu_gather *tlb,
1807: struct vm_area_struct *vma, pte_t *pte,
1808: unsigned long addr, unsigned long end,
1809: struct zap_details *details, int *rss,
1810: bool *force_flush, bool *force_break,
1811: bool *any_skipped)
1812: {
1813: pte_t ptent = ptep_get(pte);
1814: int max_nr = (end - addr) / PAGE_SIZE;
1815: int nr = 0;
1816:
1817: /* Skip all consecutive none ptes */
1818: if (pte_none(ptent)) {
1819: for (nr = 1; nr < max_nr; nr++) {
1820: ptent = ptep_get(pte + nr);
1821: if (!pte_none(ptent))
1822: break;
1823: }
1824: max_nr -= nr;
1825: if (!max_nr)
1826: return nr;
1827: pte += nr;
1828: addr += nr * PAGE_SIZE;
1829: }
1830:
1831: if (pte_present(ptent))
1832: nr += zap_present_ptes(tlb, vma, pte, ptent, max_nr, addr,
1833: details, rss, force_flush, force_break,
1834: any_skipped);
1835: else
1836: nr += zap_nonpresent_ptes(tlb, vma, pte, ptent, max_nr, addr,
1837: details, rss, any_skipped);
1838:
1839: return nr;
1840: }
SourceFile:mm/memory.c]
|
| 28/2 |
2026/08/11 01:40 |
llm |
expert |
0m
Model:
Tokens:
input: 20846
output: 35
thoughts: 12
Thoughts: Okay, here's my attempt at summarizing that thought process, tailored for an expert audience:
**Checking `zap_present_ptes`**
Alright, let's dive in. First things first, I need to verify the status of `zap_present_ptes`. This likely refers to a crucial component or process, perhaps a feature flag, a data structure, or some critical configuration setting related to ZAP (likely referring to ZAP, the security testing tool), and present PTEs (which I'm interpreting as either Present Threat Evaluations, or maybe even Present Test Environments - depends on the context). My initial thought is to confirm its state β is it enabled, disabled, configured as expected? Understanding this will dictate how to proceed.
|
| 29/2 |
2026/08/11 01:40 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/memory.c Name:zap_present_ptes]
Results: map[SourceCode:1683: /*
1684: * Zap or skip at least one present PTE, trying to batch-process subsequent
1685: * PTEs that map consecutive pages of the same folio.
1686: *
1687: * Returns the number of processed (skipped or zapped) PTEs (at least 1).
1688: */
1689: static inline int zap_present_ptes(struct mmu_gather *tlb,
1690: struct vm_area_struct *vma, pte_t *pte, pte_t ptent,
1691: unsigned int max_nr, unsigned long addr,
1692: struct zap_details *details, int *rss, bool *force_flush,
1693: bool *force_break, bool *any_skipped)
1694: {
1695: struct mm_struct *mm = tlb->mm;
1696: struct folio *folio;
1697: struct page *page;
1698: int nr;
1699:
1700: page = vm_normal_page(vma, addr, ptent);
1701: if (!page) {
1702: /* We don't need up-to-date accessed/dirty bits. */
1703: ptep_get_and_clear_full(mm, addr, pte, tlb->fullmm);
1704: arch_check_zapped_pte(vma, ptent);
1705: tlb_remove_tlb_entry(tlb, pte, addr);
1706: if (userfaultfd_pte_wp(vma, ptent))
1707: *any_skipped = zap_install_uffd_wp_if_needed(vma, addr,
1708: pte, 1, details, ptent);
1709: ksm_might_unmap_zero_page(mm, ptent);
1710: return 1;
1711: }
1712:
1713: folio = page_folio(page);
1714: if (unlikely(!should_zap_folio(details, folio))) {
1715: *any_skipped = true;
1716: return 1;
1717: }
1718:
1719: /*
1720: * Make sure that the common "small folio" case is as fast as possible
1721: * by keeping the batching logic separate.
1722: */
1723: if (unlikely(folio_test_large(folio) && max_nr != 1)) {
1724: nr = folio_pte_batch(folio, pte, ptent, max_nr);
1725: zap_present_folio_ptes(tlb, vma, folio, page, pte, ptent, nr,
1726: addr, details, rss, force_flush,
1727: force_break, any_skipped);
1728: return nr;
1729: }
1730: zap_present_folio_ptes(tlb, vma, folio, page, pte, ptent, 1, addr,
1731: details, rss, force_flush, force_break, any_skipped);
1732: return 1;
1733: }
SourceFile:mm/memory.c]
|
| 30/2 |
2026/08/11 01:40 |
llm |
expert |
0m
Model:
Tokens:
input: 21966
output: 34
thoughts: 11
Thoughts: Okay, here's my attempt at summarizing that thought process, keeping in mind I'm speaking to a fellow expert:
**Checking `should_zap_folio`**
Alright, let's get down to brass tacks. First thing's first, I need to verify that `should_zap_folio`. This is where the core logic of this operation hinges. I need to make *absolutely sure* that condition is met before moving forward. There's no room for assumptions here. Is there a bug in it? Did I forget a critical edge case in the last update? Now is the time to check it. I'm going to quickly review the context around that function call. I need to understand its inputs, the dependencies it relies upon, and what the consequences of a `True` or `False` return would actually be. Time to dive deep.
|
| 31/2 |
2026/08/11 01:40 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/memory.c Name:should_zap_folio]
Results: map[SourceCode:1582: /* Decides whether we should zap this folio with the folio pointer specified */
1583: static inline bool should_zap_folio(struct zap_details *details,
1584: struct folio *folio)
1585: {
1586: /* If we can make a decision without *folio.. */
1587: if (should_zap_cows(details))
1588: return true;
1589:
1590: /* Otherwise we should only zap non-anon folios */
1591: return !folio_test_anon(folio);
1592: }
SourceFile:mm/memory.c]
|
| 32/2 |
2026/08/11 01:40 |
llm |
expert |
0m
Model:
Tokens:
input: 22384
output: 35
thoughts: 12
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm an expert in the field and considering how I'd approach it:
**Analyzing `should_zap_cows`**
Right, so I'm looking at this variable, `should_zap_cows`. The name itself is pretty clear; it's a boolean, a flag, a signal to execute some action on cows. The "zap" is likely a metaphor or a specific technical term within this system, but the core concept is straightforward: it controls whether or not some process that affects cows is going to run.
Now, before I can really dig into what's happening, I need context. What is this `should_zap_cows` tied to? Where is it defined? How is it being used? Is it part of a larger conditional statement? What triggers it? Without that information, it's just a variable name.
My first thought would be, is this a hard-coded value, read from a configuration file, determined by the system based on other inputs, or controlled by a user? Is it possible for it to be True at the wrong time? I need to understand the control flow, because an error here could lead to some significant repercussions, especially if the "zap" is something drastic like, I don't know, a farm automation tool that mistakenly sends electrical shocks to the livestock. This has some serious ethical implications.
I would need to examine the entire function/module/script where this variable lives. I need to trace its origin and its eventual use to determine what is going on. I need to ensure there are adequate error checks and safeguards around this variable.
In short, I'm already mentally running through the debugging steps and potential scenarios, even before seeing the actual code.
|
| 33/2 |
2026/08/11 01:40 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/memory.c Name:should_zap_cows]
Results: map[SourceCode:1569: /* Whether we should zap all COWed (private) pages too */
1570: static inline bool should_zap_cows(struct zap_details *details)
1571: {
1572: /* By default, zap all pages */
1573: if (!details)
1574: return true;
1575:
1576: VM_WARN_ON_ONCE(details->skip_cows && details->reclaim_pt);
1577:
1578: /* Or, we zap COWed pages only if the caller wants to */
1579: return !details->skip_cows;
1580: }
SourceFile:mm/memory.c]
|
| 34/2 |
2026/08/11 01:40 |
llm |
expert |
0m
Model:
Tokens:
input: 23013
output: 25
thoughts: 142
Thoughts: **Analyzing the Folio Zapping Logic**
Okay, let me break this down. First, I see that `should_zap_folio` is dependent on the output of `should_zap_cows` when dealing with the `details` structure. If `details->skip_cows` is set to true, it means `should_zap_cows` returns false. Then, `should_zap_folio` returns the logical NOT of `folio_test_anon(folio)`. This implies, when skipping cows, we're skipping anonymous folios.
Now, I'm thinking about the flow⦠`unmap_mapping_folio` is setting `details.single_folio = folio`. That's a key detail! But, hang on... `should_zap_folio` doesn't even use `details->single_folio`! It doesn't seem like it's taking that information into account at all.
This raises a red flag. Where is the potential single folio consideration being checked within the zap process? I need to trace the usage of `details->single_folio` to understand its role. This is a bit disjointed; I need to find the point where that value is actually considered to understand its significance.
|
| 35/2 |
2026/08/11 01:41 |
tool |
grepper |
0m
Args: map[Expression:single_folio PathPrefix:mm/]
Results: map[Output:mm/memory.c=1992=static inline unsigned long zap_pmd_range(struct mmu_gather *tlb,
--
mm/memory.c-2010- /* fall through */
mm/memory.c:2011: } else if (details && details->single_folio &&
mm/memory.c:2012: folio_test_pmd_mappable(details->single_folio) &&
mm/memory.c-2013- next - addr == HPAGE_PMD_SIZE && pmd_none(*pmd)) {
--
mm/memory.c=4372=void unmap_mapping_folio(struct folio *folio)
--
mm/memory.c-4384- details.skip_cows = true;
mm/memory.c:4385: details.single_folio = folio;
mm/memory.c-4386- details.zap_flags = ZAP_FLAG_DROP_MARKER;
]
|
| 36/2 |
2026/08/11 01:41 |
llm |
expert |
0m
Model:
Tokens:
input: 23698
output: 35
thoughts: 45
Thoughts: Okay, let's see...
**Analyzing `details->single_folio` Usage**
Hmm, this is interesting. I'm taking a close look at `details->single_folio`, and it seems like the only place this flag is even used is within `zap_pmd_range`. I really should verify that thought though, just to be thorough. Okay, so my next step is clear. Let's delve into that function, `zap_pmd_range`, and double-check exactly how that flag is being used there. It's crucial to understand the context.
|
| 37/2 |
2026/08/11 01:41 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/memory.c Name:zap_pmd_range]
Results: map[SourceCode:1992: static inline unsigned long zap_pmd_range(struct mmu_gather *tlb,
1993: struct vm_area_struct *vma, pud_t *pud,
1994: unsigned long addr, unsigned long end,
1995: struct zap_details *details)
1996: {
1997: pmd_t *pmd;
1998: unsigned long next;
1999:
2000: pmd = pmd_offset(pud, addr);
2001: do {
2002: next = pmd_addr_end(addr, end);
2003: if (pmd_is_huge(*pmd)) {
2004: if (next - addr != HPAGE_PMD_SIZE)
2005: __split_huge_pmd(vma, pmd, addr, false);
2006: else if (zap_huge_pmd(tlb, vma, pmd, addr)) {
2007: addr = next;
2008: continue;
2009: }
2010: /* fall through */
2011: } else if (details && details->single_folio &&
2012: folio_test_pmd_mappable(details->single_folio) &&
2013: next - addr == HPAGE_PMD_SIZE && pmd_none(*pmd)) {
2014: sync_with_folio_pmd_zap(tlb->mm, pmd);
2015: }
2016: if (pmd_none(*pmd)) {
2017: addr = next;
2018: continue;
2019: }
2020: addr = zap_pte_range(tlb, vma, pmd, addr, next, details);
2021: if (addr != next)
2022: pmd--;
2023: } while (pmd++, cond_resched(), addr != end);
2024:
2025: return addr;
2026: }
SourceFile:mm/memory.c]
|
| 38/2 |
2026/08/11 01:41 |
llm |
expert |
0m
Model:
Tokens:
input: 24487
output: 37
thoughts: 35
Thoughts: Okay, let's see. My first thought is: **Hold on a second, that `details->single_folio` variable isn't used to narrow down which Page Table Entries (PTEs) get zapped?** I could have sworn I saw it in that role before, and I'm a bit surprised. This could change the behavior of the procedure I'm working with considerably, if I misread its purpose before.
Alright, let's not jump to conclusions. Time to verify. I need to take a look at `zap_present_folio_ptes` to confirm that the filtering is happening somewhere else, or perhaps not at all. Gotta make sure I understand the logic completely before I make any modifications based on this initial hunch. Let's see what's actually going on in that function.
|
| 39/2 |
2026/08/11 01:41 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/memory.c Name:zap_present_folio_ptes]
Results: map[SourceCode:1638: static __always_inline void zap_present_folio_ptes(struct mmu_gather *tlb,
1639: struct vm_area_struct *vma, struct folio *folio,
1640: struct page *page, pte_t *pte, pte_t ptent, unsigned int nr,
1641: unsigned long addr, struct zap_details *details, int *rss,
1642: bool *force_flush, bool *force_break, bool *any_skipped)
1643: {
1644: struct mm_struct *mm = tlb->mm;
1645: bool delay_rmap = false;
1646:
1647: if (!folio_test_anon(folio)) {
1648: ptent = get_and_clear_full_ptes(mm, addr, pte, nr, tlb->fullmm);
1649: if (pte_dirty(ptent)) {
1650: folio_mark_dirty(folio);
1651: if (tlb_delay_rmap(tlb)) {
1652: delay_rmap = true;
1653: *force_flush = true;
1654: }
1655: }
1656: if (pte_young(ptent) && likely(vma_has_recency(vma)))
1657: folio_mark_accessed(folio);
1658: rss[mm_counter(folio)] -= nr;
1659: } else {
1660: /* We don't need up-to-date accessed/dirty bits. */
1661: clear_full_ptes(mm, addr, pte, nr, tlb->fullmm);
1662: rss[MM_ANONPAGES] -= nr;
1663: }
1664: /* Checking a single PTE in a batch is sufficient. */
1665: arch_check_zapped_pte(vma, ptent);
1666: tlb_remove_tlb_entries(tlb, pte, nr, addr);
1667: if (unlikely(userfaultfd_pte_wp(vma, ptent)))
1668: *any_skipped = zap_install_uffd_wp_if_needed(vma, addr, pte,
1669: nr, details, ptent);
1670:
1671: if (!delay_rmap) {
1672: folio_remove_rmap_ptes(folio, page, nr, vma);
1673:
1674: if (unlikely(folio_mapcount(folio) < 0))
1675: print_bad_pte(vma, addr, ptent, page);
1676: }
1677: if (unlikely(__tlb_remove_folio_pages(tlb, page, nr, delay_rmap))) {
1678: *force_flush = true;
1679: *force_break = true;
1680: }
1681: }
SourceFile:mm/memory.c]
|
| 40/2 |
2026/08/11 01:41 |
llm |
expert |
0m
Model:
Tokens:
input: 25566
output: 26
thoughts: 1140
Thoughts: **Analyzing Potential Folio Mapping Discrepancies in `unmap_mapping_folio`**
Okay, so I'm diving into this `unmap_mapping_folio` function. The call stack reveals a pretty standard unmapping process: it traverses through VMAs mapped to the file, zapping page table entries (PTEs) within the specified range. The core question revolves around what happens when the VMA's backing folio isn't the one we *think* it should be.
My initial thought is, if `details->skip_cows` is true, then anonymous folios are skipped, but what if a VMA maps to a *different* folio at the same file offset? That's the crux. `zap_present_ptes` will still zap it, because it only checks if the folio is *not* anonymous and doesn't verify if it's our target. That zap will decrease the mapcount of this 'other' folio. But my target folio's mapcount won't be changed, and `folio_mapped` should stay true.
So, how can a VMA map a different folio to the same offset? The initial thought is page cache replacement. But `unmap_mapping_folio` is called with the folio locked and in the page cache, which should be protected from replacement.
Okay, let's explore scenarios where a folio might be mapped in a VMA *outside* of the expected `mapping->i_mmap`. This is where it gets interesting.
1. **Migration:** Nope, migration should unmap and remap correctly.
2. **`vm_insert_page` or `remap_pfn_range`:** Possibly, but these are low level operations.
3. **Swap Cache Folio:** Unlikely, but could it somehow be?
4. **Mapped in a *different* mapping?** This is where I start to see a path.
Okay, I'm getting somewhere now. The bug report mentions `invalidate_inode_pages2_range` is called from `blkdev_direct_write`. So, we're talking about a block device mapping. Could a folio in a block device mapping be mapped in a user VMA?
Yes, if the user directly mmaps the block device, like `/dev/sda`. The VMA *would* be added to the block device's `i_mmap`, which makes sense. And `unmap_mapping_folio` *should* find and unmap it in that case.
But what if the block device is *mounted* as a filesystem? If it's mounted, the filesystem reads blocks from the block device. It should allocate new folios for its page cache and copy the data.
Wait, `blkdev_direct_write` is writing *directly* to the block device. It's calling `kiocb_invalidate_pages`, which invalidates the block device's page cache. So, if a folio in the *block device's* cache is mapped in a VMA, `unmap_mapping_folio` will unmap it from the block device's `i_mmap`. But... the folio can only belong to one mapping.
Okay, DAX is probably a dead end, or at least not the initial issue, it uses PFNs or DAX exceptional entries, not folios.
`mmap` of a file on a filesystem with fscache? Not likely to cause this particular issue.
What about `splice` or `vmsplice`? If `vmsplice` maps a folio into a pipe, it's not mapped in a VMA, so `folio_mapped` will be false. We don't increment mapcount on pipes.
If a user `mmap`s a file on a filesystem... the filesystem can't *directly* share folios with the block device mapping, right? Maybe for metadata, but users don't usually mmap metadata.
What about `io_uring`? It *can* map folios. But does it increment mapcount? This needs investigation.
Then there are edge cases like secretmem folios and KVM guests, that I need to keep in mind, but they're probably not the root cause.
Okay, let's search Syzkaller for that `mm/truncate.c:632` error! I need to see if this is a known issue.
|
| 41/2 |
2026/08/11 01:41 |
tool |
grepper |
0m
Error:
Args: map[Expression:kernel BUG at mm/truncate.c:632]
Results:
|
| 42/2 |
2026/08/11 01:41 |
llm |
expert |
0m
Model:
Tokens:
input: 27704
output: 32
thoughts: 13
Thoughts: Here's my take on the `folio_mapped(folio)` logic.
**Analyzing `folio_mapped(folio)`**
Alright, let's dive into this `folio_mapped(folio)` function. My initial thought is to rigorously dissect how this logic is structured. I need to understand its fundamental purpose within the broader system. What's it mapping *to*? What's the nature of this "folio" input? Is it a key, an object, a data structure? Understanding the input's format is critical. I'm going to consider edge cases immediately β what happens with null folios? Empty folios? Malformed folios? Does the function gracefully handle these scenarios, or are there potential vulnerabilities or unexpected behaviors? I want to ensure itβs robust. Next, Iβll look at the core of the mapping process. Is it a direct lookup, a series of conditional statements, or a more complex transformation? Are there potential performance bottlenecks lurking within the mapping logic, especially if dealing with large datasets or recursive operations? Iβll need to watch out for this. Lastly, I'm thinking about the output. What's the expected result of this mapping? Is it a simple value, or a more complex data structure? How is the output consumed or utilized by other parts of the system? The implications of any returned values need to be explored. Finally, I need to look for any hidden assumptions or implicit dependencies, the things that are easily overlooked but can cause major problems later on. I'll approach this with the mindset of a compiler, breaking down the logic into its smallest components to ensure accuracy and efficiency.
|
| 43/2 |
2026/08/11 01:41 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/truncate.c Name:folio_mapped]
Results: map[SourceCode:1924: /**
1925: * folio_mapped - Is this folio mapped into userspace?
1926: * @folio: The folio.
1927: *
1928: * Return: True if any page in this folio is referenced by user page tables.
1929: */
1930: static inline bool folio_mapped(const struct folio *folio)
1931: {
1932: return folio_mapcount(folio) >= 1;
1933: }
SourceFile:include/linux/mm.h]
|
| 44/2 |
2026/08/11 01:41 |
llm |
expert |
0m
Model:
Tokens:
input: 28256
output: 35
thoughts: 412
Thoughts: **Investigating a Folio Mapping Paradox**
Okay, so I'm looking at this condition: `folio_mapcount(folio) >= 1`. That's the starting point. But, how can this be true *after* `unmap_mapping_folio` has been called? It just doesn't make sense on the surface. `unmap_mapping_folio` is supposed to unmap the folio from `mapping->i_mmap`. So, if it's unmapped, the mapcount should be zero, right?
Alright, let's break this down. The situation implies the folio is mapped in a VMA, but *not* one that's associated with `mapping->i_mmap`. That's the crux of the problem. How can that happen? Let's rule out the obvious.
1. `mremap`? No, that wouldn't do it. `mremap` keeps the VMA within the same `i_mmap` tree. So, it wouldn't create this kind of discrepancy.
2. `fork`? Nope. A `fork` duplicates the VMA, both copies are still within `i_mmap`.
3. `vm_insert_page`? Aha! That's a possibility. Could a driver call `vm_insert_page` to insert a folioβthat came from the block device's mappingβinto a VMA belonging to a completely different mapping? Like, say, a character device? Now we're getting somewhere.
The question then becomes, does anyone *actually* do this? I know many drivers utilize `vm_insert_page` or `remap_pfn_range`, but they typically insert their *own* pages, not pages taken from the block device's page cache. It's not something I'd expect to see, but... could it be happening?
Let's think through some user-level scenarios: If a user maps a block device using `mmap`, gets a VMA, and then calls `madvise(MADV_DONTNEED)` - No, that wouldn't cause an issue. What about `userfaultfd`? That would not make sense in this context. And what about the folio being anonymous? No, because if it was anonymous, `folio->mapping` would be `NULL` or `PAGE_MAPPING_ANON`, not the block device's mapping. We're getting closer...
Wait, could it be that `unmap_mapping_range_tree` is *failing* to unmap the folio, even if the VMA *is* in `i_mmap`? Maybe that's a direction to look. Okay, time to dive back into `unmap_mapping_folio` and really understand it.
|
| 45/2 |
2026/08/11 01:41 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/memory.c Name:unmap_mapping_folio]
Results: map[SourceCode:4361: /**
4362: * unmap_mapping_folio() - Unmap single folio from processes.
4363: * @folio: The locked folio to be unmapped.
4364: *
4365: * Unmap this folio from any userspace process which still has it mmaped.
4366: * Typically, for efficiency, the range of nearby pages has already been
4367: * unmapped by unmap_mapping_pages() or unmap_mapping_range(). But once
4368: * truncation or invalidation holds the lock on a folio, it may find that
4369: * the page has been remapped again: and then uses unmap_mapping_folio()
4370: * to unmap it finally.
4371: */
4372: void unmap_mapping_folio(struct folio *folio)
4373: {
4374: struct address_space *mapping = folio->mapping;
4375: struct zap_details details = { };
4376: pgoff_t first_index;
4377: pgoff_t last_index;
4378:
4379: VM_BUG_ON(!folio_test_locked(folio));
4380:
4381: first_index = folio->index;
4382: last_index = folio_next_index(folio) - 1;
4383:
4384: details.skip_cows = true;
4385: details.single_folio = folio;
4386: details.zap_flags = ZAP_FLAG_DROP_MARKER;
4387:
4388: i_mmap_lock_read(mapping);
4389: if (unlikely(mapping_mapped(mapping)))
4390: unmap_mapping_range_tree(&mapping->i_mmap, first_index,
4391: last_index, &details);
4392: i_mmap_unlock_read(mapping);
4393: }
SourceFile:mm/memory.c]
|
| 46/2 |
2026/08/11 01:41 |
llm |
expert |
0m
Model:
Tokens:
input: 29876
output: 35
thoughts: 495
Thoughts: **Unraveling Potential Folio Mapping Discrepancies**
Okay, so I'm looking at `unmap_mapping_range_tree` and it's taking `first_index` and `last_index`. My immediate thought is, if the VMA's `vm_pgoff` is shifted, it calculates the virtual address using `first_index`. What if the folio is mapped at an index that doesn't align with its `folio->index`? That's the crux of my concern. How even *could* that happen?
`mremap` doesn't change `vm_pgoff`, it just shuffles the virtual address; the virtual address base calculation from `pgoff` should still be valid. Then, what about a call to `vm_insert_page`? Aha! `vm_insert_page` *doesn't* enforce that page index matching the VMA's `vm_pgoff`. But who in their right mind would be using `vm_insert_page` on a page cache folio from a block device?
Wait, hold on... what about mapping `/dev/loop0` or something similar? Could that be a vector for misaligned mapping? I need to think this through more thoroughly.
Are there other ways this folio could be mapped? Forget `fork`, no. KSM? Nah, KSM's all about anonymous pages. What about `uprobe`? Nope, that replaces a page with an anonymous one. `ptrace`? Ptrace writes to a page, which might trigger a COW.
Okay, wait, *look* at this! `details.skip_cows = true;`. If the folio *was* COWed, the resulting COW page is anonymous, and `skip_cows` *skips* it. But the *original* folio is no longer in that PTE! That PTE now points to the anonymous page. So, the original folio's mapcount went down when the COW happened, right? So a COW *doesn't* leave the original folio mapped improperly.
Hmm, what if this folio is actually a huge one, a THP? If it's a large folio, `first_index` is `folio->index`, and `last_index` is `folio_next_index(folio) - 1`. Okay, so `unmap_mapping_range_tree` will unmap the whole range. What if this THP folio has been mapped as a PMD entry, and `unmap_mapping_range_tree` doesn't unmap the whole PMD correctly? I better check `zap_pmd_range`.
|
| 47/2 |
2026/08/11 01:41 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/memory.c Name:zap_pmd_range]
Results: map[SourceCode:1992: static inline unsigned long zap_pmd_range(struct mmu_gather *tlb,
1993: struct vm_area_struct *vma, pud_t *pud,
1994: unsigned long addr, unsigned long end,
1995: struct zap_details *details)
1996: {
1997: pmd_t *pmd;
1998: unsigned long next;
1999:
2000: pmd = pmd_offset(pud, addr);
2001: do {
2002: next = pmd_addr_end(addr, end);
2003: if (pmd_is_huge(*pmd)) {
2004: if (next - addr != HPAGE_PMD_SIZE)
2005: __split_huge_pmd(vma, pmd, addr, false);
2006: else if (zap_huge_pmd(tlb, vma, pmd, addr)) {
2007: addr = next;
2008: continue;
2009: }
2010: /* fall through */
2011: } else if (details && details->single_folio &&
2012: folio_test_pmd_mappable(details->single_folio) &&
2013: next - addr == HPAGE_PMD_SIZE && pmd_none(*pmd)) {
2014: sync_with_folio_pmd_zap(tlb->mm, pmd);
2015: }
2016: if (pmd_none(*pmd)) {
2017: addr = next;
2018: continue;
2019: }
2020: addr = zap_pte_range(tlb, vma, pmd, addr, next, details);
2021: if (addr != next)
2022: pmd--;
2023: } while (pmd++, cond_resched(), addr != end);
2024:
2025: return addr;
2026: }
SourceFile:mm/memory.c]
|
| 48/2 |
2026/08/11 01:41 |
llm |
expert |
0m
Model:
Tokens:
input: 31559
output: 35
thoughts: 114
Thoughts: **Analyzing the Memory Mapping Strategy**
Okay, so `zap_huge_pmd` returning `true` means it successfully cleared the huge PMD, got it. If it returns `false`, the code flow proceeds to `zap_pte_range`, which seems logical *unless* the target is still a huge PMD. That would be a complete disaster; `zap_pte_range` isn't designed for handling huge pages and would likely throw an error or crash because itβs trying to operate on something that isn't a regular page table entry (PTE). The condition seems like a potential flaw...
Wait, I think I'm overthinking this. `zap_huge_pmd` only returns false if the huge PMD got split in the background, like if another thread messed with it. So, a return of false indicates that the mapping is no longer a huge page. Okay, that makes more sense. Still, let's just confirm my understanding by diving into the `zap_huge_pmd` implementation. I'll take a look at it now.
|
| 49/2 |
2026/08/11 01:41 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:mm/memory.c Name:zap_huge_pmd]
Results: map[SourceCode: SourceFile:]
|
| 50/2 |
2026/08/11 01:41 |
llm |
expert |
0m
Model:
Tokens:
input: 31957
output: 37
thoughts: 22
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience and written in the first person:
**Navigating the Huge Page Landscape**
Okay, so I'm thinking about `zap_huge_pmd`. Where would that be hanging out? Given its name, it's pretty clearly related to huge pages, which puts it squarely within the memory management domain. It's almost certainly not in a driver or a more general system call area. My gut says it's got to be in `mm/huge_memory.c`. That file is where all the heavy lifting for huge page allocation, deallocation, and related operations resides. Anything dealing with manipulating the Page Middle Directory would naturally be in that central piece. Yep, `mm/huge_memory.c` feels like the right hunting ground.
|
| 51/2 |
2026/08/11 01:42 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:mm/huge_memory.c Name:zap_huge_pmd]
Results: map[SourceCode: SourceFile:]
|
| 52/2 |
2026/08/11 01:42 |
llm |
expert |
0m
Model:
Tokens:
input: 32209
output: 28
thoughts: 13
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Analyzing `zap_huge_pmd`**
Okay, so the prompt suggests we're dealing with something related to `zap_huge_pmd`. Immediately, my mind jumps to the core of this function: the unmapping of huge pages, specifically in the page table structures. That implies we're probably looking at a scenario where a huge page is being freed, unmapped due to migration, or being split into smaller pages. Given the context (implied by just the function name), I need to consider the locking implications, especially the potential for concurrent access to page tables and the associated metadata. Are we looking at a hot path? If so, performance is a major consideration. I need to think about how this affects the TLB, both flushing and potential invalidation strategies to maintain consistency. I'm going to want to consider any potential race conditions between multiple threads or processes accessing and modifying the same regions of memory. Also, how does this interact with NUMA architectures? Are there any NUMA-specific considerations in terms of migration or node-local operations? I'll probably also want to review any interaction with other page table management functions, specifically the ones that deal with regular page mappings. The kernel documentation or comments will certainly be my next stop.
|
| 53/2 |
2026/08/11 01:42 |
tool |
grepper |
0m
Args: map[Expression:zap_huge_pmd PathPrefix:mm/]
Results: map[Output:mm/huge_memory.c=2393=static inline void zap_deposited_table(struct mm_struct *mm, pmd_t *pmd)
--
mm/huge_memory.c-2401-
mm/huge_memory.c:2402:static void zap_huge_pmd_folio(struct mm_struct *mm, struct vm_area_struct *vma,
mm/huge_memory.c-2403- pmd_t pmdval, struct folio *folio, bool is_present)
--
mm/huge_memory.c=2438=static bool has_deposited_pgtable(struct vm_area_struct *vma, pmd_t pmdval,
--
mm/huge_memory.c-2459-/**
mm/huge_memory.c:2460: * zap_huge_pmd - Zap a huge THP which is of PMD size.
mm/huge_memory.c-2461- * @tlb: The MMU gather TLB state associated with the operation.
--
mm/huge_memory.c-2467- */
mm/huge_memory.c:2468:bool zap_huge_pmd(struct mmu_gather *tlb, struct vm_area_struct *vma,
mm/huge_memory.c-2469- pmd_t *pmd, unsigned long addr)
--
mm/huge_memory.c-2497- if (folio)
mm/huge_memory.c:2498: zap_huge_pmd_folio(mm, vma, orig_pmd, folio, is_present);
mm/huge_memory.c-2499- if (has_deposit)
--
mm/internal.h=509=void pmd_install(struct mm_struct *mm, pmd_t *pmd, pgtable_t *pte);
--
mm/internal.h-516- * When we find a pmd_none() while unmapping a folio without holding the PTL,
mm/internal.h:517: * zap_huge_pmd() may have cleared the PMD but not yet modified the folio to
mm/internal.h-518- * indicate that it's unmapped. Skipping the PMD without synchronization could
--
mm/memory.c=1992=static inline unsigned long zap_pmd_range(struct mmu_gather *tlb,
--
mm/memory.c-2005- __split_huge_pmd(vma, pmd, addr, false);
mm/memory.c:2006: else if (zap_huge_pmd(tlb, vma, pmd, addr)) {
mm/memory.c-2007- addr = next;
]
|
| 54/2 |
2026/08/11 01:42 |
llm |
expert |
0m
Model:
Tokens:
input: 33214
output: 37
thoughts: 22
Thoughts: Here's a summary of my thought process, as requested:
**Diving into `zap_huge_pmd`**
Alright, let's get down to brass tacks. I'm focusing in on `zap_huge_pmd`. Naturally, given my expertise, my first instinct is to zero in on where this function lives β `mm/huge_memory.c`. The name itself screams "huge pages," so I'm already anticipating that this function is central to handling the unmapping or deallocation of large memory regions managed with huge page support. I'm mentally parsing the code path: Where is it called? What are the calling contexts? What data structures does it manipulate, and how? I'll probably start by looking at its purpose, the comments are usually helpful here. After that, I will trace the call path, and identify potential locking pitfalls to examine. I wonder if there are any specific conditions that trigger its execution? Is it related to process termination, memory reclaim, or perhaps demotion of huge pages? I will likely be trying to picture the exact memory model being changed as a result of a call to `zap_huge_pmd`. Let's dig in.
|
| 55/2 |
2026/08/11 01:42 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:mm/huge_memory.c Name:zap_huge_pmd]
Results: map[SourceCode: SourceFile:]
|
| 56/2 |
2026/08/11 01:42 |
llm |
expert |
0m
Model:
Tokens:
input: 33545
output: 36
thoughts: 51
Thoughts: Here's my take on that code snippet:
**Diving into `zap_huge_pmd`**
Okay, so I'm looking at `bool zap_huge_pmd(struct mmu_gather *tlb, struct vm_area_struct *vma, pmd_t *pmd, unsigned long addr)`. My first thought is, "Right, `zap_huge_pmd`... that's the function responsible for unmapping a huge page, probably during a process or kernel operation that necessitates it." I need to understand what's happening in detail here. Let's see what the function does with those parameters; seems like the `tlb` struct is about to be utilized to invalidate the TLB entries, that `vma` pointer is the virtual memory area that is being manipulated, the `pmd` is the page middle directory entry we're operating on, and `addr` is the virtual address where the magic is about to happen. Now, to read what it does with all of this information!
|
| 57/2 |
2026/08/11 01:42 |
tool |
read-file |
0m
Args: map[File:mm/huge_memory.c FirstLine:2460 LineCount:50]
Results: map[Contents:2460: * zap_huge_pmd - Zap a huge THP which is of PMD size.
2461: * @tlb: The MMU gather TLB state associated with the operation.
2462: * @vma: The VMA containing the range to zap.
2463: * @pmd: A pointer to the leaf PMD entry.
2464: * @addr: The virtual address for the range to zap.
2465: *
2466: * Returns: %true on success, %false otherwise.
2467: */
2468: bool zap_huge_pmd(struct mmu_gather *tlb, struct vm_area_struct *vma,
2469: pmd_t *pmd, unsigned long addr)
2470: {
2471: struct mm_struct *mm = tlb->mm;
2472: struct folio *folio = NULL;
2473: bool is_present = false;
2474: bool has_deposit;
2475: spinlock_t *ptl;
2476: pmd_t orig_pmd;
2477:
2478: tlb_change_page_size(tlb, HPAGE_PMD_SIZE);
2479:
2480: ptl = __pmd_trans_huge_lock(pmd, vma);
2481: if (!ptl)
2482: return false;
2483: /*
2484: * For architectures like ppc64 we look at deposited pgtable
2485: * when calling pmdp_huge_get_and_clear. So do the
2486: * pgtable_trans_huge_withdraw after finishing pmdp related
2487: * operations.
2488: */
2489: orig_pmd = pmdp_huge_get_and_clear_full(vma, addr, pmd,
2490: tlb->fullmm);
2491: arch_check_zapped_pmd(vma, orig_pmd);
2492: tlb_remove_pmd_tlb_entry(tlb, pmd, addr);
2493:
2494: is_present = pmd_present(orig_pmd);
2495: folio = normal_or_softleaf_folio_pmd(vma, addr, orig_pmd, is_present);
2496: has_deposit = has_deposited_pgtable(vma, orig_pmd, folio);
2497: if (folio)
2498: zap_huge_pmd_folio(mm, vma, orig_pmd, folio, is_present);
2499: if (has_deposit)
2500: zap_deposited_table(mm, pmd);
2501:
2502: spin_unlock(ptl);
2503: if (is_present && folio)
2504: tlb_remove_page_size(tlb, &folio->page, HPAGE_PMD_SIZE);
2505: return true;
2506: }
2507:
2508: #ifndef pmd_move_must_withdraw
2509: static inline int pmd_move_must_withdraw(spinlock_t *new_pmd_ptl,
]
|
| 58/2 |
2026/08/11 01:42 |
llm |
expert |
0m
Model:
Tokens:
input: 34751
output: 34
thoughts: 107
Thoughts: **A Quick Memory Puzzle**
Okay, hold on a second. I just realized something about how we handle huge PMDs during zapping. The `zap_huge_pmd` function... doesn't actually seem to incorporate any checks related to `details`! That's a bit worrying. It just goes ahead and zaps the PMD, regardless of whether `details->skip_cows` or `details->single_folio` are set. That seems... potentially problematic.
But wait, I need to trace back. `zap_huge_pmd` is called from `zap_pmd_range`, right? The critical question now is: does `zap_pmd_range` itself check the `details` structure *before* it makes that call to `zap_huge_pmd`? That's where the decision is made, and I need to be sure the function is not being called without respect for my constraints. I'd better re-examine the logic within `zap_pmd_range` to confirm. Let's dig back into that function... again.
|
| 59/2 |
2026/08/11 01:42 |
tool |
read-file |
0m
Args: map[File:mm/memory.c FirstLine:1992 LineCount:35]
Results: map[Contents:1992: static inline unsigned long zap_pmd_range(struct mmu_gather *tlb,
1993: struct vm_area_struct *vma, pud_t *pud,
1994: unsigned long addr, unsigned long end,
1995: struct zap_details *details)
1996: {
1997: pmd_t *pmd;
1998: unsigned long next;
1999:
2000: pmd = pmd_offset(pud, addr);
2001: do {
2002: next = pmd_addr_end(addr, end);
2003: if (pmd_is_huge(*pmd)) {
2004: if (next - addr != HPAGE_PMD_SIZE)
2005: __split_huge_pmd(vma, pmd, addr, false);
2006: else if (zap_huge_pmd(tlb, vma, pmd, addr)) {
2007: addr = next;
2008: continue;
2009: }
2010: /* fall through */
2011: } else if (details && details->single_folio &&
2012: folio_test_pmd_mappable(details->single_folio) &&
2013: next - addr == HPAGE_PMD_SIZE && pmd_none(*pmd)) {
2014: sync_with_folio_pmd_zap(tlb->mm, pmd);
2015: }
2016: if (pmd_none(*pmd)) {
2017: addr = next;
2018: continue;
2019: }
2020: addr = zap_pte_range(tlb, vma, pmd, addr, next, details);
2021: if (addr != next)
2022: pmd--;
2023: } while (pmd++, cond_resched(), addr != end);
2024:
2025: return addr;
2026: }
]
|
| 60/2 |
2026/08/11 01:42 |
llm |
expert |
0m
Model:
Tokens:
input: 35693
output: 34
thoughts: 284
Thoughts: **Debugging a PMD Unmapping Issue**
Okay, wait a minute. Looking at `zap_pmd_range`, it appears it's skipping a crucial check on `details` before calling `zap_huge_pmd`. Now, I know `zap_huge_pmd` unmaps the PMD. And if it *was* a huge PMD, it certainly unmaps it. That unmapping should decrement the folio's mapcount. So, this isn't leaving the folio *mapped* after the operation. It's unmapping, which is the desired outcome.
But hold on, what if this PMD, the one being zapped, maps a completely *different* folio? If it maps a different folio, then the code will unmap *that* other folio. But our target `folio` isn't unmapped, because it wasn't mapped there to begin with! That's correct, isn't it?
Wait a sec... if our target `folio` wasn't mapped at this PMD location, then *why* is `folio_mapped(folio)` evaluating to true? That means it's mapped *somewhere* else! Where else *could* it possibly be mapped?
If `unmap_mapping_range_tree` is only unmapping from `mapping->i_mmap`, what if the folio is mapped in a VMA that is *not* within `mapping->i_mmap`? Now we're getting somewhere. How can a folio be mapped in a VMA that isn't connected to the usual `mapping->i_mmap`? I need to know where it's actually being mapped.
Alright, time to dig deeper. I need to search syzkaller bugs for instances of "folio_mapped" and "BUG_ON". This should give me some insights into potential related issues or corner cases.
|
| 61/2 |
2026/08/11 01:42 |
tool |
grepper |
0m
Args: map[Expression:BUG_ON\(folio_mapped\(folio\)\) PathPrefix:mm/]
Results: map[Output:mm/shmem.c=1601=int shmem_writeout(struct folio *folio, struct swap_iocb **plug,
--
mm/shmem.c-1711-
mm/shmem.c:1712: BUG_ON(folio_mapped(folio));
mm/shmem.c-1713- error = swap_writeout(folio, plug);
--
mm/truncate.c=622=int folio_unmap_invalidate(struct address_space *mapping, struct folio *folio,
--
mm/truncate.c-631- unmap_mapping_folio(folio);
mm/truncate.c:632: BUG_ON(folio_mapped(folio));
mm/truncate.c-633-
]
|
| 62/2 |
2026/08/11 01:42 |
llm |
expert |
0m
Model:
Tokens:
input: 36610
output: 45
thoughts: 138
Thoughts: **Tracing a Persistent Bug Assertion**
Okay, so I'm looking at this `BUG_ON(folio_mapped(folio))` in `mm/truncate.c:632` β it's been throwing me for a loop. Let's see... the commit that added it specifically was `4a9e23159fd3`. Hang on, wasn't that assertion in a different context before? I seem to recall it being part of `invalidate_inode_pages2_range`. Ah yes, there it is!
```c
if (folio_mapped(folio))
unmap_mapping_folio(folio);
BUG_ON(folio_mapped(folio));
```
So this `BUG_ON` has actually been around for ages! I need to get the full picture, its history, its purpose, and what has changed around it. It's time to dig into the git logs to trace its evolution. I'll execute `git log -S "BUG_ON(folio_mapped(folio))" mm/truncate.c` to see when and why this check was introduced, and if the surrounding code has shifted over time. That history might shed some light on the potential root cause and if it relates to any recent changes in the surrounding code.
|
| 63/2 |
2026/08/11 01:42 |
tool |
git-log |
0m
Args: map[CodeRegexp:BUG_ON\(folio_mapped\(folio\)\) Count:10 PathPrefix:mm/truncate.c]
Results: map[Output:4a9e23159fd3 mm/truncate: add folio_unmap_invalidate() helper
fae9bc4a9017 truncate: Convert invalidate_inode_pages2_range() to use a folio
]
|
| 64/2 |
2026/08/11 01:42 |
llm |
expert |
0m
Model:
Tokens:
input: 37134
output: 45
thoughts: 29
Thoughts: Okay, here's my attempt at summarizing that thought process, assuming I'm a seasoned kernel developer:
**Code Archaeology & Verification**
Hmm, so before that commit, `fae9bc4a9017`, the code used a `BUG_ON(page_mapped(page))` check. Interesting. That suggests a critical change or refinement happened in that commit. This means I need to understand what triggered the change. My first instinct would be to look at the commit message for `fae9bc4a9017`. Was it a bug fix? An optimization? A functional change? This could also be a refactor.
The previous use of `BUG_ON` implies that the condition `page_mapped(page)` was considered fundamentally impossible, or at least, a critical error if it ever occurred. The check may have been overly aggressive, potentially triggering false positives under certain conditions, leading to the code being changed.
I need to understand *why* the `BUG_ON` was replaced. Was it deemed too harsh? Perhaps the condition is now handled differently? Did the definition of 'mapped' change? Or maybe this section of code now expects that condition, and the `BUG_ON` was removed for performance reasons. This needs thorough investigation.
|
| 65/2 |
2026/08/11 01:42 |
tool |
git-log |
0m
Args: map[CodeRegexp:BUG_ON\(page_mapped\(page\)\) Count:10 PathPrefix:mm/truncate.c]
Results: map[Output:fae9bc4a9017 truncate: Convert invalidate_inode_pages2_range() to use a folio
d00806b18315 mm: fix fault vs invalidate race for linear mappings
]
|
| 66/2 |
2026/08/11 01:42 |
llm |
expert |
0m
Model:
Tokens:
input: 37538
output: 27
thoughts: 18
Thoughts: Okay, here's my interpretation of what's going through my mind, delivered as a first-person summary, keeping in mind the expert user:
**Assessing the Landscape: Commit ID `d00806b18315`**
Alright, let's dive into this specific commit, `d00806b18315`. I immediately want to understand what's happening here. That ID is my starting point, and it's practically a fingerprint. I need to quickly recall any context surrounding it β recent merges, related tickets, any red flags from the commit log. Is this a critical bug fix, a feature enhancement, or just some refactoring? Knowing the general category would save me time. I'll start by checking the surrounding history; a diff of `d00806b18315` would be my first step. Let's see what changed!
|
| 67/2 |
2026/08/11 01:42 |
tool |
git-show |
0m
Args:
Results: map[Output:commit d00806b183152af6d24f46f0c33f14162ca1262a
Author: Nicholas Piggin <npiggin@gmail.com>
Date: Thu Jul 19 01:46:57 2007 -0700
mm: fix fault vs invalidate race for linear mappings
Fix the race between invalidate_inode_pages and do_no_page.
Andrea Arcangeli identified a subtle race between invalidation of pages from
pagecache with userspace mappings, and do_no_page.
The issue is that invalidation has to shoot down all mappings to the page,
before it can be discarded from the pagecache. Between shooting down ptes to
a particular page, and actually dropping the struct page from the pagecache,
do_no_page from any process might fault on that page and establish a new
mapping to the page just before it gets discarded from the pagecache.
The most common case where such invalidation is used is in file truncation.
This case was catered for by doing a sort of open-coded seqlock between the
file's i_size, and its truncate_count.
Truncation will decrease i_size, then increment truncate_count before
unmapping userspace pages; do_no_page will read truncate_count, then find the
page if it is within i_size, and then check truncate_count under the page
table lock and back out and retry if it had subsequently been changed (ptl
will serialise against unmapping, and ensure a potentially updated
truncate_count is actually visible).
Complexity and documentation issues aside, the locking protocol fails in the
case where we would like to invalidate pagecache inside i_size. do_no_page
can come in anytime and filemap_nopage is not aware of the invalidation in
progress (as it is when it is outside i_size). The end result is that
dangling (->mapping == NULL) pages that appear to be from a particular file
may be mapped into userspace with nonsense data. Valid mappings to the same
place will see a different page.
Andrea implemented two working fixes, one using a real seqlock, another using
a page->flags bit. He also proposed using the page lock in do_no_page, but
that was initially considered too heavyweight. However, it is not a global or
per-file lock, and the page cacheline is modified in do_no_page to increment
_count and _mapcount anyway, so a further modification should not be a large
performance hit. Scalability is not an issue.
This patch implements this latter approach. ->nopage implementations return
with the page locked if it is possible for their underlying file to be
invalidated (in that case, they must set a special vm_flags bit to indicate
so). do_no_page only unlocks the page after setting up the mapping
completely. invalidation is excluded because it holds the page lock during
invalidation of each page (and ensures that the page is not mapped while
holding the lock).
This also allows significant simplifications in do_no_page, because we have
the page locked in the right place in the pagecache from the start.
Signed-off-by: Nick Piggin <npiggin@suse.de>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
diff --git a/fs/gfs2/ops_file.c b/fs/gfs2/ops_file.c
index 1a5e8e893d75..bad0b24cb773 100644
--- a/fs/gfs2/ops_file.c
+++ b/fs/gfs2/ops_file.c
@@ -364,6 +364,8 @@ static int gfs2_mmap(struct file *file, struct vm_area_struct *vma)
else
vma->vm_ops = &gfs2_vm_ops_private;
+ vma->vm_flags |= VM_CAN_INVALIDATE;
+
gfs2_glock_dq_uninit(&i_gh);
return error;
diff --git a/fs/gfs2/ops_vm.c b/fs/gfs2/ops_vm.c
index 404b7cc9f8c4..d5a98cbfebdc 100644
--- a/fs/gfs2/ops_vm.c
+++ b/fs/gfs2/ops_vm.c
@@ -138,6 +138,8 @@ static struct page *gfs2_sharewrite_nopage(struct vm_area_struct *area,
if (alloc_required) {
error = alloc_page_backing(ip, result);
if (error) {
+ if (area->vm_flags & VM_CAN_INVALIDATE)
+ unlock_page(result);
page_cache_release(result);
result = NULL;
goto out;
diff --git a/fs/ncpfs/mmap.c b/fs/ncpfs/mmap.c
index 70a69115500f..5416673418b8 100644
--- a/fs/ncpfs/mmap.c
+++ b/fs/ncpfs/mmap.c
@@ -123,6 +123,7 @@ int ncp_mmap(struct file *file, struct vm_area_struct *vma)
return -EFBIG;
vma->vm_ops = &ncp_file_mmap;
+ vma->vm_flags |= VM_CAN_INVALIDATE;
file_accessed(file);
return 0;
}
diff --git a/fs/ocfs2/mmap.c b/fs/ocfs2/mmap.c
index d79aa12137d2..904f39ff5340 100644
--- a/fs/ocfs2/mmap.c
+++ b/fs/ocfs2/mmap.c
@@ -226,6 +226,7 @@ int ocfs2_mmap(struct file *file, struct vm_area_struct *vma)
ocfs2_meta_unlock(file->f_dentry->d_inode, lock_level);
out:
vma->vm_ops = &ocfs2_file_vm_ops;
+ vma->vm_flags |= VM_CAN_INVALIDATE;
return 0;
}
diff --git a/fs/xfs/linux-2.6/xfs_file.c b/fs/xfs/linux-2.6/xfs_file.c
index cbcd40c8c2a0..92b2f225712f 100644
--- a/fs/xfs/linux-2.6/xfs_file.c
+++ b/fs/xfs/linux-2.6/xfs_file.c
@@ -310,6 +310,7 @@ xfs_file_mmap(
struct vm_area_struct *vma)
{
vma->vm_ops = &xfs_file_vm_ops;
+ vma->vm_flags |= VM_CAN_INVALIDATE;
#ifdef CONFIG_XFS_DMAPI
if (vn_from_inode(filp->f_path.dentry->d_inode)->v_vfsp->vfs_flag & VFS_DMI)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index a5c451816fdc..ca9536a348c8 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -168,6 +168,12 @@ extern unsigned int kobjsize(const void *objp);
#define VM_INSERTPAGE 0x02000000 /* The vma has had "vm_insert_page()" done on it */
#define VM_ALWAYSDUMP 0x04000000 /* Always include in core dumps */
+#define VM_CAN_INVALIDATE 0x08000000 /* The mapping may be invalidated,
+ * eg. truncate or invalidate_inode_*.
+ * In this case, do_no_page must
+ * return with the page locked.
+ */
+
#ifndef VM_STACK_DEFAULT_FLAGS /* arch can override this */
#define VM_STACK_DEFAULT_FLAGS VM_DATA_DEFAULT_FLAGS
#endif
diff --git a/mm/filemap.c b/mm/filemap.c
index 5d5449f3d41c..462cda58a18e 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -1325,9 +1325,10 @@ struct page *filemap_nopage(struct vm_area_struct *area,
unsigned long size, pgoff;
int did_readaround = 0, majmin = VM_FAULT_MINOR;
+ BUG_ON(!(area->vm_flags & VM_CAN_INVALIDATE));
+
pgoff = ((address-area->vm_start) >> PAGE_CACHE_SHIFT) + area->vm_pgoff;
-retry_all:
size = (i_size_read(inode) + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
if (pgoff >= size)
goto outside_data_content;
@@ -1349,7 +1350,7 @@ struct page *filemap_nopage(struct vm_area_struct *area,
* Do we have something in the page cache already?
*/
retry_find:
- page = find_get_page(mapping, pgoff);
+ page = find_lock_page(mapping, pgoff);
if (!page) {
unsigned long ra_pages;
@@ -1383,7 +1384,7 @@ struct page *filemap_nopage(struct vm_area_struct *area,
start = pgoff - ra_pages / 2;
do_page_cache_readahead(mapping, file, start, ra_pages);
}
- page = find_get_page(mapping, pgoff);
+ page = find_lock_page(mapping, pgoff);
if (!page)
goto no_cached_page;
}
@@ -1392,13 +1393,19 @@ struct page *filemap_nopage(struct vm_area_struct *area,
ra->mmap_hit++;
/*
- * Ok, found a page in the page cache, now we need to check
- * that it's up-to-date.
+ * We have a locked page in the page cache, now we need to check
+ * that it's up-to-date. If not, it is going to be due to an error.
*/
- if (!PageUptodate(page))
+ if (unlikely(!PageUptodate(page)))
goto page_not_uptodate;
-success:
+ /* Must recheck i_size under page lock */
+ size = (i_size_read(inode) + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
+ if (unlikely(pgoff >= size)) {
+ unlock_page(page);
+ goto outside_data_content;
+ }
+
/*
* Found the page and have a reference on it.
*/
@@ -1440,6 +1447,7 @@ struct page *filemap_nopage(struct vm_area_struct *area,
return NOPAGE_SIGBUS;
page_not_uptodate:
+ /* IO error path */
if (!did_readaround) {
majmin = VM_FAULT_MAJOR;
count_vm_event(PGMAJFAULT);
@@ -1451,37 +1459,15 @@ struct page *filemap_nopage(struct vm_area_struct *area,
* because there really aren't any performance issues here
* and we need to check for errors.
*/
- lock_page(page);
-
- /* Somebody truncated the page on us? */
- if (!page->mapping) {
- unlock_page(page);
- page_cache_release(page);
- goto retry_all;
- }
-
- /* Somebody else successfully read it in? */
- if (PageUptodate(page)) {
- unlock_page(page);
- goto success;
- }
ClearPageError(page);
error = mapping->a_ops->readpage(file, page);
- if (!error) {
- wait_on_page_locked(page);
- if (PageUptodate(page))
- goto success;
- } else if (error == AOP_TRUNCATED_PAGE) {
- page_cache_release(page);
+ page_cache_release(page);
+
+ if (!error || error == AOP_TRUNCATED_PAGE)
goto retry_find;
- }
- /*
- * Things didn't work out. Return zero to tell the
- * mm layer so, possibly freeing the page cache page first.
- */
+ /* Things didn't work out. Return zero to tell the mm layer so. */
shrink_readahead_size_eio(file, ra);
- page_cache_release(page);
return NOPAGE_SIGBUS;
}
EXPORT_SYMBOL(filemap_nopage);
@@ -1674,6 +1660,7 @@ int generic_file_mmap(struct file * file, struct vm_area_struct * vma)
return -ENOEXEC;
file_accessed(file);
vma->vm_ops = &generic_file_vm_ops;
+ vma->vm_flags |= VM_CAN_INVALIDATE;
return 0;
}
diff --git a/mm/memory.c b/mm/memory.c
index 9c6ff7fffdc8..e6c99f6b5649 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -1831,6 +1831,13 @@ static int unmap_mapping_range_vma(struct vm_area_struct *vma,
unsigned long restart_addr;
int need_break;
+ /*
+ * files that support invalidating or truncating portions of the
+ * file from under mmaped areas must set the VM_CAN_INVALIDATE flag, and
+ * have their .nopage function return the page locked.
+ */
+ BUG_ON(!(vma->vm_flags & VM_CAN_INVALIDATE));
+
again:
restart_addr = vma->vm_truncate_count;
if (is_restart_addr(restart_addr) && start_addr < restart_addr) {
@@ -1959,17 +1966,8 @@ void unmap_mapping_range(struct address_space *mapping,
spin_lock(&mapping->i_mmap_lock);
- /* serialize i_size write against truncate_count write */
- smp_wmb();
- /* Protect against page faults, and endless unmapping loops */
+ /* Protect against endless unmapping loops */
mapping->truncate_count++;
- /*
- * For archs where spin_lock has inclusive semantics like ia64
- * this smp_mb() will prevent to read pagetable contents
- * before the truncate_count increment is visible to
- * other cpus.
- */
- smp_mb();
if (unlikely(is_restart_addr(mapping->truncate_count))) {
if (mapping->truncate_count == 0)
reset_vma_truncate_counts(mapping);
@@ -2008,8 +2006,18 @@ int vmtruncate(struct inode * inode, loff_t offset)
if (IS_SWAPFILE(inode))
goto out_busy;
i_size_write(inode, offset);
+
+ /*
+ * unmap_mapping_range is called twice, first simply for efficiency
+ * so that truncate_inode_pages does fewer single-page unmaps. However
+ * after this first call, and before truncate_inode_pages finishes,
+ * it is possible for private pages to be COWed, which remain after
+ * truncate_inode_pages finishes, hence the second unmap_mapping_range
+ * call must be made for correctness.
+ */
unmap_mapping_range(mapping, offset + PAGE_SIZE - 1, 0, 1);
truncate_inode_pages(mapping, offset);
+ unmap_mapping_range(mapping, offset + PAGE_SIZE - 1, 0, 1);
goto out_truncate;
do_expand:
@@ -2049,6 +2057,7 @@ int vmtruncate_range(struct inode *inode, loff_t offset, loff_t end)
down_write(&inode->i_alloc_sem);
unmap_mapping_range(mapping, offset, (end - offset), 1);
truncate_inode_pages_range(mapping, offset, end);
+ unmap_mapping_range(mapping, offset, (end - offset), 1);
inode->i_op->truncate_range(inode, offset, end);
up_write(&inode->i_alloc_sem);
mutex_unlock(&inode->i_mutex);
@@ -2206,7 +2215,6 @@ static int do_swap_page(struct mm_struct *mm, struct vm_area_struct *vma,
/* No need to invalidate - it was non-present before */
update_mmu_cache(vma, address, pte);
- lazy_mmu_prot_update(pte);
unlock:
pte_unmap_unlock(page_table, ptl);
out:
@@ -2297,10 +2305,8 @@ static int do_no_page(struct mm_struct *mm, struct vm_area_struct *vma,
int write_access)
{
spinlock_t *ptl;
- struct page *new_page;
- struct address_space *mapping = NULL;
+ struct page *page, *nopage_page;
pte_t entry;
- unsigned int sequence = 0;
int ret = VM_FAULT_MINOR;
int anon = 0;
struct page *dirty_page = NULL;
@@ -2308,74 +2314,53 @@ static int do_no_page(struct mm_struct *mm, struct vm_area_struct *vma,
pte_unmap(page_table);
BUG_ON(vma->vm_flags & VM_PFNMAP);
- if (vma->vm_file) {
- mapping = vma->vm_file->f_mapping;
- sequence = mapping->truncate_count;
- smp_rmb(); /* serializes i_size against truncate_count */
- }
-retry:
- new_page = vma->vm_ops->nopage(vma, address & PAGE_MASK, &ret);
- /*
- * No smp_rmb is needed here as long as there's a full
- * spin_lock/unlock sequence inside the ->nopage callback
- * (for the pagecache lookup) that acts as an implicit
- * smp_mb() and prevents the i_size read to happen
- * after the next truncate_count read.
- */
-
+ nopage_page = vma->vm_ops->nopage(vma, address & PAGE_MASK, &ret);
/* no page was available -- either SIGBUS, OOM or REFAULT */
- if (unlikely(new_page == NOPAGE_SIGBUS))
+ if (unlikely(nopage_page == NOPAGE_SIGBUS))
return VM_FAULT_SIGBUS;
- else if (unlikely(new_page == NOPAGE_OOM))
+ else if (unlikely(nopage_page == NOPAGE_OOM))
return VM_FAULT_OOM;
- else if (unlikely(new_page == NOPAGE_REFAULT))
+ else if (unlikely(nopage_page == NOPAGE_REFAULT))
return VM_FAULT_MINOR;
+ BUG_ON(vma->vm_flags & VM_CAN_INVALIDATE && !PageLocked(nopage_page));
+ /*
+ * For consistency in subsequent calls, make the nopage_page always
+ * locked.
+ */
+ if (unlikely(!(vma->vm_flags & VM_CAN_INVALIDATE)))
+ lock_page(nopage_page);
+
/*
* Should we do an early C-O-W break?
*/
+ page = nopage_page;
if (write_access) {
if (!(vma->vm_flags & VM_SHARED)) {
- struct page *page;
-
- if (unlikely(anon_vma_prepare(vma)))
- goto oom;
- page = alloc_page_vma(GFP_HIGHUSER_MOVABLE,
- vma, address);
- if (!page)
- goto oom;
- copy_user_highpage(page, new_page, address, vma);
- page_cache_release(new_page);
- new_page = page;
+ if (unlikely(anon_vma_prepare(vma))) {
+ ret = VM_FAULT_OOM;
+ goto out_error;
+ }
+ page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma, address);
+ if (!page) {
+ ret = VM_FAULT_OOM;
+ goto out_error;
+ }
+ copy_user_highpage(page, nopage_page, address, vma);
anon = 1;
-
} else {
/* if the page will be shareable, see if the backing
* address space wants to know that the page is about
* to become writable */
if (vma->vm_ops->page_mkwrite &&
- vma->vm_ops->page_mkwrite(vma, new_page) < 0
- ) {
- page_cache_release(new_page);
- return VM_FAULT_SIGBUS;
+ vma->vm_ops->page_mkwrite(vma, page) < 0) {
+ ret = VM_FAULT_SIGBUS;
+ goto out_error;
}
}
}
page_table = pte_offset_map_lock(mm, pmd, address, &ptl);
- /*
- * For a file-backed vma, someone could have truncated or otherwise
- * invalidated this page. If unmap_mapping_range got called,
- * retry getting the page.
- */
- if (mapping && unlikely(sequence != mapping->truncate_count)) {
- pte_unmap_unlock(page_table, ptl);
- page_cache_release(new_page);
- cond_resched();
- sequence = mapping->truncate_count;
- smp_rmb();
- goto retry;
- }
/*
* This silly early PAGE_DIRTY setting removes a race
@@ -2388,43 +2373,51 @@ static int do_no_page(struct mm_struct *mm, struct vm_area_struct *vma,
* handle that later.
*/
/* Only go through if we didn't race with anybody else... */
- if (pte_none(*page_table)) {
- flush_icache_page(vma, new_page);
- entry = mk_pte(new_page, vma->vm_page_prot);
+ if (likely(pte_none(*page_table))) {
+ flush_icache_page(vma, page);
+ entry = mk_pte(page, vma->vm_page_prot);
if (write_access)
entry = maybe_mkwrite(pte_mkdirty(entry), vma);
set_pte_at(mm, address, page_table, entry);
if (anon) {
- inc_mm_counter(mm, anon_rss);
- lru_cache_add_active(new_page);
- page_add_new_anon_rmap(new_page, vma, address);
+ inc_mm_counter(mm, anon_rss);
+ lru_cache_add_active(page);
+ page_add_new_anon_rmap(page, vma, address);
} else {
inc_mm_counter(mm, file_rss);
- page_add_file_rmap(new_page);
+ page_add_file_rmap(page);
if (write_access) {
- dirty_page = new_page;
+ dirty_page = page;
get_page(dirty_page);
}
}
+
+ /* no need to invalidate: a not-present page won't be cached */
+ update_mmu_cache(vma, address, entry);
+ lazy_mmu_prot_update(entry);
} else {
- /* One of our sibling threads was faster, back out. */
- page_cache_release(new_page);
- goto unlock;
+ if (anon)
+ page_cache_release(page);
+ else
+ anon = 1; /* not anon, but release nopage_page */
}
- /* no need to invalidate: a not-present page shouldn't be cached */
- update_mmu_cache(vma, address, entry);
- lazy_mmu_prot_update(entry);
-unlock:
pte_unmap_unlock(page_table, ptl);
- if (dirty_page) {
+
+out:
+ unlock_page(nopage_page);
+ if (anon)
+ page_cache_release(nopage_page);
+ else if (dirty_page) {
set_page_dirty_balance(dirty_page);
put_page(dirty_page);
}
+
return ret;
-oom:
- page_cache_release(new_page);
- return VM_FAULT_OOM;
+
+out_error:
+ anon = 1; /* relase nopage_page */
+ goto out;
}
/*
diff --git a/mm/shmem.c b/mm/shmem.c
index 96fa79fb6ad3..5808fadd3944 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -83,6 +83,7 @@ enum sgp_type {
SGP_READ, /* don't exceed i_size, don't allocate page */
SGP_CACHE, /* don't exceed i_size, may allocate page */
SGP_WRITE, /* may exceed i_size, may allocate page */
+ SGP_NOPAGE, /* same as SGP_CACHE, return with page locked */
};
static int shmem_getpage(struct inode *inode, unsigned long idx,
@@ -1289,8 +1290,10 @@ static int shmem_getpage(struct inode *inode, unsigned long idx,
}
done:
if (*pagep != filepage) {
- unlock_page(filepage);
*pagep = filepage;
+ if (sgp != SGP_NOPAGE)
+ unlock_page(filepage);
+
}
return 0;
@@ -1310,13 +1313,15 @@ static struct page *shmem_nopage(struct vm_area_struct *vma,
unsigned long idx;
int error;
+ BUG_ON(!(vma->vm_flags & VM_CAN_INVALIDATE));
+
idx = (address - vma->vm_start) >> PAGE_SHIFT;
idx += vma->vm_pgoff;
idx >>= PAGE_CACHE_SHIFT - PAGE_SHIFT;
if (((loff_t) idx << PAGE_CACHE_SHIFT) >= i_size_read(inode))
return NOPAGE_SIGBUS;
- error = shmem_getpage(inode, idx, &page, SGP_CACHE, type);
+ error = shmem_getpage(inode, idx, &page, SGP_NOPAGE, type);
if (error)
return (error == -ENOMEM)? NOPAGE_OOM: NOPAGE_SIGBUS;
@@ -1414,6 +1419,7 @@ static int shmem_mmap(struct file *file, struct vm_area_struct *vma)
{
file_accessed(file);
vma->vm_ops = &shmem_vm_ops;
+ vma->vm_flags |= VM_CAN_INVALIDATE;
return 0;
}
@@ -2596,5 +2602,6 @@ int shmem_zero_setup(struct vm_area_struct *vma)
fput(vma->vm_file);
vma->vm_file = file;
vma->vm_ops = &shmem_vm_ops;
+ vma->vm_flags |= VM_CAN_INVALIDATE;
return 0;
}
diff --git a/mm/truncate.c b/mm/truncate.c
index f47e46d1be3b..aed85f0b707f 100644
--- a/mm/truncate.c
+++ b/mm/truncate.c
@@ -192,6 +192,11 @@ void truncate_inode_pages_range(struct address_space *mapping,
unlock_page(page);
continue;
}
+ if (page_mapped(page)) {
+ unmap_mapping_range(mapping,
+ (loff_t)page_index<<PAGE_CACHE_SHIFT,
+ PAGE_CACHE_SIZE, 0);
+ }
truncate_complete_page(mapping, page);
unlock_page(page);
}
@@ -229,6 +234,11 @@ void truncate_inode_pages_range(struct address_space *mapping,
break;
lock_page(page);
wait_on_page_writeback(page);
+ if (page_mapped(page)) {
+ unmap_mapping_range(mapping,
+ (loff_t)page->index<<PAGE_CACHE_SHIFT,
+ PAGE_CACHE_SIZE, 0);
+ }
if (page->index > next)
next = page->index;
next++;
@@ -405,7 +415,7 @@ int invalidate_inode_pages2_range(struct address_space *mapping,
break;
}
wait_on_page_writeback(page);
- while (page_mapped(page)) {
+ if (page_mapped(page)) {
if (!did_range_unmap) {
/*
* Zap the rest of the file in one hit.
@@ -425,6 +435,7 @@ int invalidate_inode_pages2_range(struct address_space *mapping,
PAGE_CACHE_SIZE, 0);
}
}
+ BUG_ON(page_mapped(page));
ret = do_launder_page(mapping, page);
if (ret == 0 && !invalidate_complete_page2(mapping, page))
ret = -EIO;
]
|
| 68/2 |
2026/08/11 01:42 |
llm |
expert |
0m
Model:
Tokens:
input: 45992
output: 38
thoughts: 354
Thoughts: **Debugging a Persistent Mapping Issue**
Okay, so I'm digging into this `BUG_ON(page_mapped(page))` in the kernel, specifically the one that popped up after the `d00806b18315` commit back in '07. The initial assumption is clear: `unmap_mapping_range` *should* unmap the page. If it's still mapped, it's a bug, plain and simple.
But where is `unmap_mapping_range` unmapping from? Ah, from `mapping->i_mmap`. So, if `page_mapped(page)` is still true, it's either in a VMA *not* part of `mapping->i_mmap`, orβand this is the key questionβ`unmap_mapping_range` (or, by extension, `unmap_mapping_folio`) somehow failed.
Let's drill down into `unmap_mapping_folio`. Okay, I see it's setting `details.skip_cows = true`, `details.single_folio = folio`, and crucially, `details.zap_flags = ZAP_FLAG_DROP_MARKER`. Hold on a sec... `ZAP_FLAG_DROP_MARKER`? That's the bit that's likely the culprit. What precisely is that flag intended to *do*? I need to check `ZAP_FLAG_DROP_MARKER` and figure out how it might be preventing a complete unmapping. That's where the failure may be.
|
| 69/2 |
2026/08/11 01:42 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:mm/memory.c Name:ZAP_FLAG_DROP_MARKER]
Results: map[SourceCode: SourceFile:]
|
| 70/2 |
2026/08/11 01:42 |
llm |
expert |
0m
Model:
Tokens:
input: 46741
output: 31
thoughts: 16
Thoughts: Okay, here's my interpretation of that thought process, as if it were my own:
**Investigating the "Drop Marker"**
Right, time to dig into this. My first instinct is to zero in on the `ZAP_FLAG_DROP_MARKER`. That's a pretty specific term, and it sounds like a critical piece of the puzzle. I'm going to start with a `grep`. That's the most efficient way to quickly locate all the instances where this flag is defined, referenced, or used within the codebase. I need a quick overview of its context. Let's see what we can find. I'm hoping this will illuminate its purpose and how it influences the overall flow.
|
| 71/2 |
2026/08/11 01:42 |
tool |
grepper |
0m
Args: map[Expression:ZAP_FLAG_DROP_MARKER PathPrefix:mm/]
Results: map[Output:mm/hugetlb.c=5145=void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
--
mm/hugetlb.c-5204- if (pte_swp_uffd_wp_any(pte) &&
mm/hugetlb.c:5205: !(zap_flags & ZAP_FLAG_DROP_MARKER))
mm/hugetlb.c-5206- set_huge_pte_at(mm, address, ptep,
--
mm/hugetlb.c-5240- if (huge_pte_uffd_wp(pte) &&
mm/hugetlb.c:5241: !(zap_flags & ZAP_FLAG_DROP_MARKER))
mm/hugetlb.c-5242- set_huge_pte_at(mm, address, ptep,
--
mm/memory.c=1594=static inline bool zap_drop_markers(struct zap_details *details)
--
mm/memory.c-1598-
mm/memory.c:1599: return details->zap_flags & ZAP_FLAG_DROP_MARKER;
mm/memory.c-1600-}
--
mm/memory.c=2161=void unmap_vmas(struct mmu_gather *tlb, struct unmap_desc *unmap)
--
mm/memory.c-2165- struct zap_details details = {
mm/memory.c:2166: .zap_flags = ZAP_FLAG_DROP_MARKER | ZAP_FLAG_UNMAP,
mm/memory.c-2167- };
--
mm/memory.c=4372=void unmap_mapping_folio(struct folio *folio)
--
mm/memory.c-4385- details.single_folio = folio;
mm/memory.c:4386: details.zap_flags = ZAP_FLAG_DROP_MARKER;
mm/memory.c-4387-
]
|
| 72/2 |
2026/08/11 01:42 |
llm |
expert |
0m
Model:
Tokens:
input: 47471
output: 34
thoughts: 731
Thoughts: **Unraveling Potential Folio Mapping Quirks**
Okay, so I'm encountering these uffd-wp markers and I need to figure out why. My mind immediately starts running through possible scenarios. First, I consider the simple cases β could the folio be mapped within a VMA that uses `VM_PFNMAP`? No, because those VMAs don't increment the mapcount, so `page_mapped` would be false.
Next, I think, what if the folio is in a VMA that *used* to be linked to `i_mmap` but isn't anymore? Ah, `unlink_file_vma` handles that, called during `free_pgtables` or `vma_close`. If it's unlinked, the PTEs should have been zapped.
But, hang on, what if `unmap_mapping_range_tree` is the problem? Could it be missing the VMA due to `first_index` and `last_index`? What about `vm_pgoff`? If the folio's index is *outside* the VMA's range, then `unmap_mapping_range_tree` won't find it. But, how could it be mapped there in the first place? Then it hits me, `mremap`!
Does `mremap` mess with `vm_pgoff`? No, if it creates a *new* VMA or extends an existing one, the new `vm_pgoff` always stays consistent with the file offset. Wait, there's `MREMAP_DONTUNMAP`. If `MREMAP_DONTUNMAP` is used, the old VMA is left empty, and the PTEs move to the new VMA! Does this mean the new VMA has the *same* `vm_pgoff`? I need to check how `mremap` with `MREMAP_DONTUNMAP` behaves. If you do `mremap(old_addr, old_size, new_size, MREMAP_MAYMOVE | MREMAP_DONTUNMAP, new_addr)`, the page tables move to `new_addr`. The new VMA at `new_addr` will get `vm_pgoff` corresponding to the file offset, so `unmap_mapping_range_tree` should still find it.
Alright, moving on, what if a folio is mapped *multiple* times within the *same* VMA? That's impossible, a virtual address can only map one page.
Finally, what about `MAP_PRIVATE` VMAs? If it's `MAP_PRIVATE`, it's *in* `i_mmap`? No, `MAP_PRIVATE` VMAs are *not* in `i_mmap` if they are anonymous. But wait, if you `mmap(MAP_PRIVATE)` a *file*, the VMA *is* added to `mapping->i_mmap`! I'll have to double-check `vma_link` or `mmap_region`.
|
| 73/2 |
2026/08/11 01:43 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/mmap.c Name:mmap_region]
Results: map[SourceCode:2810: /**
2811: * mmap_region() - Actually perform the userland mapping of a VMA into
2812: * current->mm with known, aligned and overflow-checked @addr and @len, and
2813: * correctly determined VMA flags @vm_flags and page offset @pgoff.
2814: *
2815: * This is an internal memory management function, and should not be used
2816: * directly.
2817: *
2818: * The caller must write-lock current->mm->mmap_lock.
2819: *
2820: * @file: If a file-backed mapping, a pointer to the struct file describing the
2821: * file to be mapped, otherwise NULL.
2822: * @addr: The page-aligned address at which to perform the mapping.
2823: * @len: The page-aligned, non-zero, length of the mapping.
2824: * @vm_flags: The VMA flags which should be applied to the mapping.
2825: * @pgoff: If @file is specified, the page offset into the file, if not then
2826: * the virtual page offset in memory of the anonymous mapping.
2827: * @uf: Optionally, a pointer to a list head used for tracking userfaultfd unmap
2828: * events.
2829: *
2830: * Returns: Either an error, or the address at which the requested mapping has
2831: * been performed.
2832: */
2833: unsigned long mmap_region(struct file *file, unsigned long addr,
2834: unsigned long len, vm_flags_t vm_flags,
2835: unsigned long pgoff, struct list_head *uf)
2836: {
2837: unsigned long ret;
2838: bool writable_file_mapping = false;
2839: const vma_flags_t vma_flags = legacy_to_vma_flags(vm_flags);
2840:
2841: mmap_assert_write_locked(current->mm);
2842:
2843: /* Check to see if MDWE is applicable. */
2844: if (map_deny_write_exec(&vma_flags, &vma_flags))
2845: return -EACCES;
2846:
2847: /* Allow architectures to sanity-check the vm_flags. */
2848: if (!arch_validate_flags(vm_flags))
2849: return -EINVAL;
2850:
2851: /* Map writable and ensure this isn't a sealed memfd. */
2852: if (file && is_shared_maywrite(&vma_flags)) {
2853: int error = mapping_map_writable(file->f_mapping);
2854:
2855: if (error)
2856: return error;
2857: writable_file_mapping = true;
2858: }
2859:
2860: ret = __mmap_region(file, addr, len, vma_flags, pgoff, uf);
2861:
2862: /* Clear our write mapping regardless of error. */
2863: if (writable_file_mapping)
2864: mapping_unmap_writable(file->f_mapping);
2865:
2866: validate_mm(current->mm);
2867: return ret;
2868: }
SourceFile:mm/vma.c]
|
| 74/2 |
2026/08/11 01:43 |
llm |
expert |
0m
Model:
Tokens:
input: 49891
output: 35
thoughts: 11
Thoughts: Okay, here's my attempt at summarizing that thought process, keeping in mind I'm speaking to a fellow expert:
**Investigating `__mmap_region`**
Alright, time to dive in. My immediate thought is, "Let's check `__mmap_region`." I'm mentally parsing where I know that function lives within the architecture. Is it a core kernel function? Part of a specific library? Understanding its context is crucial before I even start digging into the details. I'm already anticipating the potential complexities β is it dealing with memory mapping for files, anonymous pages, shared memory? That initial keyword gives me a direct line of investigation. Time to get to work.
|
| 75/2 |
2026/08/11 01:43 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/vma.c Name:__mmap_region]
Results: map[SourceCode:2734: static unsigned long __mmap_region(struct file *file, unsigned long addr,
2735: unsigned long len, vma_flags_t vma_flags,
2736: unsigned long pgoff, struct list_head *uf)
2737: {
2738: struct mm_struct *mm = current->mm;
2739: struct vm_area_struct *vma = NULL;
2740: bool have_mmap_prepare = file && file->f_op->mmap_prepare;
2741: VMA_ITERATOR(vmi, mm, addr);
2742: MMAP_STATE(map, mm, &vmi, addr, len, pgoff, vma_flags, file);
2743: struct vm_area_desc desc = {
2744: .mm = mm,
2745: .file = file,
2746: .action = {
2747: .type = MMAP_NOTHING, /* Default to no further action. */
2748: },
2749: .vm_ops = &vma_dummy_vm_ops,
2750: };
2751: bool allocated_new = false;
2752: int error;
2753:
2754: map.check_ksm_early = can_set_ksm_flags_early(&map);
2755:
2756: error = __mmap_setup(&map, &desc, uf);
2757: if (!error && have_mmap_prepare)
2758: error = call_mmap_prepare(&map, &desc);
2759: if (error)
2760: goto abort_munmap;
2761:
2762: if (map.check_ksm_early)
2763: update_ksm_flags(&map);
2764:
2765: /* Attempt to merge with adjacent VMAs... */
2766: if (map.prev || map.next) {
2767: VMG_MMAP_STATE(vmg, &map, /* vma = */ NULL);
2768:
2769: vma = vma_merge_new_range(&vmg);
2770: }
2771:
2772: /* ...but if we can't, allocate a new VMA. */
2773: if (!vma) {
2774: error = __mmap_new_vma(&map, &vma, &desc.action);
2775: if (error)
2776: goto unacct_error;
2777: allocated_new = true;
2778: }
2779:
2780: if (have_mmap_prepare)
2781: set_vma_user_defined_fields(vma, &map);
2782:
2783: __mmap_complete(&map, vma);
2784:
2785: if (have_mmap_prepare && allocated_new) {
2786: error = mmap_action_complete(vma, &desc.action,
2787: /*is_compat=*/false);
2788: if (error)
2789: return error;
2790: }
2791:
2792: return addr;
2793:
2794: /* Accounting was done by __mmap_setup(). */
2795: unacct_error:
2796: if (map.charged)
2797: vm_unacct_memory(map.charged);
2798: abort_munmap:
2799: /*
2800: * This indicates that .mmap_prepare has set a new file, differing from
2801: * desc->vm_file. But since we're aborting the operation, only the
2802: * original file will be cleaned up. Ensure we clean up both.
2803: */
2804: if (map.file_doesnt_need_get)
2805: fput(map.file);
2806: vms_abort_munmap_vmas(&map.vms, &map.mas_detach);
2807: return error;
2808: }
SourceFile:mm/vma.c]
|
| 76/2 |
2026/08/11 01:43 |
llm |
expert |
0m
Model:
Tokens:
input: 51277
output: 34
thoughts: 935
Thoughts: **Unraveling VMA, Folio, and Mapping Interactions**
Okay, let's break this down. My initial thought process is centered around how memory mapped areas (VMAs) interact with file mappings and folios, especially in the context of `MAP_PRIVATE`. I was thinking about the `__mmap_new_vma` call and how it links VMAs into the `i_mmap` structure through `vma_link`. The key observation is that even `MAP_PRIVATE` VMAs *are* added to `i_mmap` if they are associated with a file (`vma->vm_file`). This happens because `vma_link` checks for `vma->vm_file` and, if it exists, adds it to `vma->vm_file->f_mapping->i_mmap`. So, `MAP_PRIVATE` mappings *are* represented there.
This has immediate implications. If these `MAP_PRIVATE` areas are in `i_mmap`, `unmap_mapping_range_tree` will find them and unmap the folio. However, with `MAP_PRIVATE`, the scenario is different. If the folio is *not* Copy-on-Write (COW), it's mapped read-only. `unmap_mapping_range_tree` will then unmap it. If it *is* COW, the Page Table Entry (PTE) points to an anonymous folio, and `unmap_mapping_range_tree` will skip it because of the `details.skip_cows = true` flag. But then the initial folio's mapcount should have been decremented when the COW occurred, so it shouldn't be mapped any more, really.
My mind then wanders: what if the VMA's `vm_file` changes after `mmap`? Is that even possible? No, it seems that `vma->vm_file` is immutable.
My train of thought then pivoted to a more complex scenario. What if a folio is mapped in a VMA that belongs to a *different* file? How can a folio from file A be associated with a VMA for file B? The answer could be within the `fault` handler. If the `fault` handler for a given VMA returns a folio originating from a different file, we have a problem.
I started listing potential scenarios where this might happen. Overlayfs came to mind as it can map the lower/upper file directly. So the VMA's `vm_file` would be the lower/upper file. Then I moved onto other possibilities: `shmem`, `binder`, the `loop` device.
The `loop` device example was interesting. If I `mmap` a loop device, the VMA's `vm_file` is `/dev/loop0`. I wondered about the `fault` handler for `/dev/loop0`. Turns out that it uses `blkdev_default_mmap` and `generic_file_vm_ops`. The `fault` handler defaults to `filemap_fault`. `filemap_fault` then looks up the folio in `vma->vm_file->f_mapping`, which maps to the loop device's own mapping.
However, the loop device is backed by a file (e.g., `foo.img`). It reads from the backing file into *its own* page cache. This means the folios in `/dev/loop0`'s mapping are *different* from the folios in `foo.img`'s mapping. Therefore, they don't share folios.
Next, I thought: what if a filesystem uses `pagecache_get_page` or `filemap_fault` on a *different* mapping? Is there something where it's possible? `ext4` DAX doesn't use folios. What about `fscache` / `cachefiles`? Or `cramfs` or `romfs`? EROFS? EROFS shares pages between files, particularly if deduplicated, but that's read-only. So it shouldn't be what we are dealing with.
I then returned to the `blkdev_direct_write` use case, which involves writes to a block device. Block device mappings are unique to the block device. Ah, but what about a partition? Could partitions share the same mapping as the whole disk? For instance, `/dev/sda1` and `/dev/sda`?
It struck me: opening `/dev/sda1` means its `f_mapping` is the block device mapping for that partition. And then it hit me! Block devices for partitions share the *same* mapping as the whole disk. Time to check `bdev_mapping`.
|
| 77/2 |
2026/08/11 01:43 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:block/bdev.c Name:bdev_mapping]
Results: map[SourceCode: SourceFile:]
|
| 78/2 |
2026/08/11 01:43 |
llm |
expert |
0m
Model:
Tokens:
input: 53280
output: 28
thoughts: 10
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Diving into `blkdev_open`**
Alright, time to dig into this. "Let's check `blkdev_open`." My brain immediately jumps to the kernel's block device open function. That function is the core of how we access and initialize block devices, making them ready for use by higher layers like the filesystem. I need to understand its role. What are the key elements to examine here, considering my work?
First, I will review the associated error handling. What mechanisms are in place? Are there any potential race conditions I should be mindful of? How can I ensure correct and safe operation?
I want to look for potential issues with its interaction with the storage stack. Is there an over-reliance on any specific device characteristics? Are any assumptions made that might break down in certain edge cases? Also, I will assess whether the current implementation correctly handles any asynchronous operations. How are concurrency and locking managed? This is critical for performance and data integrity. Any recent updates, bug reports, or related discussions in the mailing list?
|
| 79/2 |
2026/08/11 01:43 |
tool |
grepper |
0m
Error:
Args: map[Expression:inode->i_mapping = PathPrefix:block/]
Results:
|
| 80/2 |
2026/08/11 01:43 |
llm |
expert |
0m
Model:
Tokens:
input: 53576
output: 26
thoughts: 20
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Investigating Block Device Mapping**
Alright, time to get my hands dirty. I need to understand how the `bdev->bd_inode->i_mapping` is being configured. This is a crucial link in the chain, right? I want to trace the setup of the address space mapping for this block device. I need to see exactly where the `i_mapping` is being assigned, and by what mechanism. Is it happening during device registration? Through some sort of filesystem interaction? Or maybe directly in the block layer somewhere? I need to understand the lifecycle of that particular field and any dependencies. Iβll start by digging into the initialization paths of the `bdev` structure itself and its associated `bd_inode`. The goal is to identify the source and context of the mapping's initialization.
|
| 81/2 |
2026/08/11 01:43 |
tool |
grepper |
0m
Error:
Args: map[Expression:i_mapping = PathPrefix:block/]
Results:
|
| 82/2 |
2026/08/11 01:43 |
llm |
expert |
0m
Model:
Tokens:
input: 53821
output: 25
thoughts: 15
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Block Device Initialization: A Deep Dive**
Okay, so I'm thinking about the block device layer, and where these things actually get created. It hits me β it's gotta be in `block/bdev.c`. That's the logical place, isn't it? The core of the block device management is handled there. So, when I need to trace back from high level calls like device mounting and unmounting, or lower level calls like disk I/O, I'll be sure to start my examination there.
|
| 83/2 |
2026/08/11 01:43 |
tool |
grepper |
0m
Args: map[Expression:i_mapping PathPrefix:block/]
Results: map[Output:block/bdev.c=140=static void set_init_blocksize(struct block_device *bdev)
--
block/bdev.c-150- BD_INODE(bdev)->i_blkbits = blksize_bits(bsize);
block/bdev.c:151: mapping_set_folio_min_order(BD_INODE(bdev)->i_mapping,
block/bdev.c-152- get_order(bsize));
--
block/bdev.c=178=int set_blocksize(struct file *file, int size)
--
block/bdev.c-203- inode_lock(inode);
block/bdev.c:204: filemap_invalidate_lock(inode->i_mapping);
block/bdev.c-205-
--
block/bdev.c-209- inode->i_blkbits = blksize_bits(size);
block/bdev.c:210: mapping_set_folio_min_order(inode->i_mapping, get_order(size));
block/bdev.c:211: filemap_invalidate_unlock(inode->i_mapping);
block/bdev.c-212- inode_unlock(inode);
--
block/bdev.c=519=long nr_blockdev_pages(void)
--
block/bdev.c-525- list_for_each_entry(inode, &blockdev_superblock->s_inodes, i_sb_list)
block/bdev.c:526: ret += inode->i_mapping->nrpages;
block/bdev.c-527- spin_unlock(&blockdev_superblock->s_inode_list_lock);
--
block/bdev.c=1267=void sync_bdevs(bool wait)
--
block/bdev.c-1272- list_for_each_entry(inode, &blockdev_superblock->s_inodes, i_sb_list) {
block/bdev.c:1273: struct address_space *mapping = inode->i_mapping;
block/bdev.c-1274- struct block_device *bdev;
--
block/bdev.c-1306- */
block/bdev.c:1307: filemap_fdatawait_keep_errors(inode->i_mapping);
block/bdev.c-1308- } else {
block/bdev.c:1309: filemap_fdatawrite(inode->i_mapping);
block/bdev.c-1310- }
--
block/fops.c=843=static long blkdev_fallocate(struct file *file, int mode, loff_t start,
--
block/fops.c-882- inode_lock(inode);
block/fops.c:883: filemap_invalidate_lock(inode->i_mapping);
block/fops.c-884-
--
block/fops.c-911- fail:
block/fops.c:912: filemap_invalidate_unlock(inode->i_mapping);
block/fops.c-913- inode_unlock(inode);
]
|
| 84/2 |
2026/08/11 01:43 |
llm |
expert |
0m
Model:
Tokens:
input: 54821
output: 27
thoughts: 54
Thoughts: Okay, here's my take on those thoughts, framed as my own:
**Reflecting on Block Device Mappings in Linux**
Hold on a second... I'm just revisiting a point that's been rattling around in the back of my mind. In the "old days," as I recall, within a Linux environment, there was a clear distinction in how the system handled block devices at the partition level versus the entire disk level. They had distinct inodes and completely separate mappings, didn't they? That was just how it worked. I could always rely on that.
But, wasn't there a shift, a change that took place a few years back? I'm getting a mental ping that the whole approach to mapping has been consolidated. Now, isn't it true that all block devices pointing to the *same* physical disk now share the *exact same* mapping? I think that's right, yeah, there was a shift towards that more unified view.
Okay, I'm going to follow my nose here, this warrants investigation. I should check `bdev_mapping`. I'm going to have to dive into that data structure, make sure I am on the right track!
|
| 85/2 |
2026/08/11 01:43 |
tool |
grepper |
0m
Args: map[Expression:mapping PathPrefix:block/bdev.c]
Results: map[Output:block/bdev.c=58=struct block_device *file_bdev(struct file *bdev_file)
block/bdev.c-59-{
block/bdev.c:60: return I_BDEV(bdev_file->f_mapping->host);
block/bdev.c-61-}
--
block/bdev.c=83=static void kill_bdev(struct block_device *bdev)
block/bdev.c-84-{
block/bdev.c:85: struct address_space *mapping = bdev->bd_mapping;
block/bdev.c-86-
block/bdev.c:87: if (mapping_empty(mapping))
block/bdev.c-88- return;
--
block/bdev.c-90- invalidate_bh_lrus();
block/bdev.c:91: truncate_inode_pages(mapping, 0);
block/bdev.c-92-}
--
block/bdev.c=95=void invalidate_bdev(struct block_device *bdev)
block/bdev.c-96-{
block/bdev.c:97: struct address_space *mapping = bdev->bd_mapping;
block/bdev.c-98-
block/bdev.c:99: if (mapping->nrpages) {
block/bdev.c-100- invalidate_bh_lrus();
block/bdev.c-101- lru_add_drain_all(); /* make sure all lru add caches are flushed */
block/bdev.c:102: invalidate_mapping_pages(mapping, 0, -1);
block/bdev.c-103- }
--
block/bdev.c=111=int truncate_bdev_range(struct block_device *bdev, blk_mode_t mode,
--
block/bdev.c-124-
block/bdev.c:125: truncate_inode_pages_range(bdev->bd_mapping, lstart, lend);
block/bdev.c-126- if (!(mode & BLK_OPEN_EXCL))
--
block/bdev.c-134- */
block/bdev.c:135: return invalidate_inode_pages2_range(bdev->bd_mapping,
block/bdev.c-136- lstart >> PAGE_SHIFT,
--
block/bdev.c=140=static void set_init_blocksize(struct block_device *bdev)
--
block/bdev.c-150- BD_INODE(bdev)->i_blkbits = blksize_bits(bsize);
block/bdev.c:151: mapping_set_folio_min_order(BD_INODE(bdev)->i_mapping,
block/bdev.c-152- get_order(bsize));
--
block/bdev.c=178=int set_blocksize(struct file *file, int size)
block/bdev.c-179-{
block/bdev.c:180: struct inode *inode = file->f_mapping->host;
block/bdev.c-181- struct block_device *bdev = I_BDEV(inode);
--
block/bdev.c-194- * Flush and truncate the pagecache before we reconfigure the
block/bdev.c:195: * mapping geometry because folio sizes are variable now. If a
block/bdev.c-196- * reader has already allocated a folio whose size is smaller
--
block/bdev.c-203- inode_lock(inode);
block/bdev.c:204: filemap_invalidate_lock(inode->i_mapping);
block/bdev.c-205-
--
block/bdev.c-209- inode->i_blkbits = blksize_bits(size);
block/bdev.c:210: mapping_set_folio_min_order(inode->i_mapping, get_order(size));
block/bdev.c:211: filemap_invalidate_unlock(inode->i_mapping);
block/bdev.c-212- inode_unlock(inode);
--
block/bdev.c=260=int sync_blockdev_nowait(struct block_device *bdev)
--
block/bdev.c-263- return 0;
block/bdev.c:264: return filemap_flush(bdev->bd_mapping);
block/bdev.c-265-}
block/bdev.c=266=EXPORT_SYMBOL_GPL(sync_blockdev_nowait);
--
block/bdev.c-269- * Write out and wait upon all the dirty data associated with a block
block/bdev.c:270: * device via its mapping. Does not take the superblock lock.
block/bdev.c-271- */
block/bdev.c=272=int sync_blockdev(struct block_device *bdev)
--
block/bdev.c-275- return 0;
block/bdev.c:276: return filemap_write_and_wait(bdev->bd_mapping);
block/bdev.c-277-}
--
block/bdev.c=280=int sync_blockdev_range(struct block_device *bdev, loff_t lstart, loff_t lend)
block/bdev.c-281-{
block/bdev.c:282: return filemap_write_and_wait_range(bdev->bd_mapping,
block/bdev.c-283- lstart, lend);
--
block/bdev.c=459=struct block_device *bdev_alloc(struct gendisk *disk, u8 partno)
--
block/bdev.c-469- inode->i_data.a_ops = &def_blk_aops;
block/bdev.c:470: mapping_set_gfp_mask(&inode->i_data, GFP_USER);
block/bdev.c-471-
--
block/bdev.c-476- atomic_set(&bdev->__bd_flags, partno);
block/bdev.c:477: bdev->bd_mapping = &inode->i_data;
block/bdev.c-478- bdev->bd_queue = disk->queue;
--
block/bdev.c=498=void bdev_add(struct block_device *bdev, dev_t dev)
--
block/bdev.c-501- if (bdev_stable_writes(bdev))
block/bdev.c:502: mapping_set_stable_writes(bdev->bd_mapping);
block/bdev.c-503- bdev->bd_dev = dev;
--
block/bdev.c=519=long nr_blockdev_pages(void)
--
block/bdev.c-525- list_for_each_entry(inode, &blockdev_superblock->s_inodes, i_sb_list)
block/bdev.c:526: ret += inode->i_mapping->nrpages;
block/bdev.c-527- spin_unlock(&blockdev_superblock->s_inode_list_lock);
--
block/bdev.c=676=static void bd_end_claim(struct block_device *bdev, void *holder)
--
block/bdev.c-710-
block/bdev.c:711:static void blkdev_flush_mapping(struct block_device *bdev)
block/bdev.c-712-{
--
block/bdev.c=719=static void blkdev_put_whole(struct block_device *bdev)
--
block/bdev.c-721- if (atomic_dec_and_test(&bdev->bd_openers))
block/bdev.c:722: blkdev_flush_mapping(bdev);
block/bdev.c-723- if (bdev->bd_disk->fops->release)
--
block/bdev.c=810=static void blkdev_put_part(struct block_device *part)
--
block/bdev.c-814- if (atomic_dec_and_test(&part->bd_openers)) {
block/bdev.c:815: blkdev_flush_mapping(part);
block/bdev.c-816- whole->bd_disk->open_partitions--;
--
block/bdev.c=889=static inline bool bdev_unclaimed(const struct file *bdev_file)
block/bdev.c-890-{
block/bdev.c:891: return bdev_file->private_data == BDEV_I(bdev_file->f_mapping->host);
block/bdev.c-892-}
--
block/bdev.c=929=int bdev_open(struct block_device *bdev, blk_mode_t mode, void *holder,
--
block/bdev.c-991- bdev_file->f_mode |= FMODE_WRITE_RESTRICTED;
block/bdev.c:992: bdev_file->f_mapping = bdev->bd_mapping;
block/bdev.c:993: bdev_file->f_wb_err = filemap_sample_wb_err(bdev_file->f_mapping);
block/bdev.c-994- bdev_file->private_data = holder;
--
block/bdev.c=1163=void bdev_fput(struct file *bdev_file)
--
block/bdev.c-1179- */
block/bdev.c:1180: bdev_file->private_data = BDEV_I(bdev_file->f_mapping->host);
block/bdev.c-1181- mutex_unlock(&disk->open_mutex);
--
block/bdev.c=1226=EXPORT_SYMBOL(lookup_bdev);
--
block/bdev.c-1238- * and writes back inodes and then invalidates any cached data in the inodes on
block/bdev.c:1239: * the file system. In addition we also invalidate the block device mapping.
block/bdev.c-1240- */
--
block/bdev.c=1267=void sync_bdevs(bool wait)
--
block/bdev.c-1272- list_for_each_entry(inode, &blockdev_superblock->s_inodes, i_sb_list) {
block/bdev.c:1273: struct address_space *mapping = inode->i_mapping;
block/bdev.c-1274- struct block_device *bdev;
--
block/bdev.c-1277- if (inode_state_read(inode) & (I_FREEING | I_WILL_FREE | I_NEW) ||
block/bdev.c:1278: mapping->nrpages == 0) {
block/bdev.c-1279- spin_unlock(&inode->i_lock);
--
block/bdev.c-1301- /*
block/bdev.c:1302: * We keep the error status of individual mapping so
block/bdev.c-1303- * that applications can catch the writeback error using
--
block/bdev.c-1306- */
block/bdev.c:1307: filemap_fdatawait_keep_errors(inode->i_mapping);
block/bdev.c-1308- } else {
block/bdev.c:1309: filemap_fdatawrite(inode->i_mapping);
block/bdev.c-1310- }
]
|
| 86/2 |
2026/08/11 01:43 |
llm |
expert |
0m
Model:
Tokens:
input: 58201
output: 39
thoughts: 531
Thoughts: **Analyzing Block Device and Folio Sharing**
Okay, let me think this through. `bdev->bd_mapping = &inode->i_data;` ... right. And `inode` is allocated during `bdev_alloc`. So each block device, like `/dev/sda1` and `/dev/sda`, gets its *own* `inode`. Hmm... that means they each have their own `bd_mapping` as well. Interesting!
So, if `/dev/sda1` has its own `inode` and `bd_mapping`, and `/dev/sda` has its own `inode` and `bd_mapping`, then they *don't* share folios! Makes sense. Therefore, reading from `/dev/sda1` would populate folios within `/dev/sda1`'s mapping. And reading from `/dev/sda` would put them in *its* mapping.
If that's the case, `unmap_mapping_folio` called on `/dev/sda`'s mapping wouldn't touch folios that originated from `/dev/sda1`'s mapping. But wait... if the folio *isn't* shared, then `folio->mapping` must be `/dev/sda`'s mapping, right? If the folio is mapped within a VMA, that VMA must live inside `/dev/sda`'s `i_mmap`. So that's good! So, `unmap_mapping_folio` *should* unmap it!
Hold on, what if the VMA *is* in `i_mmap`, but `unmap_mapping_range_tree` doesn't unmap it because `folio_mapped(folio)` is true, but that's because of a page table entry that isn't within the VMA's range? How could that happen? Is there an edge case?
Could `mremap` be responsible? No, `mremap` updates the VMA's range. But, what if `mremap` is used with `MREMAP_DONTUNMAP`? That creates a *new* VMA! That VMA is *also* added to `i_mmap`. Therefore, `unmap_mapping_range_tree` *will* iterate over BOTH VMAs! It should unmap the folio from BOTH VMAs! Okay, that's good.
Wait a second. What if the folio is mapped in a VMA, but that VMA is *currently* being torn down? If `munmap` is called, does it remove the VMA from `i_mmap` *before* unmapping the PTEs? Let me check `do_vmi_align_munmap` or `vms_clear_ptes`.
|
| 87/2 |
2026/08/11 01:43 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/mmap.c Name:do_vmi_align_munmap]
Results: map[SourceCode:1569: /*
1570: * do_vmi_align_munmap() - munmap the aligned region from @start to @end.
1571: * @vmi: The vma iterator
1572: * @vma: The starting vm_area_struct
1573: * @mm: The mm_struct
1574: * @start: The aligned start address to munmap.
1575: * @end: The aligned end address to munmap.
1576: * @uf: The userfaultfd list_head
1577: * @unlock: Set to true to drop the mmap_lock. unlocking only happens on
1578: * success.
1579: *
1580: * Return: 0 on success and drops the lock if so directed, error and leaves the
1581: * lock held otherwise.
1582: */
1583: int do_vmi_align_munmap(struct vma_iterator *vmi, struct vm_area_struct *vma,
1584: struct mm_struct *mm, unsigned long start, unsigned long end,
1585: struct list_head *uf, bool unlock)
1586: {
1587: struct maple_tree mt_detach;
1588: MA_STATE(mas_detach, &mt_detach, 0, 0);
1589: mt_init_flags(&mt_detach, vmi->mas.tree->ma_flags & MT_FLAGS_LOCK_MASK);
1590: mt_on_stack(mt_detach);
1591: struct vma_munmap_struct vms;
1592: int error;
1593:
1594: init_vma_munmap(&vms, vmi, vma, start, end, uf, unlock);
1595: error = vms_gather_munmap_vmas(&vms, &mas_detach);
1596: if (error)
1597: goto gather_failed;
1598:
1599: error = vma_iter_clear_gfp(vmi, start, end, GFP_KERNEL);
1600: if (error)
1601: goto clear_tree_failed;
1602:
1603: /* Point of no return */
1604: vms_complete_munmap_vmas(&vms, &mas_detach);
1605: return 0;
1606:
1607: clear_tree_failed:
1608: reattach_vmas(&mas_detach);
1609: gather_failed:
1610: validate_mm(mm);
1611: return error;
1612: }
SourceFile:mm/vma.c]
|
| 88/2 |
2026/08/11 01:43 |
llm |
expert |
0m
Model:
Tokens:
input: 60124
output: 40
thoughts: 16
Thoughts: Okay, here's my attempt at summarizing that thought process, tailored for an expert audience:
**Examining `vms_complete_munmap_vmas`**
Alright, let's dive into this. I'm going to take a look at `vms_complete_munmap_vmas`. I need to understand its role in the overall memory management scheme, particularly in the context of unmapping operations. What specific scenarios is this function responsible for handling? I'm expecting it probably deals with the final cleanup steps after a range of virtual memory areas have been unmapped β making sure all resources are properly released, any necessary page table entries are cleared, and any associated structures are updated. My initial thought is it is likely concerned with a finalization function that is triggered as part of the unmap process. Is it dealing with contiguous or fragmented VMA ranges? Are there any special considerations for shared memory regions or huge pages within its purview? I need to ascertain if it interacts directly with the page cache, swap space, or any other critical kernel subsystems. I'm looking for its control flow: How does it get invoked? What are its inputs and outputs? Does it interact with locking mechanisms; what race conditions might be present? I'm going to have to trace its call stack and cross-reference it with the VM subsystem's data structures, specifically the `vm_area_struct` and `mm_struct`, and maybe even the page table structures. Understanding `vms_complete_munmap_vmas`'s role is critical to diagnosing potential memory leaks, performance bottlenecks, or even security vulnerabilities related to memory management. I suspect there may be subtle interactions with the garbage collection or NUMA policies that need some further inspection.
|
| 89/2 |
2026/08/11 01:43 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/vma.c Name:vms_complete_munmap_vmas]
Results: map[SourceCode:1321: /*
1322: * vms_complete_munmap_vmas() - Finish the munmap() operation
1323: * @vms: The vma munmap struct
1324: * @mas_detach: The maple state of the detached vmas
1325: *
1326: * This updates the mm_struct, unmaps the region, frees the resources
1327: * used for the munmap() and may downgrade the lock - if requested. Everything
1328: * needed to be done once the vma maple tree is updated.
1329: */
1330: static void vms_complete_munmap_vmas(struct vma_munmap_struct *vms,
1331: struct ma_state *mas_detach)
1332: {
1333: struct vm_area_struct *vma;
1334: struct mm_struct *mm;
1335:
1336: mm = current->mm;
1337: mm->map_count -= vms->vma_count;
1338: mm->locked_vm -= vms->locked_vm;
1339: if (vms->unlock)
1340: mmap_write_downgrade(mm);
1341:
1342: if (!vms->nr_pages)
1343: return;
1344:
1345: vms_clear_ptes(vms, mas_detach, !vms->unlock);
1346: /* Update high watermark before we lower total_vm */
1347: update_hiwater_vm(mm);
1348: /* Stat accounting */
1349: WRITE_ONCE(mm->total_vm, READ_ONCE(mm->total_vm) - vms->nr_pages);
1350: /* Paranoid bookkeeping */
1351: VM_WARN_ON(vms->exec_vm > mm->exec_vm);
1352: VM_WARN_ON(vms->stack_vm > mm->stack_vm);
1353: VM_WARN_ON(vms->data_vm > mm->data_vm);
1354: mm->exec_vm -= vms->exec_vm;
1355: mm->stack_vm -= vms->stack_vm;
1356: mm->data_vm -= vms->data_vm;
1357:
1358: /* Remove and clean up vmas */
1359: mas_set(mas_detach, 0);
1360: mas_for_each(mas_detach, vma, ULONG_MAX)
1361: remove_vma(vma);
1362:
1363: vm_unacct_memory(vms->nr_accounted);
1364: validate_mm(mm);
1365: if (vms->unlock)
1366: mmap_read_unlock(mm);
1367:
1368: __mt_destroy(mas_detach->tree);
1369: }
SourceFile:mm/vma.c]
|
| 90/2 |
2026/08/11 01:43 |
llm |
expert |
0m
Model:
Tokens:
input: 61394
output: 40
thoughts: 64
Thoughts: **Analyzing Memory Management During Unmapping**
Okay, so I'm diving into this unmapping process. I see `vms_clear_ptes` in the mix, and that's the part that actually clears the Page Table Entries, which is what I'd expect. But then I'm thinking, hold on a second... what about `vms_gather_munmap_vmas`? I need to remember that this function might have already done some work on the Virtual Memory Areas. Specifically, I'm concerned that it may have already unlinked the VMAs from `i_mmap`. So the question in my mind is, has that linking already been dealt with and is there a race condition? I need to double-check `vms_gather_munmap_vmas` to be absolutely certain of the flow and how things are being handled in relation to `i_mmap`. Gotta make sure I have all the pieces of this unmapping puzzle.
|
| 91/2 |
2026/08/11 01:43 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/vma.c Name:vms_gather_munmap_vmas]
Results: map[SourceCode:1388: /*
1389: * vms_gather_munmap_vmas() - Put all VMAs within a range into a maple tree
1390: * for removal at a later date. Handles splitting first and last if necessary
1391: * and marking the vmas as isolated.
1392: *
1393: * @vms: The vma munmap struct
1394: * @mas_detach: The maple state tracking the detached tree
1395: *
1396: * Return: 0 on success, error otherwise
1397: */
1398: static int vms_gather_munmap_vmas(struct vma_munmap_struct *vms,
1399: struct ma_state *mas_detach)
1400: {
1401: struct vm_area_struct *next = NULL;
1402: int error;
1403:
1404: /*
1405: * If we need to split any vma, do it now to save pain later.
1406: * Does it split the first one?
1407: */
1408: if (vms->start > vms->vma->vm_start) {
1409:
1410: /*
1411: * Make sure that map_count on return from munmap() will
1412: * not exceed its limit; but let map_count go just above
1413: * its limit temporarily, to help free resources as expected.
1414: */
1415: if (vms->end < vms->vma->vm_end &&
1416: vms->vma->vm_mm->map_count >= get_sysctl_max_map_count()) {
1417: error = -ENOMEM;
1418: goto map_count_exceeded;
1419: }
1420:
1421: /* Don't bother splitting the VMA if we can't unmap it anyway */
1422: if (vma_is_sealed(vms->vma)) {
1423: error = -EPERM;
1424: goto start_split_failed;
1425: }
1426:
1427: error = __split_vma(vms->vmi, vms->vma, vms->start, 1);
1428: if (error)
1429: goto start_split_failed;
1430: }
1431: vms->prev = vma_prev(vms->vmi);
1432: if (vms->prev)
1433: vms->unmap_start = vms->prev->vm_end;
1434:
1435: /*
1436: * Detach a range of VMAs from the mm. Using next as a temp variable as
1437: * it is always overwritten.
1438: */
1439: for_each_vma_range(*(vms->vmi), next, vms->end) {
1440: long nrpages;
1441:
1442: if (vma_is_sealed(next)) {
1443: error = -EPERM;
1444: goto modify_vma_failed;
1445: }
1446: /* Does it split the end? */
1447: if (next->vm_end > vms->end) {
1448: error = __split_vma(vms->vmi, next, vms->end, 0);
1449: if (error)
1450: goto end_split_failed;
1451: }
1452: vma_start_write(next);
1453: mas_set(mas_detach, vms->vma_count++);
1454: error = mas_store_gfp(mas_detach, next, GFP_KERNEL);
1455: if (error)
1456: goto munmap_gather_failed;
1457:
1458: vma_mark_detached(next);
1459: nrpages = vma_pages(next);
1460:
1461: vms->nr_pages += nrpages;
1462: if (vma_test(next, VMA_LOCKED_BIT))
1463: vms->locked_vm += nrpages;
1464:
1465: if (vma_test(next, VMA_ACCOUNT_BIT))
1466: vms->nr_accounted += nrpages;
1467:
1468: if (is_exec_mapping(next->vm_flags))
1469: vms->exec_vm += nrpages;
1470: else if (is_stack_mapping(next->vm_flags))
1471: vms->stack_vm += nrpages;
1472: else if (is_data_mapping_vma_flags(&next->flags))
1473: vms->data_vm += nrpages;
1474:
1475: if (vms->uf) {
1476: /*
1477: * If userfaultfd_unmap_prep returns an error the vmas
1478: * will remain split, but userland will get a
1479: * highly unexpected error anyway. This is no
1480: * different than the case where the first of the two
1481: * __split_vma fails, but we don't undo the first
1482: * split, despite we could. This is unlikely enough
1483: * failure that it's not worth optimizing it for.
1484: */
1485: error = userfaultfd_unmap_prep(next, vms->start,
1486: vms->end, vms->uf);
1487: if (error)
1488: goto userfaultfd_error;
1489: }
1490: #ifdef CONFIG_DEBUG_VM_MAPLE_TREE
1491: BUG_ON(next->vm_start < vms->start);
1492: BUG_ON(next->vm_start > vms->end);
1493: #endif
1494: }
1495:
1496: vms->next = vma_next(vms->vmi);
1497: if (vms->next)
1498: vms->unmap_end = vms->next->vm_start;
1499:
1500: #if defined(CONFIG_DEBUG_VM_MAPLE_TREE)
1501: /* Make sure no VMAs are about to be lost. */
1502: {
1503: MA_STATE(test, mas_detach->tree, 0, 0);
1504: struct vm_area_struct *vma_mas, *vma_test;
1505: int test_count = 0;
1506:
1507: vma_iter_set(vms->vmi, vms->start);
1508: rcu_read_lock();
1509: vma_test = mas_find(&test, vms->vma_count - 1);
1510: for_each_vma_range(*(vms->vmi), vma_mas, vms->end) {
1511: BUG_ON(vma_mas != vma_test);
1512: test_count++;
1513: vma_test = mas_next(&test, vms->vma_count - 1);
1514: }
1515: rcu_read_unlock();
1516: BUG_ON(vms->vma_count != test_count);
1517: }
1518: #endif
1519:
1520: while (vma_iter_addr(vms->vmi) > vms->start)
1521: vma_iter_prev_range(vms->vmi);
1522:
1523: vms->clear_ptes = true;
1524: return 0;
1525:
1526: userfaultfd_error:
1527: munmap_gather_failed:
1528: end_split_failed:
1529: modify_vma_failed:
1530: reattach_vmas(mas_detach);
1531: start_split_failed:
1532: map_count_exceeded:
1533: return error;
1534: }
SourceFile:mm/vma.c]
|
| 92/2 |
2026/08/11 01:43 |
llm |
expert |
0m
Model:
Tokens:
input: 64069
output: 34
thoughts: 1451
Thoughts: **Concurrent Unmapping Analysis**
Okay, so I'm diving into this `folio_mapped` check, and something feels off. Let's break down how a VMA can potentially be accessed by an `unmap_mapping_range_tree` call while concurrent operations are in progress, particularly `munmap`, `mremap`, `madvise`, and `fork`.
First, I realize that `vma_mark_detached` is just marking the VMA, not unlinking it. `remove_vma` handles the unlinking via `unlink_file_vma`, which removes the VMA from `i_mmap`. Crucially, `remove_vma` is called *after* `vms_clear_ptes` in `vms_complete_munmap_vmas`. This means the VMA *is* in `i_mmap` when PTEs are being cleared.
Now, the core concern is concurrency: what happens if `unmap_mapping_range_tree` is running simultaneously with `munmap`? `munmap` has the `mmap_lock` in write mode, and `unmap_mapping_range_tree` holds the `i_mmap_lock` in read mode. They *can* run concurrently. So, during `munmap`, PTEs are cleared in `vms_clear_ptes`. Then, `remove_vma` is called, and `unlink_file_vma` takes the `i_mmap_lock` in write mode. This means `unmap_mapping_range_tree` could see the VMA before it's unlinked, and attempt to operate on PTEs. If `munmap` has *already* cleared the PTEs, `unmap_mapping_range_tree` would do nothing on that page. If `munmap` hadn't finished clearing them, `unmap_mapping_range_tree` would clear them. Either way, the folio *should* be unmapped (meaning `folio_mapped` should be false) after that.
But wait, what if the clearing of PTEs in `vms_clear_ptes` is happening concurrently, and the folio's state hasn't been completely updated yet? But, `unmap_mapping_range_tree` does not hold the `mmap_lock` like `munmap` in `vms_clear_ptes` does, it only holds the `i_mmap_lock`, so they are fully concurrent. The important detail is that `zap_pte_range` takes the page table lock (`ptl`), which serializes the PTE access. If `unmap_mapping_range_tree` finishes, it will have iterated through all the VMAs in `i_mmap`. For each VMA, it has obtained the `ptl` and cleared the PTEs. So, after `unmap_mapping_range_tree`, all PTEs in all VMAs within `i_mmap` *should* be cleared. Therefore, `folio_mapped(folio)` *must* be false *unless* there's a VMA outside of `i_mmap`!
How can a VMA not be in `i_mmap`? It could happen if `unlink_file_vma` has *already* been called. But, `unlink_file_vma` is called in `remove_vma`, which happens *after* `vms_clear_ptes`. Wait, is there a case where `unlink_file_vma` is called *before* PTEs are cleared? In `free_pgtables`? That's called during `exit` or `munmap`. During `exit`, `exit_mmap` does unmap PTEs, then calls `free_pgtables`, where the unlinking happens. So PTEs are cleared *before* unlinking.
Next, I looked at `mremap`. `mremap` moves the VMA. `move_vma` calls `copy_vma` to create a new VMA and *link* it to `i_mmap`, before calling `move_page_tables`, and then finally `do_vmi_align_munmap` on the old VMA. Therefore, the *new* VMA is in `i_mmap` before the old one is unmapped. So, `unmap_mapping_range_tree` *would* see both VMAs during the concurrent run.
What about `madvise(MADV_DONTNEED)`? That just clears PTEs, so nothing too problematic there.
Finally, `fork` threw up a red flag. `dup_mmap` copies VMAs. It calls `vma_link`
|
| 93/2 |
2026/08/11 01:44 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/memory.c Name:copy_page_range]
Results: map[SourceCode:1506: int
1507: copy_page_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma)
1508: {
1509: pgd_t *src_pgd, *dst_pgd;
1510: unsigned long addr = src_vma->vm_start;
1511: unsigned long end = src_vma->vm_end;
1512: struct mm_struct *dst_mm = dst_vma->vm_mm;
1513: struct mm_struct *src_mm = src_vma->vm_mm;
1514: struct mmu_notifier_range range;
1515: unsigned long next;
1516: bool is_cow;
1517: int ret;
1518:
1519: if (!vma_needs_copy(dst_vma, src_vma))
1520: return 0;
1521:
1522: if (is_vm_hugetlb_page(src_vma))
1523: return copy_hugetlb_page_range(dst_mm, src_mm, dst_vma, src_vma);
1524:
1525: /*
1526: * We need to invalidate the secondary MMU mappings only when
1527: * there could be a permission downgrade on the ptes of the
1528: * parent mm. And a permission downgrade will only happen if
1529: * is_cow_mapping() returns true.
1530: */
1531: is_cow = is_cow_mapping(src_vma->vm_flags);
1532:
1533: if (is_cow) {
1534: mmu_notifier_range_init(&range, MMU_NOTIFY_PROTECTION_PAGE,
1535: 0, src_mm, addr, end);
1536: mmu_notifier_invalidate_range_start(&range);
1537: /*
1538: * Disabling preemption is not needed for the write side, as
1539: * the read side doesn't spin, but goes to the mmap_lock.
1540: *
1541: * Use the raw variant of the seqcount_t write API to avoid
1542: * lockdep complaining about preemptibility.
1543: */
1544: vma_assert_write_locked(src_vma);
1545: raw_write_seqcount_begin(&src_mm->write_protect_seq);
1546: }
1547:
1548: ret = 0;
1549: dst_pgd = pgd_offset(dst_mm, addr);
1550: src_pgd = pgd_offset(src_mm, addr);
1551: do {
1552: next = pgd_addr_end(addr, end);
1553: if (pgd_none_or_clear_bad(src_pgd))
1554: continue;
1555: if (unlikely(copy_p4d_range(dst_vma, src_vma, dst_pgd, src_pgd,
1556: addr, next))) {
1557: ret = -ENOMEM;
1558: break;
1559: }
1560: } while (dst_pgd++, src_pgd++, addr = next, addr != end);
1561:
1562: if (is_cow) {
1563: raw_write_seqcount_end(&src_mm->write_protect_seq);
1564: mmu_notifier_invalidate_range_end(&range);
1565: }
1566: return ret;
1567: }
SourceFile:mm/memory.c]
|
| 94/2 |
2026/08/11 01:44 |
llm |
expert |
0m
Model:
Tokens:
input: 67639
output: 34
thoughts: 1199
Thoughts: **Race Condition in PTE Copying During File Truncation**
Okay, so I'm digging into this code, and it's hitting me: `copy_page_range` copies Page Table Entries (PTEs). If `unmap_mapping_range_tree` unmaps the new Virtual Memory Area (`new_vma`) first, then `copy_page_range` copies a PTE from the old VMA (`old_vma`) to `new_vma`, and *then* `unmap_mapping_range_tree` unmaps `old_vma`, that PTE is now solely in `new_vma`. It seems valid, but then I stop. Does `copy_page_range` lock `i_mmap_lock`? No, it just grabs `mmap_lock` and `ptl`. So, a race is possible here!
The question then becomes: what's the order of the unmapping? If `old_vma` is unmapped first, `copy_page_range` won't find the PTE, and it's safe. But if `new_vma` is unmapped first, then `copy_page_range` copies, and then `old_vma` is unmapped, then the PTE lives in `new_vma`. Then `folio_mapped` is true! And then I need to stop and think this through.
`unmap_mapping_range_tree` iterates over the interval tree. The interval tree is ordered by `vm_start`. If `new_vma` and `old_vma` have the same `vm_start`, their order is arbitrary! So, `new_vma` *could* be visited before `old_vma`. Is this a known bug? Let's check `unmap_mapping_folio` to see if it is protected. `unmap_mapping_folio` is called with the folio locked. Does `copy_page_range` check the lock? No, `copy_page_range` just copies the PTE. It doesn't lock anything.
Now it clicks: if `copy_page_range` copies the PTE, it increments the mapcount, but `unmap_mapping_folio` expects the mapcount to be zero after it's done! If this race happens, then `BUG_ON(folio_mapped(folio))` will trigger! Bingo!
The next question: Is `copy_page_range` allowed to copy a file mapping PTE without holding `i_mmap_lock`? Yes, `fork` doesn't hold `i_mmap_lock`. But wait, when a file is truncated, `truncate_inode_pages` locks the folio, unmaps it, and removes it from the page cache. If `fork` copies the PTE, the child will have a PTE to a folio that's been yanked from the page cache!
Will this cause problems? If the child accesses the page, it'll get the old data, but the data is truncated! If the folio is removed, it's freed, but if the mapcount is > 0 it won't be, so it becomes an anonymous page? Nope, it's still a file page, but `folio->mapping` is NULL.
Does `do_fault` see that it's NULL? No, `do_fault` is only called if the PTE is not present. If the PTE *is* present, the child just accesses the page. But the page isn't associated with the file!
Wait... this is *exactly* the race described in `d00806b18315`! That patch says invalidation must shoot down *all* mappings to a page before it's discarded from the page cache. `do_no_page` (now `filemap_fault`) is fixed to return with the page locked so that invalidation blocks on the page lock, and can check if the page has been truncated. But `fork` doesn't call `filemap_fault`! It just copies the PTE! If `fork` copies, then it's not locked. So `fork` can run while invalidation is running.
`fork` holds the `mmap_lock` of the parent, but in write mode, right? Invalidation uses `i_mmap_lock` and the page lock, but *not* `mmap_lock`. So, `fork` can run concurrently with invalidation! Can the child end up with a PTE to a truncated page? Okay, let's check `copy_present_pte`...
|
| 95/2 |
2026/08/11 01:44 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:mm/memory.c Name:copy_present_pte]
Results: map[SourceCode: SourceFile:]
|
| 96/2 |
2026/08/11 01:44 |
llm |
expert |
0m
Model:
Tokens:
input: 69884
output: 35
thoughts: 12
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm deeply familiar with the relevant codebase:
**Reviewing `copy_present_ptes` Function**
Alright, time to dive into `copy_present_ptes`. I immediately think "present PTEs" β that's memory management, specifically page table entries. This function likely deals with copying or transferring page table entries, which is crucial for things like process creation, virtual memory manipulation, or perhaps even handling page faults. I need to understand *how* it's doing the copy. Is it a deep copy, shallow copy, or a specific optimization? Are we dealing with the physical addresses directly? What context is this code running in? Is it kernel space? User space? The implications are huge. I want to make sure this function is efficient, correct, and thread-safe if necessary. Any potential for race conditions here? Iβm looking for any signs of memory leaks or subtle bugs that could lead to performance bottlenecks or, even worse, system crashes. I need to verify its behavior under a variety of conditions, including error cases. I also need to ensure its inline comments are detailed enough for future readers, including me, in case I forget it.
|
| 97/2 |
2026/08/11 01:44 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/memory.c Name:copy_present_ptes]
Results: map[SourceCode:1121: /*
1122: * Copy one present PTE, trying to batch-process subsequent PTEs that map
1123: * consecutive pages of the same folio by copying them as well.
1124: *
1125: * Returns -EAGAIN if one preallocated page is required to copy the next PTE.
1126: * Otherwise, returns the number of copied PTEs (at least 1).
1127: */
1128: static inline int
1129: copy_present_ptes(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma,
1130: pte_t *dst_pte, pte_t *src_pte, pte_t pte, unsigned long addr,
1131: int max_nr, int *rss, struct folio **prealloc)
1132: {
1133: fpb_t flags = FPB_MERGE_WRITE;
1134: struct page *page;
1135: struct folio *folio;
1136: int err, nr;
1137:
1138: page = vm_normal_page(src_vma, addr, pte);
1139: if (unlikely(!page))
1140: goto copy_pte;
1141:
1142: folio = page_folio(page);
1143:
1144: /*
1145: * If we likely have to copy, just don't bother with batching. Make
1146: * sure that the common "small folio" case is as fast as possible
1147: * by keeping the batching logic separate.
1148: */
1149: if (unlikely(!*prealloc && folio_test_large(folio) && max_nr != 1)) {
1150: if (!(src_vma->vm_flags & VM_SHARED))
1151: flags |= FPB_RESPECT_DIRTY;
1152: if (vma_soft_dirty_enabled(src_vma))
1153: flags |= FPB_RESPECT_SOFT_DIRTY;
1154:
1155: nr = folio_pte_batch_flags(folio, src_vma, src_pte, &pte, max_nr, flags);
1156: folio_ref_add(folio, nr);
1157: if (folio_test_anon(folio)) {
1158: if (unlikely(folio_try_dup_anon_rmap_ptes(folio, page,
1159: nr, dst_vma, src_vma))) {
1160: folio_ref_sub(folio, nr);
1161: return -EAGAIN;
1162: }
1163: rss[MM_ANONPAGES] += nr;
1164: VM_WARN_ON_FOLIO(PageAnonExclusive(page), folio);
1165: } else {
1166: folio_dup_file_rmap_ptes(folio, page, nr, dst_vma);
1167: rss[mm_counter_file(folio)] += nr;
1168: }
1169: __copy_present_ptes(dst_vma, src_vma, dst_pte, src_pte, pte,
1170: addr, nr);
1171: return nr;
1172: }
1173:
1174: folio_get(folio);
1175: if (folio_test_anon(folio)) {
1176: /*
1177: * If this page may have been pinned by the parent process,
1178: * copy the page immediately for the child so that we'll always
1179: * guarantee the pinned page won't be randomly replaced in the
1180: * future.
1181: */
1182: if (unlikely(folio_try_dup_anon_rmap_pte(folio, page, dst_vma, src_vma))) {
1183: /* Page may be pinned, we have to copy. */
1184: folio_put(folio);
1185: err = copy_present_page(dst_vma, src_vma, dst_pte, src_pte,
1186: addr, rss, prealloc, page);
1187: return err ? err : 1;
1188: }
1189: rss[MM_ANONPAGES]++;
1190: VM_WARN_ON_FOLIO(PageAnonExclusive(page), folio);
1191: } else {
1192: folio_dup_file_rmap_pte(folio, page, dst_vma);
1193: rss[mm_counter_file(folio)]++;
1194: }
1195:
1196: copy_pte:
1197: __copy_present_ptes(dst_vma, src_vma, dst_pte, src_pte, pte, addr, 1);
1198: return 1;
1199: }
SourceFile:mm/memory.c]
|
| 98/2 |
2026/08/11 01:44 |
llm |
expert |
0m
Model:
Tokens:
input: 71575
output: 34
thoughts: 631
Thoughts: **Analyzing a Potential Race Condition in Page Unmapping During `fork`**
Okay, so I'm diving into a tricky scenario with page unmapping and `fork`. The core of my concern revolves around how `invalidate_inode_pages2_range` interacts with the `fork` system call, particularly concerning page table entries (PTEs) and `mapcount` updates.
First, I realized that `copy_present_ptes` uses `folio_dup_file_rmap_pte`, which correctly increments the `mapcount`. On the other hand, `unmap_mapping_range_tree` is responsible for unmapping the PTE from a parent mapping, leading to a `mapcount` decrement. But there's a potential problem: if the child's mapping still exists, the `mapcount` might remain non-zero, even after the parent's PTE is cleared. This could lead `folio_mapped(folio)` to erroneously return true, which would be... problematic.
I'm working through the sequence of events. `invalidate_inode_pages2_range` calls `unmap_mapping_folio`. This function is taking `i_mmap_lock_read`. It then iterates the `i_mmap` list and finds the parent's VMA, takes `ptl`, clears the PTE, and releases `ptl`.
Meanwhile, `fork` is running. It's in the middle of `copy_page_range`, and it has *already* copied the VMA structure. The key is in how it handles the PTE itself. If `unmap_mapping_folio` clears the PTE before `fork` copies it, `fork` won't see a valid PTE, and the page won't be copied which is fine... but what if `fork` copies the PTE *before* `unmap_mapping_folio` has a chance to clear it? If that happens, then `fork` increments the folio's `mapcount`.
Then, `unmap_mapping_folio` clears the PTE in the parent's VMA, and decrements the `mapcount`. Okay, so far so good. But I need to figure out what happens in the child's VMA. Is the child's VMA actually *present* in the `i_mmap` list? Aha! `dup_mmap` calls `vma_link`, which adds the child's VMA to `i_mmap`.
So now I see the problem. `unmap_mapping_folio` will iterate through *both* VMAs, because `unmap_mapping_range_tree` iterates over *all* VMAs in `i_mmap`. This means it will clear *both* PTEs! If this happens then everything is fine. But...
There's another race condition... when is the child's VMA added to `i_mmap`? If it's added *before* `unmap_mapping_folio` takes `i_mmap_lock`, then `unmap_mapping_folio` sees both VMAs and correctly clears both PTEs. If, however, it's added *after* `unmap_mapping_folio` takes `i_mmap_lock`, then `fork` is blocked by taking `i_mmap_lock_write` for a moment!
I need to confirm whether `vma_link`'s locking prevents a race, let me check `vma_link`...
|
| 99/2 |
2026/08/11 01:44 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/vma.c Name:vma_link]
Results: map[SourceCode:1844: static int vma_link(struct mm_struct *mm, struct vm_area_struct *vma)
1845: {
1846: VMA_ITERATOR(vmi, mm, 0);
1847:
1848: vma_iter_config(&vmi, vma->vm_start, vma->vm_end);
1849: if (vma_iter_prealloc(&vmi, vma))
1850: return -ENOMEM;
1851:
1852: vma_start_write(vma);
1853: vma_iter_store_new(&vmi, vma);
1854: vma_link_file(vma, /* hold_rmap_lock= */false);
1855: mm->map_count++;
1856: validate_mm(mm);
1857: return 0;
1858: }
SourceFile:mm/vma.c]
|
| 100/2 |
2026/08/11 01:44 |
llm |
expert |
0m
Model:
Tokens:
input: 73274
output: 36
thoughts: 12
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Analyzing `vma_link_file`**
Alright, let's dive in. The immediate need here is to thoroughly examine the implications of `vma_link_file`. I need to understand its role within the broader context of virtual memory management and, specifically, how it interacts with file mappings. My initial thought process is to quickly refresh my understanding of how this links to the larger picture. I'm thinking about the data structures involved, the potential for contention, and any implications for performance. Is this about linking VMAs to inodes or something more complex? I need to quickly run through the scenarios it handles and make sure I haven't missed any edge cases in the past. Time to get down into the details of the code and consider the potential ramifications.
|
| 101/2 |
2026/08/11 01:44 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/vma.c Name:vma_link_file]
Results: map[SourceCode:1830: static void vma_link_file(struct vm_area_struct *vma, bool hold_rmap_lock)
1831: {
1832: struct file *file = vma->vm_file;
1833: struct address_space *mapping;
1834:
1835: if (file) {
1836: mapping = file->f_mapping;
1837: i_mmap_lock_write(mapping);
1838: __vma_link_file(vma, mapping);
1839: if (!hold_rmap_lock)
1840: i_mmap_unlock_write(mapping);
1841: }
1842: }
SourceFile:mm/vma.c]
|
| 102/2 |
2026/08/11 01:44 |
llm |
expert |
0m
Model:
Tokens:
input: 73723
output: 39
thoughts: 1178
Thoughts: **Analyzing a Potential Race Condition in Page Table Entry Management**
Okay, so I'm digging into this interaction between `fork` and `unmap_mapping_folio` and I think I've got a handle on it. The key is how `i_mmap_lock` and the page table locks (`ptl`) are used. I see that `vma_link_file` *does* take `i_mmap_lock_write`, which is exactly what I was hoping for. That means `fork` can't add the child VMA to `i_mmap` while `unmap_mapping_folio` holds `i_mmap_lock_read`. Let's break this down.
My initial thought was this sequence:
1. `fork` grabs `i_mmap_lock_write`, adds the child's VMA, then releases the lock.
2. `unmap_mapping_folio` acquires `i_mmap_lock_read`. It now sees both the parent and child VMAs.
3. `unmap_mapping_folio` clears the PTE in the parent's VMA.
4. `unmap_mapping_folio` clears the PTE in the child's VMA (which should currently be empty).
5. `unmap_mapping_folio` releases `i_mmap_lock_read`.
6. `fork` proceeds to `copy_page_range`.
7. `fork` attempts to copy the PTE from the parent to the child. But, wait, the parent's PTE is now NONE! It copies a NULL PTE.
So this isn't a race condition.
I thought, what if `unmap_mapping_folio` runs *before* `fork` adds the child's VMA?
1. `unmap_mapping_folio` takes `i_mmap_lock_read`. It only sees the parent's VMA.
2. It clears the PTE in the parent's VMA.
3. It releases `i_mmap_lock_read`.
4. `fork` takes `i_mmap_lock_write`, adds the child's VMA.
5. `fork` copies the PTE from the parent to the child. Again, the parent's PTE is NONE. So, it copies a NULL PTE.
This sequence is also safe.
Then, there's the case where `fork` is in `copy_page_range` and `unmap_mapping_folio` runs.
1. `fork` already has the child's VMA in `i_mmap`.
2. `fork` is in `copy_page_range`.
3. `unmap_mapping_folio` takes `i_mmap_lock_read`. It now sees both VMAs.
4. `unmap_mapping_folio` clears the PTE in the parent's VMA.
5. `unmap_mapping_folio` clears the PTE in the child's VMA.
6. `unmap_mapping_folio` drops `i_mmap_lock_read`.
But, what if `fork` copies the PTE *after* `unmap_mapping_folio` clears the child's VMA, but *before* it clears the parent's VMA? Okay, this is where it gets interesting...
`unmap_mapping_range_tree` iterates over the interval tree. Suppose the interval tree has the child VMA first, then the parent VMA.
1. `unmap_mapping_folio` clears the PTE in the child VMA. (It's now NONE).
2. `fork` copies the PTE from the parent VMA to the child VMA. (It's PRESENT at this point).
3. `unmap_mapping_folio` clears the PTE in the parent VMA. (It becomes NONE).
4. `unmap_mapping_folio` finishes.
5. The folio is STILL mapped in the child VMA!
Is this even possible? This looks like a bug. Let's look at the locking within `copy_page_range`.
`copy_page_range` grabs the `ptl` of the parent, then the `ptl` of the child. `unmap_mapping_folio` grabs the `ptl` of the child, then later the `ptl` of the parent. Let's trace it out:
If `unmap_mapping_folio` is at the child, it takes the child's `ptl`, clears it, and releases. Then, `fork` grabs the
|
| 103/2 |
2026/08/11 01:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/interval_tree.c Name:vma_interval_tree_insert]
Results: map[SourceCode: 18: INTERVAL_TREE_DEFINE(struct vm_area_struct, shared.rb,
SourceFile:mm/interval_tree.c]
|
| 104/2 |
2026/08/11 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 76018
output: 29
thoughts: 11
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, and delivered as a first-person summary:
**Checking `INTERVAL_TREE_DEFINE`**
Right, let's dive in. My first thought is to immediately check the definition of `INTERVAL_TREE_DEFINE`. That macro, depending on the library or codebase, is the linchpin for how the interval tree is actually structured, and how it handles the core data. My mind jumps straight to potential implementations β is it using a self-balancing binary search tree under the hood? Or perhaps a more specialized, possibly even non-standard, node structure tailored for interval operations? The choice there dramatically impacts performance characteristics, insertion, deletion, and especially the efficiency of overlap queries. I need to understand if it's optimized for point lookups, range searches, or a hybrid approach. The details of how it handles interval endpoints (closed vs. open) is also critical for understanding the behavior when intervals abut. Knowing the nuances here is the foundation for any real comprehension of the interval tree's functionality and its suitability for any specific task I have in mind. I need to quickly recall which project, and which version of the codebase, I'm working with; those details will determine the exact implications of this macro. Time to delve into the source.
|
| 105/2 |
2026/08/11 01:45 |
tool |
grepper |
0m
Args: map[Expression:INTERVAL_TREE_DEFINE PathPrefix:include/linux/]
Results: map[Output:include/linux/interval_tree_generic.h-27-
include/linux/interval_tree_generic.h:28:#define INTERVAL_TREE_DEFINE(ITSTRUCT, ITRB, ITTYPE, ITSUBTREE, \
include/linux/interval_tree_generic.h-29- ITSTART, ITLAST, ITSTATIC, ITPREFIX) \
]
|
| 106/2 |
2026/08/11 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 76434
output: 38
thoughts: 25
Thoughts: Here's my take on the thought process, as if it were my own:
**Diving into the Interval Tree Implementation**
Okay, I need to refresh my memory on the specifics of the `INTERVAL_TREE_DEFINE` macro. It's time to crack open `include/linux/interval_tree_generic.h` and see exactly how they're handling the core definition. I want to understand how the data structures are defined, how the tree itself is structured, and what the key considerations are for this generic implementation. I need to make sure I grasp the core building blocks before I dig into any specific use cases or algorithms built on top. I'm hoping I don't see any obvious gotchas or unexpected design choices that might trip me up later. Let's see what's in there.
|
| 107/2 |
2026/08/11 01:45 |
tool |
read-file |
0m
Args: map[File:include/linux/interval_tree_generic.h FirstLine:28 LineCount:50]
Results: map[Contents: 28: #define INTERVAL_TREE_DEFINE(ITSTRUCT, ITRB, ITTYPE, ITSUBTREE, \
29: ITSTART, ITLAST, ITSTATIC, ITPREFIX) \
30: \
31: /* Callbacks for augmented rbtree insert and remove */ \
32: \
33: RB_DECLARE_CALLBACKS_MAX(static, ITPREFIX ## _augment, \
34: ITSTRUCT, ITRB, ITTYPE, ITSUBTREE, ITLAST) \
35: \
36: /* Insert / remove interval nodes from the tree */ \
37: \
38: ITSTATIC void ITPREFIX ## _insert(ITSTRUCT *node, \
39: struct rb_root_cached *root) \
40: { \
41: struct rb_node **link = &root->rb_root.rb_node, *rb_parent = NULL; \
42: ITTYPE start = ITSTART(node), last = ITLAST(node); \
43: ITSTRUCT *parent; \
44: bool leftmost = true; \
45: \
46: while (*link) { \
47: rb_parent = *link; \
48: parent = rb_entry(rb_parent, ITSTRUCT, ITRB); \
49: if (parent->ITSUBTREE < last) \
50: parent->ITSUBTREE = last; \
51: if (start < ITSTART(parent)) \
52: link = &parent->ITRB.rb_left; \
53: else { \
54: link = &parent->ITRB.rb_right; \
55: leftmost = false; \
56: } \
57: } \
58: \
59: node->ITSUBTREE = last; \
60: rb_link_node(&node->ITRB, rb_parent, link); \
61: rb_insert_augmented_cached(&node->ITRB, root, \
62: leftmost, &ITPREFIX ## _augment); \
63: } \
64: \
65: ITSTATIC void ITPREFIX ## _remove(ITSTRUCT *node, \
66: struct rb_root_cached *root) \
67: { \
68: rb_erase_augmented_cached(&node->ITRB, root, &ITPREFIX ## _augment); \
69: } \
70: \
71: /* \
72: * Iterate over intervals intersecting [start;last] \
73: * \
74: * Note that a node's interval intersects [start;last] iff: \
75: * Cond1: ITSTART(node) <= last \
76: * and \
77: * Cond2: start <= ITLAST(node) \
]
|
| 108/2 |
2026/08/11 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 77540
output: 39
thoughts: 720
Thoughts: **Analyzing a Potential Race Condition in Virtual Memory Management**
Okay, so I'm thinking through this potential race condition. The scenario involves virtual memory areas (VMAs), specifically focusing on how they're handled during `fork` and `unmap_mapping_folio`. The core of my analysis revolves around understanding the order in which VMAs are visited within the interval tree.
Initially, I considered the placement of the newly created child VMA relative to its parent. I realized that if the insertion logic (`start == ITSTART(parent)`) dictates a right-hand placement (right child), the parent VMA will *always* be visited before the child VMA in an in-order traversal of the red-black tree. This is crucial.
I broke down the process:
1. `unmap_mapping_folio` visits the `parent_vma` and clears its page table entry (PTE).
2. `fork` then attempts to copy the PTE from the `parent_vma` to the `child_vma`. However, since `unmap_mapping_folio` already cleared the PTE in the parent, the copy will result in `NONE`.
3. `unmap_mapping_folio` then visits the `child_vma`, clearing the PTE again, but in this case the PTE will be `NONE`.
Essentially, I concluded that this sequence doesn't present a race condition, regardless of whether the `fork` operation happens before or after the parent's PTE is cleared.
I explored several scenarios. The child VMA is always inserted *after* the parent VMA. I considered that rotations within the RB-tree during insertion could change the order of traversal, but then I remembered that rotations maintain the in-order sequence. The tree is *strictly* ordered based on the insertion logic itself (`start == ITSTART(parent)`) which dictates right-side insertion. Since I know this, the traversal will always be `parent_vma` followed by `child_vma`.
Thus, if the parent's PTE is cleared *before* the copy during `fork`, the child will *always* end up with a `NONE` PTE. There's no possible race.
Then, I shifted my focus to `mremap`, specifically with `MREMAP_DONTUNMAP`. A new VMA is created with the *same* `vm_pgoff` (offset) but potentially a *different* `vm_start` address. The interval tree is ordered on the offset. I started to wonder if this could introduce a different race. This avenue requires further exploration to verify if this has an impact on the analysis.
|
| 109/2 |
2026/08/11 01:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/interval_tree.c Name:vma_start_pgoff]
Results: map[SourceCode: 13: static inline unsigned long vma_start_pgoff(struct vm_area_struct *v)
14: {
15: return v->vm_pgoff;
16: }
SourceFile:mm/interval_tree.c]
|
| 110/2 |
2026/08/11 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 78949
output: 35
thoughts: 2181
Thoughts: **Deep Dive into a Memory Mapping Bug**
Okay, so I'm thinking through this memory mapping issue, starting with how `vm_pgoff` is used for ordering. I see that `mremap` with a new VMA will maintain the correct insertion order based on `vm_pgoff`, which means the existing VMA is visited before the new one, and the logic should hold. But then I consider the case where the *old* VMA is inserted *after* the *new* VMA, which seems weird. It clicks that if I `mmap` the same file twice with the same offset, the second mapping gets inserted later. If I then remap the second one to the first, no overlap happens. I then consider two unrelated VMAs that map the same file at the same offset. But those are simply two VMAs, and `unmap_mapping_folio` should touch both. Forking with two such VMAs won't cause issues either; the `fork` operation replicates the PTEs.
Now, could there ever be a case where a VMA is visited *before* the VMA it's copying from? I think through a fork situation, and then a second fork. The VMAs should still be correctly ordered, as far as insertion goes. But, what about unmapping?
I shift gears to consider failures during unmapping. If a THP gets in the way of `unmap_mapping_folio` because of a failure within `zap_huge_pmd` due to a lock issue (`__pmd_trans_huge_lock`), it shouldn't be an issue. If the PMD isn't a huge page, `zap_pte_range` will handle unmapping the regular PTEs. So, that's not it.
Then, what if the VMA is locked? `VM_LOCKED` on a VMA? I recall that `unmap_mapping_range` ignores the lock and proceeds anyway. Okay.
What about a KVM guest? KVM uses MMU notifiers. Unmapping calls `mmu_notifier_invalidate_range_start` and `end`, and the guest will unmap. But KVM takes a reference to the page, increasing `folio_refcount`, not `folio_mapcount`. Thus, `folio_mapped` should still return false, since I'm concerned about the `mapcount`.
I think about other things that can increase `refcount` but not `mapcount`, such as `io_uring`, `vfio`, and `bpf`.
So, how can `folio_mapcount` be >= 1? Only if a page is actually mapped in a page table, right? How can that still be true after `unmap_mapping_folio` clears the PTEs for *all* VMAs within the file's mapping (`i_mmap`)? Could it be a VMA *not* in `i_mmap`? How? I consider an anonymous VMA, but the folio is a file folio! Can a file folio *ever* be mapped in an anonymous VMA? Only if `vm_insert_page` is called with a file folio into an anonymous VMA. Is there any driver that does this? If there is, it's a driver bug.
The bug report is triggered by `syzbot`. Syzkaller can `mmap` various devices. I go back to the bug report: it's a `kernel BUG at mm/truncate.c:632!`, `folio_unmap_invalidate`, within `blkdev_direct_write`. Direct IO write to a block device is the trigger. `kiocb_invalidate_pages` calls `invalidate_inode_pages2_range`, and then that calls `folio_unmap_invalidate`. If a folio is in the block device's mapping, then `unmap_mapping_folio` will handle unmapping. But, how can it still be mapped?
I consider the possibility of the folio being in *another* block device's VMA. I think not, block devices use their own `filemap_fault`, which maps folios within its own mapping.
What if it's a partition? Like `/dev/sda1`. I recalled seeing that `/dev/sda` and `/dev/sda1` shared the same mapping. But did they? I check `bdev_mapping`. Each partition has its own inode and mapping thanks to `bdev_alloc` (allocating a *new* inode). So, `/dev/sda1` has its own mapping separate from `/dev/sda`.
But then, when `blkdev_open` is called, a file mapping is set. Thus, a `/dev/sda1` file mapping is
|
| 111/2 |
2026/08/11 01:45 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:block/fops.c Name:blkdev_mmap]
Results: map[SourceCode: SourceFile:]
|
| 112/2 |
2026/08/11 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 82211
output: 35
thoughts: 11
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm deeply familiar with kernel internals and block device operations:
**Checking `blkdev_fops`**
Alright, time to get to the core of this. When I see that thought, `blkdev_fops`, my mind immediately jumps to the file operations structure for block devices within the kernel. I need to understand what's happening with these devices, and this is the first place I'd look. This `blkdev_fops` structure is the central point of contact, the gateway to all the critical interactions with these block devices. It's where the read and write methods are defined, along with functions for device control, metadata manipulation, and everything else that makes a block device work.
I'm thinking about the implications of the particular operation I'm investigating. Is this a read? A write? An IOCTL? Understanding which specific function pointers within `blkdev_fops` are being used, and their implementation, is essential to tracing the flow. The relationships, the various functions called - these are all critical to understand the larger context. Is something not working as it should? I need to know.
I'll be mentally parsing it, running it through the potential code paths, considering its role in the overall device interaction. Is it implemented correctly? Is something potentially missing or broken? What are the implications for other modules or the file system? It's the starting point for my deeper dive. Now let's get into the details...
|
| 113/2 |
2026/08/11 01:45 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:block/fops.c Name:blkdev_fops]
Results: map[SourceCode: SourceFile:]
|
| 114/2 |
2026/08/11 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 82598
output: 28
thoughts: 18
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Investigating `mmap` Usage in `block/fops.c`**
Alright, I need to get a handle on how `mmap` is being utilized within the block device file operations. My immediate thought is to jump straight into the code. Let's see... the `block/fops.c` file is the most likely location for the relevant file operations. So, I'm going to quickly use `grep` to hunt down instances of `mmap` within that file. This will give me a first pass at identifying the specific functions or contexts where memory mapping is being employed for block device access. This should quickly highlight any areas of potential interest or concern. It's a quick and dirty way to get my bearings and narrow down the scope for further investigation. From there, I can analyze the surrounding code to determine how `mmap` is being used, for what purposes (e.g., direct I/O, caching strategies), and whether any specific configurations are pertinent to my current focus.
|
| 115/2 |
2026/08/11 01:46 |
tool |
grepper |
0m
Args: map[Expression:mmap PathPrefix:block/fops.c]
Results: map[Output:block/fops.c=843=static long blkdev_fallocate(struct file *file, int mode, loff_t start,
--
block/fops.c-916-
block/fops.c:917:static int blkdev_mmap_prepare(struct vm_area_desc *desc)
block/fops.c-918-{
--
block/fops.c-921- if (bdev_read_only(I_BDEV(bdev_file_inode(file))))
block/fops.c:922: return generic_file_readonly_mmap_prepare(desc);
block/fops.c-923-
block/fops.c:924: return generic_file_mmap_prepare(desc);
block/fops.c-925-}
--
block/fops.c=927=const struct file_operations def_blk_fops = {
--
block/fops.c-933- .iopoll = iocb_bio_iopoll,
block/fops.c:934: .mmap_prepare = blkdev_mmap_prepare,
block/fops.c-935- .fsync = blkdev_fsync,
]
|
| 116/2 |
2026/08/11 01:46 |
llm |
expert |
0m
Model:
Tokens:
input: 83205
output: 36
thoughts: 1090
Thoughts: **Debugging a Kernel BUG: mm/truncate.c:632!**
Okay, so I'm digging into this kernel bug, and the trace points me to `folio_unmap_invalidate` called from `blkdev_direct_write`. First thought: `blkdev_mmap_prepare` calls `generic_file_mmap_prepare`, but it doesn't seem to mess with the `vm_pgoff`. Could `mremap` be involved, perhaps shifting the mapping's offset? No, `mremap` seems to handle `vm_pgoff` correctly relative to the file.
Now I'm considering scenarios where the Virtual Memory Area (VMA) is somehow not linked to the `i_mmap` structure, or maybe in a different one, after a file mapping. But `vma->vm_file` should be immutable, so that's not it. Then a scary thought: What if a folio *is* mapped in a page table, but the VMA it belongs to has been destroyed already? The PTEs *should* be cleared during VMA destruction. Is there a leak here? While there have been bugs with `mremap` or `munmap` leaking PTEs, syzkaller would probably catch it.
Back to the bug report: It's failing in `mm/truncate.c`, within `folio_unmap_invalidate`, called from `invalidate_inode_pages2_range`, triggered by `blkdev_direct_write`. Could this relate to Direct Access (DAX)? `blkdev_direct_write` does call `kiocb_invalidate_pages`, but DAX folios are special, and `invalidate_inode_pages2_range` handles them. Still, this is a call for a *normal* folio.
Let's rule out other special folio types: secretmem folios, BPF-mapped folios. No.
Time to revisit `unmap_mapping_folio` more closely. I see it locks the `i_mmap` and calls `unmap_mapping_range_tree` with `first_index` and `last_index`. Now the real question: What if the folio is mapped at a virtual address, corresponding to a *different* index than the folio's actual index? How would that be possible? If `vm_insert_page` was used! But wait, block devices don't use `vm_insert_page`, that's impossible.
Could the folio be mapped within a VMA, but its index is *outside* the VMA's `vm_start` and `vm_end`? If the VMA doesn't cover the folio's index, `unmap_mapping_range_tree` won't even touch it. Again, `mremap` handles that consistently.
Wait...what if `mapping_mapped(mapping)` is FALSE? In that case, `unmap_mapping_folio` does NOTHING! But if `folio_mapped(folio)` is TRUE, there *is* a mapping, but `mapping_mapped` says there's no VMA! How can a folio be mapped if there are no VMAs mapping the file? That could happen if the VMA was unlinked from `i_mmap`, but the Page Table Entries (PTEs) are *not* cleared yet! `munmap` and `exit_mmap` should clear the PTEs before unlinking. Are there other places where this could happen? The code path to check now is `unlink_file_vma`. This is where it's at!
|
| 117/2 |
2026/08/11 01:46 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:mm/vma.c Name:unlink_file_vma]
Results: map[SourceCode: SourceFile:]
|
| 118/2 |
2026/08/11 01:46 |
llm |
expert |
0m
Model:
Tokens:
input: 85137
output: 28
thoughts: 13
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Analyzing `unlink_file_vma` for Optimization**
Okay, so I'm thinking about performance optimization, and the first thing that jumps to mind is the potential impact of `unlink_file_vma`. It's a key function for memory management, and I need to understand its role in the critical path. My initial approach is to immediately grep for `unlink_file_vma`. That's the baseline, the quick and dirty method to establish context. From there, I'll need to look at its definition and call sites, and, if that grep query proves fruitful, I'll identify the hotspots, maybe use some performance monitoring, and determine where this function spends the most time. I'll need to figure out which components of the system are invoking it the most and if those calls are being optimized with respect to the memory model. The goal is to see if any obvious bottlenecks exist within this function or, more importantly, are *caused* by it. Are there any unnecessary operations? Can some locking be reduced? Is there any redundant work or areas where we might be able to exploit parallelism? Is the VM using the correct strategies for the load? Essentially, I'm trying to quickly build a mental map of its involvement in the system and identify areas ripe for investigation.
|
| 119/2 |
2026/08/11 01:46 |
tool |
grepper |
0m
Args: map[Expression:unlink_file_vma PathPrefix:mm/]
Results: map[Output:mm/memory.c=373=void free_pgtables(struct mmu_gather *tlb, struct unmap_desc *unmap)
--
mm/memory.c-402-
mm/memory.c:403: unlink_file_vma_batch_init(&vb);
mm/memory.c:404: unlink_file_vma_batch_add(&vb, vma);
mm/memory.c-405-
--
mm/memory.c-414- unlink_anon_vmas(vma);
mm/memory.c:415: unlink_file_vma_batch_add(&vb, vma);
mm/memory.c-416- }
mm/memory.c:417: unlink_file_vma_batch_final(&vb);
mm/memory.c-418-
--
mm/vma.c=1777=struct vm_area_struct *vma_merge_extend(struct vma_iterator *vmi,
--
mm/vma.c-1788-
mm/vma.c:1789:void unlink_file_vma_batch_init(struct unlink_vma_file_batch *vb)
mm/vma.c-1790-{
--
mm/vma.c-1793-
mm/vma.c:1794:static void unlink_file_vma_batch_process(struct unlink_vma_file_batch *vb)
mm/vma.c-1795-{
--
mm/vma.c-1806-
mm/vma.c:1807: unlink_file_vma_batch_init(vb);
mm/vma.c-1808-}
mm/vma.c-1809-
mm/vma.c:1810:void unlink_file_vma_batch_add(struct unlink_vma_file_batch *vb,
mm/vma.c-1811- struct vm_area_struct *vma)
--
mm/vma.c-1817- vb->count == ARRAY_SIZE(vb->vmas))
mm/vma.c:1818: unlink_file_vma_batch_process(vb);
mm/vma.c-1819-
--
mm/vma.c-1823-
mm/vma.c:1824:void unlink_file_vma_batch_final(struct unlink_vma_file_batch *vb)
mm/vma.c-1825-{
mm/vma.c-1826- if (vb->count > 0)
mm/vma.c:1827: unlink_file_vma_batch_process(vb);
mm/vma.c-1828-}
--
mm/vma.h=440=__must_check struct vm_area_struct *vma_merge_extend(struct vma_iterator *vmi,
--
mm/vma.h-442-
mm/vma.h:443:void unlink_file_vma_batch_init(struct unlink_vma_file_batch *vb);
mm/vma.h-444-
mm/vma.h:445:void unlink_file_vma_batch_final(struct unlink_vma_file_batch *vb);
mm/vma.h-446-
mm/vma.h:447:void unlink_file_vma_batch_add(struct unlink_vma_file_batch *vb,
mm/vma.h-448- struct vm_area_struct *vma);
]
|
| 120/2 |
2026/08/11 01:46 |
llm |
expert |
0m
Model:
Tokens:
input: 86397
output: 37
thoughts: 587
Thoughts: **My Analysis of VMA Unlinking and File Mapping**
Okay, let's break this down. It seems the key issue revolves around the timing of VMA unlinking and PTE clearing, particularly in relation to file mappings. I'm seeing that `free_pgtables` calls `unlink_file_vma_batch_add`. I know `free_pgtables` is called in `exit_mmap` after `unmap_vmas`. This tells me that PTEs are cleared *before* the VMAs are unlinked. Similarly, `do_vmi_align_munmap` clears PTEs using `vms_clear_ptes` before it calls `remove_vma` which in turn calls `unlink_file_vma`. So, the pattern is clear: PTEs are always cleared before VMAs are unlinked.
But what about `mremap`? It calls `move_page_tables`, then it calls `do_vmi_align_munmap` on the old VMA. This confirms the PTEs are cleared and then the old VMA is unlinked. The new VMA is linked and gets the PTEs. Seems logical, so far.
Now, the potential for a problem arises when a folio is mapped in a VMA, but the VMA's `vm_file` differs from `folio->mapping->host`. Imagine `vma->vm_file` being file A, but the folio actually belonging to file B. The VMA is in file A's `i_mmap` list, right? But `unmap_mapping_folio` looks at `folio->mapping`, which is file B. It iterates over file B's `i_mmap` list, it won't find the VMA because it's in file A's `i_mmap`. So the folio isn't unmapped! This is problematic, as it leaves `folio_mapped(folio)` as true, which is a problem. This is a BUG!
The critical question now is, *how* can this `vm_file` and `folio->mapping->host` mismatch occur? The answer seems to be when a file's fault handler returns a folio from a *different* file. I need to know if this situation actually happens. I can remember that `overlayfs` exists, and it is a likely candidate. With `overlayfs`, the `mmap`'ed file is the overlayfs file, but the fault handler calls the lower file's fault handler, which might return a folio from the lower file. However, I think that overlayfs was changed to map the lower file directly. I should double check `ovl_mmap`. I need to look into this more deeply.
|
| 121/2 |
2026/08/11 01:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/overlayfs/file.c Name:ovl_mmap]
Results: map[SourceCode: 468: static int ovl_mmap(struct file *file, struct vm_area_struct *vma)
469: {
470: struct ovl_file *of = file->private_data;
471: struct backing_file_ctx ctx = {
472: .cred = ovl_creds(file_inode(file)->i_sb),
473: .accessed = ovl_file_accessed,
474: };
475:
476: return backing_file_mmap(of->realfile, vma, &ctx);
477: }
SourceFile:fs/overlayfs/file.c]
|
| 122/2 |
2026/08/11 01:46 |
llm |
expert |
0m
Model:
Tokens:
input: 87804
output: 37
thoughts: 12
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm deeply familiar with memory management and backing files:
**Checking `backing_file_mmap`**
Right, let's dive in. I see that prompt: "Let's check `backing_file_mmap`." Immediately, my mind's racing. `backing_file_mmap` β thatβs probably the key data structure or function related to memory-mapped files. I need to understand its current state. I need to assess how the system is currently using memory mapped files. Is there an issue with paging, are there any inconsistencies? Does it even exist? Is this something I wrote? Where is it located? Is the issue related to a specific file or all memory mapped files? The context of the problem is all-important here.
My expert-level intuition kicks in. My mind quickly scans for potential problem areas related to mmap: memory allocation strategies, page fault handling, synchronization if it's multi-threaded, caching behavior, the specific file system it's working with. I need to understand what this check entails. Is it a debugging step? Is it part of a performance analysis? Or, worse, a potential crash investigation? I'm already formulating questions: What's the target system? What's the workload? What are the symptoms being observed? I'm mentally building a checklist of things to investigate: the memory map flags, the file descriptors involved, the offsets, lengths, and protection modes. Letβs get to the bottom of this.
|
| 123/2 |
2026/08/11 01:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/backing-file.c Name:backing_file_mmap]
Results: map[SourceCode: 329: int backing_file_mmap(struct file *file, struct vm_area_struct *vma,
330: struct backing_file_ctx *ctx)
331: {
332: struct file *user_file = vma->vm_file;
333: int ret;
334:
335: if (WARN_ON_ONCE(!(file->f_mode & FMODE_BACKING)))
336: return -EIO;
337:
338: if (!can_mmap_file(file))
339: return -ENODEV;
340:
341: vma_set_file(vma, file);
342:
343: scoped_with_creds(ctx->cred) {
344: ret = security_mmap_backing_file(vma, file, user_file);
345: if (ret)
346: return ret;
347:
348: ret = vfs_mmap(vma->vm_file, vma);
349: }
350:
351: if (ctx->accessed)
352: ctx->accessed(user_file);
353:
354: return ret;
355: }
SourceFile:fs/backing-file.c]
|
| 124/2 |
2026/08/11 01:46 |
llm |
expert |
0m
Model:
Tokens:
input: 88581
output: 41
thoughts: 163
Thoughts: **VMA File Association and Unmapping Logic**
Okay, so `vma_set_file(vma, file)` essentially links the VMA to a new, "lower" file. This is key. That means the VMA gets added to the lower file's `i_mmap` list. The implication is, when it comes time to unmap, `unmap_mapping_folio` should definitely be able to find it! That's good. But...
My mind immediately starts racing with corner cases. What about shared memory (`shmem`)? How does this interact with the `loop` device? And what about `binder`? It seems like it would be similar to `shmem`. `bpf` maps are another consideration that is probably a similar case to shmem.
This is where the real complexity lies. I also see that DRM drivers are heavily reliant on `shmem`-like approaches. Many DRM drivers allocate pages through `shmem` and then map them into userspace, which I think is pretty common. What happens if a DRM driver allocates a `shmem` file and maps its pages within the device's `mmap` handler? Specifically, what happens with `drm_gem_mmap`? I should probably start my investigation there.
|
| 125/2 |
2026/08/11 01:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/gpu/drm/drm_gem.c Name:drm_gem_mmap]
Results: map[SourceCode:1362: /**
1363: * drm_gem_mmap - memory map routine for GEM objects
1364: * @filp: DRM file pointer
1365: * @vma: VMA for the area to be mapped
1366: *
1367: * If a driver supports GEM object mapping, mmap calls on the DRM file
1368: * descriptor will end up here.
1369: *
1370: * Look up the GEM object based on the offset passed in (vma->vm_pgoff will
1371: * contain the fake offset we created) and map it with a call to
1372: * drm_gem_mmap_obj().
1373: *
1374: * If the caller is not granted access to the buffer object, the mmap will fail
1375: * with EACCES. Please see the vma manager for more information.
1376: */
1377: int drm_gem_mmap(struct file *filp, struct vm_area_struct *vma)
1378: {
1379: struct drm_gem_object *obj;
1380: int ret;
1381:
1382: obj = drm_gem_object_lookup_at_offset(filp, vma->vm_pgoff,
1383: vma_pages(vma));
1384: if (IS_ERR(obj))
1385: return PTR_ERR(obj);
1386:
1387: ret = drm_gem_mmap_obj(obj,
1388: drm_vma_node_size(&obj->vma_node) << PAGE_SHIFT,
1389: vma);
1390:
1391: drm_gem_object_put(obj);
1392:
1393: return ret;
1394: }
SourceFile:drivers/gpu/drm/drm_gem.c]
|
| 126/2 |
2026/08/11 01:46 |
llm |
expert |
0m
Model:
Tokens:
input: 89605
output: 43
thoughts: 14
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into `drm_gem_mmap_obj`**
Alright, let's take a look at `drm_gem_mmap_obj`. I need to refresh my memory on the specifics of this function within the GEM memory management context. I know it's *critical* for handling memory mapping within the DRM subsystem, allowing userspace processes to directly access GEM objects. I'm especially interested in how it handles the interaction between the kernel's virtual address space and the physical memory backing the GEM object. I need to remember the locking semantics - is it based on the object, the file descriptor, or a more granular approach? How does it interact with the page table modifications? Are there any potential race conditions I should be on the lookout for, particularly in multi-threaded environments? I need to think through how this is different from a straight `mmap` call, what are the restrictions and which bits are managed by the kernel. I also want to make sure I'm clear on how it handles caching and coherency β does it perform any necessary flushes or invalidations to ensure data integrity? I know that there are some subtleties regarding security and privilege levels involved here, so I will have to consider them closely. I hope there's no subtle, previously-undetected memory corruption bugs in there! I need to ensure there are no surprises in the code.
|
| 127/2 |
2026/08/11 01:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/gpu/drm/drm_gem.c Name:drm_gem_mmap_obj]
Results: map[SourceCode:1199: /**
1200: * drm_gem_mmap_obj - memory map a GEM object
1201: * @obj: the GEM object to map
1202: * @obj_size: the object size to be mapped, in bytes
1203: * @vma: VMA for the area to be mapped
1204: *
1205: * Set up the VMA to prepare mapping of the GEM object using the GEM object's
1206: * vm_ops. Depending on their requirements, GEM objects can either
1207: * provide a fault handler in their vm_ops (in which case any accesses to
1208: * the object will be trapped, to perform migration, GTT binding, surface
1209: * register allocation, or performance monitoring), or mmap the buffer memory
1210: * synchronously after calling drm_gem_mmap_obj.
1211: *
1212: * This function is mainly intended to implement the DMABUF mmap operation, when
1213: * the GEM object is not looked up based on its fake offset. To implement the
1214: * DRM mmap operation, drivers should use the drm_gem_mmap() function.
1215: *
1216: * drm_gem_mmap_obj() assumes the user is granted access to the buffer while
1217: * drm_gem_mmap() prevents unprivileged users from mapping random objects. So
1218: * callers must verify access restrictions before calling this helper.
1219: *
1220: * Return 0 or success or -EINVAL if the object size is smaller than the VMA
1221: * size, or if no vm_ops are provided.
1222: */
1223: int drm_gem_mmap_obj(struct drm_gem_object *obj, unsigned long obj_size,
1224: struct vm_area_struct *vma)
1225: {
1226: int ret;
1227:
1228: /* Check for valid size. */
1229: if (obj_size < vma->vm_end - vma->vm_start)
1230: return -EINVAL;
1231:
1232: /* Take a ref for this mapping of the object, so that the fault
1233: * handler can dereference the mmap offset's pointer to the object.
1234: * This reference is cleaned up by the corresponding vm_close
1235: * (which should happen whether the vma was created by this call, or
1236: * by a vm_open due to mremap or partial unmap or whatever).
1237: */
1238: drm_gem_object_get(obj);
1239:
1240: vma->vm_private_data = obj;
1241: vma->vm_ops = obj->funcs->vm_ops;
1242:
1243: if (obj->funcs->mmap) {
1244: ret = obj->funcs->mmap(obj, vma);
1245: if (ret)
1246: goto err_drm_gem_object_put;
1247: WARN_ON(!(vma->vm_flags & VM_DONTEXPAND));
1248: } else {
1249: if (!vma->vm_ops) {
1250: ret = -EINVAL;
1251: goto err_drm_gem_object_put;
1252: }
1253:
1254: vm_flags_set(vma, VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
1255: vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
1256: vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
1257: }
1258:
1259: return 0;
1260:
1261: err_drm_gem_object_put:
1262: drm_gem_object_put(obj);
1263: return ret;
1264: }
SourceFile:drivers/gpu/drm/drm_gem.c]
|
| 128/2 |
2026/08/11 01:46 |
llm |
expert |
0m
Model:
Tokens:
input: 91133
output: 48
thoughts: 284
Thoughts: **The Curious Case of the DRM VMA and the Shmem File**
Okay, so I'm digging into this memory mapping issue, and something just clicked. `drm_gem_mmap_obj` is setting up the VMA, specifically setting `vma->vm_ops` and `vma->vm_private_data`, but it *isn't* touching `vma->vm_file`. That means the VMA is residing within the DRM device's `i_mmap`.
Now, if the DRM device is actually *using* `shmem` to allocate its pages, then those pages, from the kernel's perspective, belong to the `shmem` file's mapping. Here's where it gets interesting. If that `shmem` file gets truncated or invalidated, `unmap_mapping_folio` is going to iterate through the `i_mmap` of the *`shmem`* file. But, and this is crucial, our VMA is in the DRM device's `i_mmap`, not the `shmem` file's. So, `unmap_mapping_folio` *won't* find the VMA.
Consequently, `folio_mapped(folio)` would return `true` because the folio *is* associated with a different mapping! And then, we get a `BUG_ON(folio_mapped(folio))`, which, as we know, is bad news.
Wait, is this even *possible*? I need to verify. My first thought is to check if any DRM drivers are even using `shmem` pages and mapping them to userspace using `vm_insert_page` or some `fault` handler. If they are using `VM_PFNMAP`, `folio_mapped` should be false. Okay, but if they're using a `fault` handler that returns `shmem` pages, we need to carefully investigate the logic, specifically, how `drm_gem_shmem_fault` works. This could be where the problem is lurking. I need to understand that code.
|
| 129/2 |
2026/08/11 01:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/gpu/drm/drm_gem_shmem_helper.c Name:drm_gem_shmem_fault]
Results: map[SourceCode: 671: static vm_fault_t drm_gem_shmem_fault(struct vm_fault *vmf)
672: {
673: return drm_gem_shmem_any_fault(vmf, 0);
674: }
SourceFile:drivers/gpu/drm/drm_gem_shmem_helper.c]
|
| 130/2 |
2026/08/11 01:46 |
llm |
expert |
0m
Model:
Tokens:
input: 92019
output: 50
thoughts: 16
Thoughts: Okay, here's my attempt at summarizing that thought process, tailored for an expert audience, and written in the first person:
**Diving into `drm_gem_shmem_any_fault`**
Alright, time to dig into `drm_gem_shmem_any_fault`. I need to refresh my memory on the specifics of this function within the DRM GEM memory management framework. I know it's related to shared memory, which means it likely plays a crucial role in handling page faults on shared memory objects. I'm guessing it's the point where the kernel intervenes when a process tries to access a page that isn't currently mapped or valid in a shared memory region.
I'm thinking about the implications for performance. Fault handling can be a significant bottleneck if not optimized. I need to consider the locking mechanisms used within this function β are they efficient? Are we holding locks for too long during the fault resolution process? What's the context switching overhead involved? Also, I need to look closely at the error handling β how gracefully does it deal with out-of-memory situations or other hardware-related issues during the page fault?
Furthermore, I need to refresh my knowledge of the interaction between this function and the underlying hardware. How does it interface with the MMU and the graphics card's memory management unit? I'll probably want to trace the execution path and see how it fits into the broader picture of GEM object creation, mapping, and unmapping. I suspect there might be nuances regarding different hardware architectures and how this function adapts. It's time to get a more specific understanding of what `drm_gem_shmem_any_fault` is really doing.
|
| 131/2 |
2026/08/11 01:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/gpu/drm/drm_gem_shmem_helper.c Name:drm_gem_shmem_any_fault]
Results: map[SourceCode: 631: static vm_fault_t drm_gem_shmem_any_fault(struct vm_fault *vmf, unsigned int order)
632: {
633: struct vm_area_struct *vma = vmf->vma;
634: struct drm_gem_object *obj = vma->vm_private_data;
635: struct drm_device *dev = obj->dev;
636: struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
637: loff_t num_pages = obj->size >> PAGE_SHIFT;
638: vm_fault_t ret = VM_FAULT_SIGBUS;
639: struct page **pages = shmem->pages;
640: pgoff_t page_offset = vmf->pgoff - vma->vm_pgoff; /* page offset within VMA */
641: struct page *page;
642: struct folio *folio;
643: unsigned long pfn;
644:
645: if (order && order != PMD_ORDER)
646: return VM_FAULT_FALLBACK;
647:
648: dma_resv_lock(obj->resv, NULL);
649:
650: if (page_offset >= num_pages || drm_WARN_ON_ONCE(dev, !shmem->pages) ||
651: shmem->madv < 0)
652: goto out;
653:
654: page = pages[page_offset];
655: if (drm_WARN_ON_ONCE(dev, !page))
656: goto out;
657: folio = page_folio(page);
658:
659: pfn = page_to_pfn(page);
660:
661: ret = try_insert_pfn(vmf, order, pfn);
662: if (ret == VM_FAULT_NOPAGE)
663: folio_mark_accessed(folio);
664:
665: out:
666: dma_resv_unlock(obj->resv);
667:
668: return ret;
669: }
SourceFile:drivers/gpu/drm/drm_gem_shmem_helper.c]
|
| 132/2 |
2026/08/11 01:46 |
llm |
expert |
0m
Model:
Tokens:
input: 93115
output: 27
thoughts: 158
Thoughts: **Analyzing Folio Mapping and Page Table Entry Behavior**
Okay, hold on a second. This is important. I need to clarify something about how these page frame numbers (PFNs) are inserted into the page tables. It seems like `try_insert_pfn` is ultimately calling `vmf_insert_pfn`. Right, `vmf_insert_pfn` is the one doing the real work of, well, inserting the PFN. Now, I recall that `vmf_insert_pfn` is responsible for actually *setting* the page table entry (PTE), and this involves marking it as special in some way, probably with a `_PAGE_SPECIAL` flag or something along those lines. Crucially, it *doesn't* actually touch the folio's mapcount. That's the key here.
So, since `vmf_insert_pfn` doesn't increment the mapcount, that means a folio that's been inserted via that route... the `folio_mapped(folio)` macro is going to return FALSE, no matter what! This has significant implications. I need to make sure I understand this correctly.
So, this means DRM drivers that leverage `vmf_insert_pfn` are *not* going to have their folios considered "mapped" by the kernel in the traditional sense. Right. Got it.
Now, what about the cases where a driver might use `vm_insert_page` instead? *That* function *does* increment the folio's mapcount. That's a critical difference. I should check if any drivers are using `vm_insert_page` with a folio that is already part of another mapping. If that's the case, we have to consider if there might be unexpected behavior.
|
| 133/2 |
2026/08/11 01:47 |
tool |
grepper |
0m
Args: map[Expression:vm_insert_page PathPrefix:drivers/]
Results: map[Output:drivers/accel/amdxdna/amdxdna_gem.c=527=static int amdxdna_gem_dmabuf_mmap(struct dma_buf *dma_buf, struct vm_area_struct *vma)
--
drivers/accel/amdxdna/amdxdna_gem.c-543- vm_flags_mod(vma, VM_MIXEDMAP, VM_PFNMAP);
drivers/accel/amdxdna/amdxdna_gem.c:544: ret = vm_insert_pages(vma, vma->vm_start, abo->base.pages,
drivers/accel/amdxdna/amdxdna_gem.c-545- &num_pages);
--
drivers/accel/habanalabs/gaudi/gaudi.c=4162=static int gaudi_mmap(struct hl_device *hdev, struct vm_area_struct *vma,
--
drivers/accel/habanalabs/gaudi/gaudi.c-4172- * If dma_alloc_coherent() returns a vmalloc address, set VM_MIXEDMAP
drivers/accel/habanalabs/gaudi/gaudi.c:4173: * so vm_insert_page() can handle it safely. Without this, the kernel
drivers/accel/habanalabs/gaudi/gaudi.c-4174- * may BUG_ON due to VM_PFNMAP.
--
drivers/accel/habanalabs/gaudi2/gaudi2.c=6832=static int gaudi2_mmap(struct hl_device *hdev, struct vm_area_struct *vma,
--
drivers/accel/habanalabs/gaudi2/gaudi2.c-6842- * If dma_alloc_coherent() returns a vmalloc address, set VM_MIXEDMAP
drivers/accel/habanalabs/gaudi2/gaudi2.c:6843: * so vm_insert_page() can handle it safely. Without this, the kernel
drivers/accel/habanalabs/gaudi2/gaudi2.c-6844- * may BUG_ON due to VM_PFNMAP.
--
drivers/android/binder/page_range.rs=128=pub(crate) struct ShrinkablePageRange {
--
drivers/android/binder/page_range.rs-134- mm: ARef<Mm>,
drivers/android/binder/page_range.rs:135: /// Used to synchronize calls to `vm_insert_page` and `zap_vma_range`.
drivers/android/binder/page_range.rs-136- #[pin]
--
drivers/android/binder/page_range.rs=401= unsafe fn use_page_slow(&self, i: usize) -> Result<()> {
--
drivers/android/binder/page_range.rs-454- match vma {
drivers/android/binder/page_range.rs:455: Some(vma) => vma.vm_insert_page(user_page_addr, &new_page)?,
drivers/android/binder/page_range.rs-456- None => return Err(ESRCH),
--
drivers/android/binder_alloc.c=254=static int binder_page_insert(struct binder_alloc *alloc,
--
drivers/android/binder_alloc.c-265- if (binder_alloc_is_mapped(alloc))
drivers/android/binder_alloc.c:266: ret = vm_insert_page(vma, addr, page);
drivers/android/binder_alloc.c-267- vma_end_read(vma);
--
drivers/android/binder_alloc.c-274- if (vma && binder_alloc_is_mapped(alloc))
drivers/android/binder_alloc.c:275: ret = vm_insert_page(vma, addr, page);
drivers/android/binder_alloc.c-276- mmap_read_unlock(mm);
--
drivers/infiniband/hw/bnxt_re/ib_verbs.c=4952=int bnxt_re_mmap(struct ib_ucontext *ib_uctx, struct vm_area_struct *vma)
--
drivers/infiniband/hw/bnxt_re/ib_verbs.c-4982- case BNXT_RE_MMAP_SH_PAGE:
drivers/infiniband/hw/bnxt_re/ib_verbs.c:4983: ret = vm_insert_page(vma, vma->vm_start, virt_to_page(uctx->shpg));
drivers/infiniband/hw/bnxt_re/ib_verbs.c-4984- break;
--
drivers/infiniband/hw/bnxt_re/ib_verbs.c-4996- else
drivers/infiniband/hw/bnxt_re/ib_verbs.c:4997: ret = vm_insert_page(vma, vma->vm_start,
drivers/infiniband/hw/bnxt_re/ib_verbs.c-4998- virt_to_page((void *)bnxt_entry->mem_offset));
--
drivers/infiniband/hw/efa/efa_verbs.c=1978=static int __efa_mmap(struct efa_dev *dev, struct efa_ucontext *ucontext,
--
drivers/infiniband/hw/efa/efa_verbs.c-2018- va += PAGE_SIZE, pfn++) {
drivers/infiniband/hw/efa/efa_verbs.c:2019: err = vm_insert_page(vma, va, pfn_to_page(pfn));
drivers/infiniband/hw/efa/efa_verbs.c-2020- if (err)
--
drivers/infiniband/hw/mlx5/main.c=2459=static int mlx5_ib_mmap_clock_info_page(struct mlx5_ib_dev *dev,
--
drivers/infiniband/hw/mlx5/main.c-2476-
drivers/infiniband/hw/mlx5/main.c:2477: return vm_insert_page(vma, vma->vm_start,
drivers/infiniband/hw/mlx5/main.c-2478- virt_to_page(dev->mdev->clock_info));
--
drivers/infiniband/hw/qedr/verbs.c=382=int qedr_mmap(struct ib_ucontext *ucontext, struct vm_area_struct *vma)
--
drivers/infiniband/hw/qedr/verbs.c-413- case QEDR_USER_MMAP_PHYS_PAGE:
drivers/infiniband/hw/qedr/verbs.c:414: rc = vm_insert_page(vma, vma->vm_start,
drivers/infiniband/hw/qedr/verbs.c-415- virt_to_page(entry->address));
--
drivers/media/pci/intel/ipu6/ipu6-dma.c=297=int ipu6_dma_mmap(struct ipu6_bus_device *sys, struct vm_area_struct *vma,
--
drivers/media/pci/intel/ipu6/ipu6-dma.c-320- for (i = 0; i < count; i++) {
drivers/media/pci/intel/ipu6/ipu6-dma.c:321: ret = vm_insert_page(vma, vma->vm_start + PFN_PHYS(i),
drivers/media/pci/intel/ipu6/ipu6-dma.c-322- info->pages[i]);
--
drivers/pci/p2pdma.c=92=static int p2pmem_alloc_mmap(struct file *filp, struct kobject *kobj,
--
drivers/pci/p2pdma.c-131- /*
drivers/pci/p2pdma.c:132: * vm_insert_page() can sleep, so a reference is taken to mapping
drivers/pci/p2pdma.c-133- * such that rcu_read_unlock() can be done before inserting the
--
drivers/pci/p2pdma.c-151- set_page_count(page, 1);
drivers/pci/p2pdma.c:152: ret = vm_insert_page(vma, vaddr, page);
drivers/pci/p2pdma.c-153- if (ret) {
--
drivers/staging/media/ipu7/ipu7-dma.c=297=int ipu7_dma_mmap(struct ipu7_bus_device *sys, struct vm_area_struct *vma,
--
drivers/staging/media/ipu7/ipu7-dma.c-320- for (i = 0; i < count; i++) {
drivers/staging/media/ipu7/ipu7-dma.c:321: ret = vm_insert_page(vma, vma->vm_start + PFN_PHYS(i),
drivers/staging/media/ipu7/ipu7-dma.c-322- info->pages[i]);
--
drivers/xen/gntalloc.c=500=static int gntalloc_mmap(struct file *filp, struct vm_area_struct *vma)
--
drivers/xen/gntalloc.c-542- gref->users++;
drivers/xen/gntalloc.c:543: rv = vm_insert_page(vma, vma->vm_start + i * PAGE_SIZE,
drivers/xen/gntalloc.c-544- gref->page);
]
|
| 134/2 |
2026/08/11 01:47 |
llm |
expert |
0m
Model:
Tokens:
input: 96181
output: 29
thoughts: 126
Thoughts: **Investigating Kernel Page Cache Folio Mapping Scenarios**
Okay, so none of these driver implementations are actually *directly* mapping a block device's page cache folio. They're all using their own allocated pages, which is a key distinction.
But wait, hold on a second⦠what about situations where the folio is mapped indirectly? My initial thought goes to `splice` or `vmsplice`. Right, right... `vmsplice` is all about mapping *user* pages into a pipe, not the other way around. It doesn't put kernel pages into the user's VMAs. Scratch that.
Hmm, what about `io_uring`? This is the interesting one. `io_uring` has the potential to map kernel buffers into userspace. This is a very real possibility. Okay, but *how* does it do that? Does it end up using `vm_insert_page` somewhere in the stack? That's what I need to verify. Time to dive into the `io_uring` mmap implementation and check the details.
|
| 135/2 |
2026/08/11 01:47 |
tool |
grepper |
0m
Args: map[Expression:vm_insert_page PathPrefix:io_uring/]
Results: map[Output:io_uring/memmap.c=284=static int io_region_mmap(struct io_ring_ctx *ctx,
--
io_uring/memmap.c-291- vm_flags_set(vma, VM_DONTEXPAND);
io_uring/memmap.c:292: return vm_insert_pages(vma, vma->vm_start, mr->pages, &nr_pages);
io_uring/memmap.c-293-}
]
|
| 136/2 |
2026/08/11 01:47 |
llm |
expert |
0m
Model:
Tokens:
input: 96695
output: 34
thoughts: 1733
Thoughts: **Deep Dive into a Mapping Conundrum**
Alright, let's break this down. My initial thought was, "Wait, `io_region_mmap` uses `vm_insert_pages`!" That's a key starting point. Then, what's going on with `mr->pages`? They're the pages allocated by `io_uring` for its rings β SQ/CQ rings. Not block device page cache folios, that's important to remember.
Okay, now, could a race in `unmap_mapping_folio` itself be causing the issue? I need to consider all possibilities. Let's trace it carefully, specifically this section:
```c
i_mmap_lock_read(mapping);
if (unlikely(mapping_mapped(mapping)))
unmap_mapping_range_tree(&mapping->i_mmap, first_index,
last_index, &details);
i_mmap_unlock_read(mapping);
```
What if `mapping_mapped(mapping)` is true, but the `unmap_mapping_range_tree` doesn't unmap the folio because `details.skip_cows = true`? But hold on, I analyzed that `skip_cows` *only* skips anonymous folios, and our folio is a file folio. Then I went deeper to see if `should_zap_folio` returned false for our folio:
```c
static inline bool should_zap_folio(struct zap_details *details,
struct folio *folio)
{
/* If we can make a decision without *folio.. */
if (should_zap_cows(details))
return true;
/* Otherwise we should only zap non-anon folios */
return !folio_test_anon(folio);
}
```
`details->skip_cows` being true makes `should_zap_cows` return false. It then returns `!folio_test_anon(folio)`. Because our folio is a file folio, `folio_test_anon` is false, and therefore, it returns true, meaning it WILL zap our folio! That's good.
Next, I need to consider the PMD path. What if the folio is mapped as a PMD, and `zap_pmd_range` skips it? I recalled that `zap_pmd_range` calls `zap_huge_pmd`, but what if `pmd_is_huge(*pmd)` is true, but `zap_huge_pmd` returns false? Then I saw that `zap_huge_pmd` returns false if `__pmd_trans_huge_lock` fails. But if that fails, it should fall through to `zap_pte_range`, which will BUG if it's a huge PMD. Wait, no! If `__pmd_trans_huge_lock` fails, it means the PMD is NO LONGER huge! So `zap_pte_range` *will* handle the PTEs.
Now, this is an interesting thought. What if the folio is mapped in a VMA, but the VMA's `vm_flags` has `VM_SHARED` cleared? If a user does a `MAP_PRIVATE` mmaps of a block device, it gets a private mapping, reads, the block device's folios are mapped read-only, and writes COWs them. This leads me to ask - Is there any case where a private VMA is NOT in `i_mmap`? No, `vma_link` adds it to `i_mmap` if `vma->vm_file` is not NULL. I need to consider other cases too... What if the user uses `madvise(MADV_DONTNEED)`? `madvise(MADV_FREE)`? `mprotect`? `mlock`? They don't remove the VMA from `i_mmap`.
Now, what if the folio is a THP, and it was split? But if a THP is split, the mapcount is distributed to the subpages. But `folio_mapped` checks the mapcount of the folio. So it should be fine.
Now, let's rethink: `BUG_ON(folio_mapped(folio))` in `folio_unmap_invalidate`. Is it possible that `folio_mapped` is true because of a speculative page reference? No, `folio_mapped` checks `_mapcount` or `_nr_pages_mapped`, not `_refcount`. Speculative references increase `_refcount`, not `_mapcount`.
Okay, is it possible that `unmap_mapping_folio` is called
|
| 137/2 |
2026/08/11 01:47 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/filemap.c Name:filemap_fault]
Results: map[SourceCode:3523: /**
3524: * filemap_fault - read in file data for page fault handling
3525: * @vmf: struct vm_fault containing details of the fault
3526: *
3527: * filemap_fault() is invoked via the vma operations vector for a
3528: * mapped memory region to read in file data during a page fault.
3529: *
3530: * The goto's are kind of ugly, but this streamlines the normal case of having
3531: * it in the page cache, and handles the special cases reasonably without
3532: * having a lot of duplicated code.
3533: *
3534: * vma->vm_mm->mmap_lock must be held on entry.
3535: *
3536: * If our return value has VM_FAULT_RETRY set, it's because the mmap_lock
3537: * may be dropped before doing I/O or by lock_folio_maybe_drop_mmap().
3538: *
3539: * If our return value does not have VM_FAULT_RETRY set, the mmap_lock
3540: * has not been released.
3541: *
3542: * We never return with VM_FAULT_RETRY and a bit from VM_FAULT_ERROR set.
3543: *
3544: * Return: bitwise-OR of %VM_FAULT_ codes.
3545: */
3546: vm_fault_t filemap_fault(struct vm_fault *vmf)
3547: {
3548: int error;
3549: struct file *file = vmf->vma->vm_file;
3550: struct file *fpin = NULL;
3551: struct address_space *mapping = file->f_mapping;
3552: struct inode *inode = mapping->host;
3553: pgoff_t max_idx, index = vmf->pgoff;
3554: struct folio *folio;
3555: vm_fault_t ret = 0;
3556: bool mapping_locked = false;
3557:
3558: max_idx = DIV_ROUND_UP(i_size_read(inode), PAGE_SIZE);
3559: if (unlikely(index >= max_idx))
3560: return VM_FAULT_SIGBUS;
3561:
3562: trace_mm_filemap_fault(mapping, index);
3563:
3564: /*
3565: * Do we have something in the page cache already?
3566: */
3567: folio = filemap_get_folio(mapping, index);
3568: if (likely(!IS_ERR(folio))) {
3569: /*
3570: * We found the page, so try async readahead before waiting for
3571: * the lock.
3572: */
3573: if (!(vmf->flags & FAULT_FLAG_TRIED))
3574: fpin = do_async_mmap_readahead(vmf, folio);
3575: if (unlikely(!folio_test_uptodate(folio))) {
3576: filemap_invalidate_lock_shared(mapping);
3577: mapping_locked = true;
3578: }
3579: } else {
3580: ret = filemap_fault_recheck_pte_none(vmf);
3581: if (unlikely(ret))
3582: return ret;
3583:
3584: /* No page in the page cache at all */
3585: count_vm_event(PGMAJFAULT);
3586: count_memcg_event_mm(vmf->vma->vm_mm, PGMAJFAULT);
3587: ret = VM_FAULT_MAJOR;
3588: fpin = do_sync_mmap_readahead(vmf);
3589: retry_find:
3590: /*
3591: * See comment in filemap_create_folio() why we need
3592: * invalidate_lock
3593: */
3594: if (!mapping_locked) {
3595: filemap_invalidate_lock_shared(mapping);
3596: mapping_locked = true;
3597: }
3598: folio = __filemap_get_folio(mapping, index,
3599: FGP_CREAT|FGP_FOR_MMAP,
3600: vmf->gfp_mask);
3601: if (IS_ERR(folio)) {
3602: if (fpin)
3603: goto out_retry;
3604: filemap_invalidate_unlock_shared(mapping);
3605: return VM_FAULT_OOM;
3606: }
3607: }
3608:
3609: if (!lock_folio_maybe_drop_mmap(vmf, folio, &fpin))
3610: goto out_retry;
3611:
3612: /* Did it get truncated? */
3613: if (unlikely(folio->mapping != mapping)) {
3614: folio_unlock(folio);
3615: folio_put(folio);
3616: goto retry_find;
3617: }
3618: VM_BUG_ON_FOLIO(!folio_contains(folio, index), folio);
3619:
3620: /*
3621: * We have a locked folio in the page cache, now we need to check
3622: * that it's up-to-date. If not, it is going to be due to an error,
3623: * or because readahead was otherwise unable to retrieve it.
3624: */
3625: if (unlikely(!folio_test_uptodate(folio))) {
3626: /*
3627: * If the invalidate lock is not held, the folio was in cache
3628: * and uptodate and now it is not. Strange but possible since we
3629: * didn't hold the page lock all the time. Let's drop
3630: * everything, get the invalidate lock and try again.
3631: */
3632: if (!mapping_locked) {
3633: folio_unlock(folio);
3634: folio_put(folio);
3635: goto retry_find;
3636: }
3637:
3638: /*
3639: * OK, the folio is really not uptodate. This can be because the
3640: * VMA has the VM_RAND_READ flag set, or because an error
3641: * arose. Let's read it in directly.
3642: */
3643: goto page_not_uptodate;
3644: }
3645:
3646: /*
3647: * We've made it this far and we had to drop our mmap_lock, now is the
3648: * time to return to the upper layer and have it re-find the vma and
3649: * redo the fault.
3650: */
3651: if (fpin) {
3652: folio_unlock(folio);
3653: goto out_retry;
3654: }
3655: if (mapping_locked)
3656: filemap_invalidate_unlock_shared(mapping);
3657:
3658: /*
3659: * Found the page and have a reference on it.
3660: * We must recheck i_size under page lock.
3661: */
3662: max_idx = DIV_ROUND_UP(i_size_read(inode), PAGE_SIZE);
3663: if (unlikely(index >= max_idx)) {
3664: folio_unlock(folio);
3665: folio_put(folio);
3666: return VM_FAULT_SIGBUS;
3667: }
3668:
3669: vmf->page = folio_file_page(folio, index);
3670: return ret | VM_FAULT_LOCKED;
3671:
3672: page_not_uptodate:
3673: /*
3674: * Umm, take care of errors if the page isn't up-to-date.
3675: * Try to re-read it _once_. We do this synchronously,
3676: * because there really aren't any performance issues here
3677: * and we need to check for errors.
3678: */
3679: fpin = maybe_unlock_mmap_for_io(vmf, fpin);
3680: error = filemap_read_folio(file, mapping->a_ops->read_folio, folio);
3681: if (fpin)
3682: goto out_retry;
3683: folio_put(folio);
3684:
3685: if (!error || error == AOP_TRUNCATED_PAGE)
3686: goto retry_find;
3687: filemap_invalidate_unlock_shared(mapping);
3688:
3689: return VM_FAULT_SIGBUS;
3690:
3691: out_retry:
3692: /*
3693: * We dropped the mmap_lock, we need to return to the fault handler to
3694: * re-find the vma and come back and find our hopefully still populated
3695: * page.
3696: */
3697: if (!IS_ERR(folio))
3698: folio_put(folio);
3699: if (mapping_locked)
3700: filemap_invalidate_unlock_shared(mapping);
3701: if (fpin)
3702: fput(fpin);
3703: return ret | VM_FAULT_RETRY;
3704: }
SourceFile:mm/filemap.c]
|
| 138/2 |
2026/08/11 01:47 |
llm |
expert |
0m
Model:
Tokens:
input: 102277
output: 45
thoughts: 1103
Thoughts: **Analyzing a Potential Mapcount Leak Leading to a Kernel Bug**
Okay, so `filemap_fault` uses `vmf->vma->vm_file->f_mapping`, which means the folio *should* always belong to `vma->vm_file->f_mapping`, and `unmap_mapping_folio` *should* find the VMA. But, wait a minute, what if we're dealing with a block device file opened with `O_DIRECT`? `O_DIRECT` bypasses the standard page cache. In that scenario, `f_mapping` isn't used as expected. And if the block device is a loop device backed by a file, that has its own mapping, doesn't it?
Then there's the `mmap` case. What if `unmap_mapping_folio` is called while the folio is being mapped during `mmap`? `mmap` adds the VMA to `i_mmap` *before* returning to userspace, so the page fault can't happen before the VMA is present. Makes sense. What about `get_user_pages`? GUP increments the folio's `_refcount`, not `_mapcount`. That's just pinning, no mapping.
Now, let's think critically: Could there be a bug in how `folio_mapcount` is calculated, making `folio_mapped` incorrectly report true? `folio_mapcount` is `atomic_read(&folio->_mapcount) + 1`. If there's a mapcount leak, `folio_mapped` will remain true forever. Syzkaller frequently finds mapcount leaks. If a mapcount leak exists, `unmap_mapping_folio` would unmap all PTEs, but the mapcount will still be > 0. Then, the `BUG_ON(folio_mapped(folio))` will trigger! Bingo, this is a very common manifestation of mapcount leaks! If there's a leak, any file truncation or invalidation will trigger this `BUG_ON`. Since `blkdev_direct_write` invalidates the block device's page cache, it'll hit this bug if a folio in the block device has a leaked mapcount. So this `BUG` could just be a symptom.
If it *is* a mapcount leak, is this exploitable? A leak means the page will never be freed. That's a memory leak, a Denial of Service. But could it lead to privilege escalation? Usually, leaks don't directly give you privilege, but if the mapcount leaks enough times, it could cause an integer overflow! If `_mapcount` overflows, it could become -1 (zero mappings!). Then the page might be freed *while still mapped*! That would be a Use-After-Free! So, *theoretically*, a mapcount leak *can* be exploited if you can trigger it 2^32 times, but that takes a ton of time and memory.
Are there other ways `folio_mapped` could be true? What if the folio is mapped, but the VMA was unlinked from `i_mmap` due to a bug? If a VMA is unlinked but PTEs remain, that's a huge bug (PTE leak / UAF). That's definitely exploitable too.
So, either a mapcount leak or a VMA unlinking bug, both are memory management issues. Are these accessible to unprivileged processes? Yes! Unprivileged processes can `mmap`, `fork`, `munmap`, `mremap`, and open loop devices. But, the bug's in `blkdev_direct_write`, which needs block device write access. Unprivileged users don't have write access to `/dev/sda` usually. But what about loop devices or normal files? Wait, `invalidate_inode_pages2_range` is also called for normal files! For instance, `generic_file_direct_write` calls `kiocb_invalidate_pages` which calls `invalidate_inode_pages2_range`. So, a user can trigger this on a *normal* file by writing `O_DIRECT` to a file with a leaked mapcount! That write trigger is accessible. And so is the root cause. This looks like a potentially exploitable vulnerability accessible to unprivileged users!
Time to check for any recent commits that might address this!
|
| 139/2 |
2026/08/11 01:47 |
tool |
git-log |
0m
Args: map[CodeRegexp:BUG_ON\(folio_mapped\(folio\)\) Count:10 PathPrefix:mm/truncate.c]
Results: map[Output:4a9e23159fd3 mm/truncate: add folio_unmap_invalidate() helper
fae9bc4a9017 truncate: Convert invalidate_inode_pages2_range() to use a folio
]
|
| 140/2 |
2026/08/11 01:47 |
llm |
expert |
0m
Model:
Tokens:
input: 104428
output: 28
thoughts: 22
Thoughts: Here's a summary of my thought process, tailored for an expert like yourself:
**Investigating Memory Leaks and Invalidations in the Mapcount Subsystem**
Alright, I need to get to the bottom of this memory issue. Before I dive too deep into debugging, a quick sanity check is in order. My initial thought is to zero in on potential suspects related to the `mapcount` subsystem, which is often the culprit in memory leak scenarios, especially when dealing with page mappings. So, my search query needs to be focused. I'm going to start by hunting for commits that explicitly address `mapcount` leaks. I'm assuming that fixes for these sorts of problems are likely to exist. My goal is to find if there are any known issues or specific patches I can leverage. I also want to check for problems with `folio_unmap_invalidate`. These are low level functions and so a bug here would be problematic. This function is critical for invalidating page mappings, and a bug here could easily lead to unreleased resources. I need to see if the symptoms align with a known issue and save me time.
|
| 141/2 |
2026/08/11 01:47 |
tool |
git-log |
0m
Args: map[Count:10 MessageRegexps:[folio_unmap_invalidate]]
Results: map[Output:ccaba7858219 Revert "f2fs: remove non-uptodate folio from the page cache in move_data_block"
615d9bb2ccad mm: call ->free_folio() directly in folio_unmap_invalidate()
25b065a744ff mm/filemap: use filemap_end_dropbehind() for read invalidation
095f627add86 mm/filemap: gate dropbehind invalidate on folio !dirty && !writeback
927289988068 mm/truncate: don't skip dirty page in folio_unmap_invalidate()
4a9e23159fd3 mm/truncate: add folio_unmap_invalidate() helper
]
|
| 142/2 |
2026/08/11 01:48 |
llm |
expert |
0m
Model:
Tokens:
input: 104901
output: 66
thoughts: 2699
Thoughts: **Analysis of `folio_unmap_invalidate` and Potential Mapcount Leak**
Okay, let's break this down. I'm looking at commit `927289988068` and its implications on `folio_unmap_invalidate`. It seems they've removed the early return for dirty folios in `folio_unmap_invalidate`. Now, even if a folio is dirty, `unmap_mapping_folio` gets called. This change feels significant, like a potential entry point for a nasty bug. The core check is the `BUG_ON(folio_mapped(folio))` after the unmapping. If `unmap_mapping_folio` successfully unmaps, `folio_mapped` *should* be false. Why isn't it?
My initial thought is a race condition. Is there a scenario where a dirty folio *can't* be unmapped? No, `unmap_mapping_folio` doesn't care about the dirty state. But what about concurrent access? `folio_unmap_invalidate` holds the folio lock. That *should* prevent another thread from faulting the page back in. I also considered `vm_insert_page`, but that's not a block device thing, and I'm looking at `blkdev_direct_write`. I also considered `mremap`, that just moves PTEs. `fork` is the big one. It *could* increment the mapcount concurrently, but I analyzed that earlier and determined the `i_mmap_lock` serialization prevents that from happening. No, the child's VMA will get unmapped too.
Okay, let's explore if the VMA in question isn't in `i_mmap`. However, the parent's VMA MUST be in `i_mmap` for the folio to be mapped in the first place, and fork puts the child VMA in. I looked at `vma_link` and confirmed it's called. What about `MAP_PRIVATE` and COWs? That wouldn't affect the *file* folio's mapcount, it would be the anonymous page. What if `mapping_mapped` is false but the folio is still mapped? The VMA would have been unlinked from `i_mmap` *after* the PTEs have been cleared. This usually happens after. `truncate_inode_pages` doesn't unlink VMAs. What about `vma_adjust`, `vma_merge`, or `vma_split`? That's when I start thinking that perhaps, there could be a case where the PTE is not removed.
Let's look at KVM. Not applicable, doesn't increment mapcount.
I'm starting to zero in on a mapcount leak or a similar memory management issue where the folio is somehow staying mapped. This BUG_ON often signals this. Common causes are missing `folio_put`s or `page_remove_rmap`s, race conditions in page fault handling, or issues with `mremap` or `munmap`. I also know there can be GUP issues, but that doesn't increment mapcount.
This is a kernel BUG, meaning a Denial of Service. And it's exploitable, likely a UAF via mapcount overflow, leading to privilege escalation. It's accessible to unprivileged processes, user namespaces, and it looks like a block device or generic file problem and not a remote/network thing.
Could this be a malicious filesystem? Maybe a filesystem is returning the wrong folio in its fault handler, but syzkaller typically triggers this through normal syscalls. The stack trace indicates a block device write. It's not necessarily a malicious filesystem.
I've examined the code from multiple angles, the most likely issue is that something is preventing the folio from truly being unmapped. It might be a VMA tracking bug, or the PTEs aren't cleared correctly in the VMA. These are still critical MM bugs, so it's most likely exploitable. The results I will set are Exploitable: True, DenialOfService: True, Unprivileged: True, UserNamespace: True, and all other triggers false.
|
| 143/2 |
2026/08/11 01:48 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 144/2 |
2026/08/11 01:48 |
llm |
expert |
0m
Model:
Tokens:
input: 108621
output: 501
thoughts: 422
Thoughts: **My Final Reasoning on the Kernel Bug**
Okay, here's my final take on this bug. The analysis is pretty clear at this point. We've got a `BUG_ON(folio_mapped(folio))` in `folio_unmap_invalidate()`. This is triggered during the page cache invalidation process, specifically when unmapping a folio. The goal of this function is to ensure a folio is no longer mapped in any user page tables after it's been evicted from the page cache, using `unmap_mapping_folio()`. If `folio_mapped()` returns true *after* this unmap operation, that's a serious inconsistency. It shouldn't be mapped.
There are two likely culprits, and they're both nasty:
First, a **mapcount leak**. This is a classic reference counting bug. The folio's `_mapcount` gets bumped up somewhere (page fault, `mremap`, or a `munmap` race are the likely suspects) but never decremented properly. `unmap_mapping_folio()` goes and clears all page table entries, but that leaked mapcount remains. Since `folio_mapped()` is basically just checking that mapcount, it then erroneously reports the folio is still mapped. And while it's a critical memory error, I see this as potentially exploitable. A clever attacker could theoretically overflow that 32-bit counter with enough triggers (the leak triggered 2^32 times) and end up with a Use-After-Free (UAF) vulnerability, which is basically game over for the system. Privilege escalation for sure.
Second, a **VMA tracking bug**. This is even more concerning. The folio is mapped within a VMA (virtual memory area), but that VMA isn't properly linked to the file's `i_mmap` interval tree. So, `unmap_mapping_folio()` can't find the VMA and therefore can't clear the associated page table entries. So the folio really *is* still mapped, but we don't know about it. Again, this opens the door to UAF or even outright page table corruption.
The trigger is interesting. It's coming through `blkdev_direct_write()`, specifically an `O_DIRECT` write to a block device, but I can see that same invalidation path (`invalidate_inode_pages2_range()`) is also reachable with `O_DIRECT` writes to regular files. So, the attack surface is much larger than it initially seems. The fact that it's accessible to unprivileged users is a big deal. The `BUG_ON` then immediately crashes the kernel, leading to a Denial of Service.
|
| 145/1 |
2026/08/11 01:48 |
action |
format-explanation |
0m
Results: map[Explanation:The bug is a `BUG_ON(folio_mapped(folio))` in `folio_unmap_invalidate()`. This
function is called to invalidate a folio from the page cache (e.g., during an
`O_DIRECT` write or file truncation). It first attempts to unmap the folio from
any user page tables by calling `unmap_mapping_folio()`. If the folio is still
reported as mapped after this operation, it triggers the `BUG_ON`.
This indicates a severe memory management inconsistency, which is typically
caused by one of two scenarios:
1. **Mapcount Leak**: A reference counting bug where the folio's `_mapcount` was
incremented but not properly decremented (e.g., due to an error path bug, or a
race condition during page faults, `mremap`, or `munmap`). When
`unmap_mapping_folio()` clears all actual page table entries, the leaked
mapcount remains, causing `folio_mapped()` to incorrectly return true.
2. **VMA Tracking Bug**: The folio is mapped in a VMA that is not properly
linked to the file's `i_mmap` interval tree. Consequently,
`unmap_mapping_folio()` fails to find the VMA and does not clear the page table
entries, leaving the folio genuinely mapped.
**Security Impact:**
* **Exploitable**: Yes. A mapcount leak can theoretically be exploited by
overflowing the 32-bit `_mapcount` counter (by triggering the leak 2^32 times).
This causes the mapcount to wrap around, potentially leading to a Use-After-Free
(UAF) of the page, which can be leveraged for privilege escalation. A VMA
tracking bug can similarly lead to UAF or page table corruption.
* **Denial of Service**: Yes. The `BUG_ON` immediately halts the kernel,
allowing an attacker to easily crash the system.
* **Unprivileged Access**: Yes. While the stack trace shows the bug being
triggered via `blkdev_direct_write()` (writing to a block device), the
underlying invalidation path (`invalidate_inode_pages2_range()`) is also
reachable via `O_DIRECT` writes to normal files or other standard memory
management syscalls (`mmap`, `madvise`, etc.). Thus, an unprivileged user can
trigger the root cause and the subsequent crash.]
|