Look up another CVE · open CVE-2025-39779 in the interactive tracker
CVE-2025-39779
btrfs: subpage: keep TOWRITE tag until folio is cleaned
Published 2025-09-11 (13 months ago) by the Linux kernel CNA. Subsystem fs/btrfs. Debian urgency: not yet assigned.
What to do, per Debian release
Debian's own status for CVE-2025-39779 in every release this site tracks, and what each one means in practice. Every status below is the one the Debian Security Team publishes for this CVE. source
| Release | Status | Version | What this means |
|---|---|---|---|
| Debian 12bookworm | vulnerable | 6.1.190-1 | No fix published anywhere yet, neither in Debian nor upstream for the series bookworm tracks. Watch the Debian tracker page for this CVE. Advisories for bookworm have historically landed a median of 23 days after publication. |
| Debian 13trixie | fixedDSA-6008-1 · 2025-09-22 · text | 6.12.48-1 | Update the kernel and reboot. DSA-6008-1 shipped the fix on 2025-09-22, in 6.12.48-1. A running kernel keeps the old code until the machine restarts. |
| Debian 14forky | fixed | 6.16.5-1 | Update the kernel and reboot. Fixed in 6.16.5-1. A running kernel keeps the old code until the machine restarts. |
| Debian unstablesid | fixed | 6.16.5-1 | Update the kernel and reboot. Fixed in 6.16.5-1. A running kernel keeps the old code until the machine restarts. |
The vulnerability
What the bug is
btrfs_subpage_set_writeback() calls folio_start_writeback() the first time a folio is written back, and it also clears the PAGECACHE_TAG_TOWRITE tag even if there are still dirty blocks in the folio. This can break ordering guarantees, such as those required by btrfs_wait_ordered_extents(). That ordering breakage leads to a real failure. For example, running generic/464 on a zoned setup will hit the following ASSERT. This happens because the broken ordering fails to flush existing dirty pages before the file size is truncated. assertion failed: !list_empty(&ordered->list) :: 0, in fs/btrfs/zoned.c:1899 ------------[ cut here ]------------ kernel BUG at fs/btrfs/zoned.c:1899! Oops: invalid opcode: 0000 [#1] SMP NOPTI CPU: 2 UID: 0 PID: 1906169 Comm: kworker/u130:2 Kdump: loaded Not tainted 6.16.0-rc6-BTRFS-ZNS+ #554 PREEMPT(voluntary) Hardware name: Supermicro Super Server/H12SSL-NT, BIOS 2.0 02/22/2021 Workqueue: btrfs-endio-write btrfs_work_helper [btrfs] RIP: 0010:btrfs_finish_ordered_zoned.cold+0x50/0x52 [btrfs] RSP: 0018:ffffc9002efdbd60 EFLAGS: 00010246 RAX: 000000000000004c RBX: ffff88811923c4e0 RCX: 0000000000000000 RDX: 0000000000000000 RSI: ffffffff827e38b1 RDI: 00000000ffffffff RBP: ffff88810005d000 R08: 00000000ffffdfff R09: ffffffff831051c8 R10: ffffffff83055220 R11: 0000000000000000 R12: ffff8881c2458c00 R13: ffff88811923c540 R14: ffff88811923c5e8 R15: ffff8881c1bd9680 FS: 0000000000000000(0000) GS:ffff88a04acd0000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007f907c7a918c CR3: 0000000004024000 CR4: 0000000000350ef0 Call Trace: <TASK> ? srso_return_thunk+0x5/0x5f btrfs_finish_ordered_io+0x4a/0x60 [btrfs] btrfs_work_helper+0xf9/0x490 [btrfs] process_one_work+0x204/0x590 ? srso_return_thunk+0x5/0x5f worker_thread+0x1d6/0x3d0 ? __pfx_worker_thread+0x10/0x10 kthread+0x118/0x230 ? __pfx_kthread+0x10/0x10 ret_from_fork+0x205/0x260 ? __pfx_kthread+0x10/0x10 ret_from_fork_asm+0x1a/0x30 </TASK> Consider process A calling writepages() with WB_SYNC_NONE. In zoned mode or for compressed writes, it locks several folios for delalloc and starts writing them out. Let's call the last locked folio folio X. Suppose the write range only partially covers folio X, leaving some pages dirty. Process A calls btrfs_subpage_set_writeback() when building a bio. This function call clears the TOWRITE tag of folio X, whose size = 8K and the block size = 4K. It is following state. 0 4K 8K |/////|/////| (flag: DIRTY, tag: DIRTY) <-----> Process A will write this range. Now suppose process B concurrently calls writepages() with WB_SYNC_ALL. It calls tag_pages_for_writeback() to tag dirty folios with PAGECACHE_TAG_TOWRITE. Since folio X is still dirty, it gets tagged. Then, B collects tagged folios using filemap_get_folios_tag() and must wait for folio X to be written before returning from writepages(). 0 4K 8K |/////|/////| (flag: DIRTY, tag: DIRTY|TOWRITE) However, between tagging and collecting, process A may call btrfs_subpage_set_writeback() and clear folio X's TOWRITE tag. 0 4K 8K | |/////| (flag: DIRTY|WRITEBACK, tag: DIRTY) As a result, process B won't see folio X in its batch, and returns without waiting for it. This breaks the WB_SYNC_ALL ordering requirement. Fix this by using btrfs_subpage_set_writeback_keepwrite(), which retains the TOWRITE tag. We now manually clear the tag only after the folio becomes clean, via the xas operation.
Where it lives
The fix touches these files. If the subsystem is one you do not use, the practical exposure is lower, though the package is still the vulnerable one.
fs/btrfs/subpage.c
- What this is
- BTRFS FILE SYSTEMMAINTAINERS section matched on fs/btrfs/ · linux-btrfs@vger.kernel.org · Maintained
- Kernel module
- btrfs
Check it on the machine
grep -w CONFIG_BTRFS_FS /boot/config-$(uname -r)Reads the booted kernel's own configuration, on the machine and nowhere else. =y is built in, =m is a module, and an is not set line means the code is not there.
lsmod | grep -w btrfsPrints a line if that module is loaded right now, and nothing if it is not. One that is not loaded can still be loaded later by anything that needs it.
Show whether Debian builds this, per release
Debian builds this as a loadable module. Every release below compiles it as a module on every flavour, so whether the code is in the running kernel depends on whether that module is loaded.
- bookworm, trixie, forky, sidm
- m
- a loadable module on every flavour - check lsmod, can be blacklisted
- Build switch
- CONFIG_BTRFS_FS
From the configuration Debian publishes for every kernel flavour it builds. It covers only the architectures Debian ships a linux-config package for, and nothing about a kernel you built yourself. Debian's own status above stays authoritative about the source package.MAINTAINERS kbuild Makefiles Debian linux-config packages
Signals
- Published
- 2025-09-11 (13 months ago)source
- CVSS
- 7.8 high
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:Hsource recompute it - Attack vector
- local (AV:L)source
- EPSS
- 0.15%, higher than 4.2% of all CVEssource
- CISA KEV
- not listedthe KEV catalogue
- Debian urgency
- not yet assignedsource
- Subsystem
- fs/btrfs
Upstream fixes
The versions the CNA records as introducing and fixing this, and the commit for each. source
| Introduced | Fixed in | Commit |
|---|---|---|
| 5.13 | 6.12.44 | 3d61136945a7 |
| 5.13 | 6.16.4 | bce7a5c77a1e |
| 5.13 | 6.17 | b1511360c8ac |
Debian advisories
| Advisory | Date | Shipped to | Debian |
|---|---|---|---|
| DSA-6008-1 | 2025-09-22 | trixie 6.12.48-1 | tracker |
Why the CNA scored it this way
Quoted from the kernel CNA's own scoring file, word for word. A highlighted metric carries the most severe value CVSS v3.1 defines for it, which is what pushes the score up. source
- AV:LAttack vector, local
- The vulnerable path is btrfs buffered-writeback reached only through local file syscalls (write/mmap dirtying, sync_file_range, fsync, ftruncate) on a mounted btrfs filesystem; there is no network or remote-peer input path into btrfs_subpage_set_writeback(). Exploitation requires the ability to run code on the machine.
- AC:LAttack complexity, low
- The attacker drives both sides of the race himself — one thread issuing sync_file_range(SYNC_FILE_RANGE_WRITE) (WB_SYNC_NONE) over a partial range of a folio while another loops on fsync()/ftruncate() (WB_SYNC_ALL) — and can retry indefinitely until the window between tag_pages_for_writeback() and filemap_get_folios_tag() is hit. Subpage mode (sectorsize < folio size) is a standard shipping deployment on 16K/64K-page arm64 and ppc64 kernels, not a rare debug option, and the compression/zoned delalloc behaviour that leaves partial dirty folios comes from ordinary mount options.
- PR:LPrivileges required, low
- Only an ordinary unprivileged user with write access to any file on the btrfs filesystem (e.g. /home or /tmp) is needed — no capabilities, namespaces, or root, since dirtying pages and calling fsync/sync_file_range/ftruncate on one's own file is enough. The attacker owns the file involved in the race.
- UI:NUser interaction, none
- The attacker performs every step on a file it creates itself; no victim must mount a filesystem, open a file, or take any action. The already-mounted btrfs is normal system state.
- S:UScope, unchanged
- The broken writeback ordering, the empty-list dereference, and the resulting filesystem metadata damage are all handled by the same kernel security authority. No VM, IOMMU, or sandbox boundary is crossed.
- C:HConfidentiality impact, high
- With CONFIG_BTRFS_ASSERT off, btrfs_finish_ordered_zoned() performs list_first_entry() on an empty list head, an out-of-bounds read of kernel memory preceding btrfs_ordered_extent::list, and that garbage value is written into ordered->disk_bytenr / em->disk_bytenr and persisted to the file extent item, remapping the attacker's file onto an arbitrary filesystem logical address whose contents (other files' data or freed blocks) can then be read back. The same ordering breakage lets truncate proceed without flushing, the exact condition btrfs's truncated_len machinery exists to prevent from exposing stale on-disk data.
- I:HIntegrity impact, high
- The corrupted logical address is committed into the extent map and the on-disk file extent item, an uncontrolled modification of filesystem metadata that can also misdirect extent-tree reference accounting onto blocks owned by other files. Independently, WB_SYNC_ALL writepages returning before the folio is written means fsync()/sync() acknowledge data that was never persisted, violating the durability and transaction-commit ordering guarantees btrfs_wait_ordered_extents() provides.
- A:HAvailability impact, high
- The bug was reproduced as a hard kernel BUG — "assertion failed: !list_empty(&ordered->list)" / "Oops: invalid opcode" in btrfs_finish_ordered_zoned() from a btrfs-endio-write worker — which panics the machine. Even without CONFIG_BTRFS_ASSERT, the fabricated btrfs_ordered_sum pointer and the possibly-NULL extent map in btrfs_rewrite_logical_zoned() readily oops the kernel.