VYPR
Unrated severityNVD Advisory· Published May 27, 2026· Updated May 27, 2026

CVE-2026-46066

CVE-2026-46066

Description

In the Linux kernel, the following vulnerability has been resolved:

ceph: fix num_ops off-by-one when crypto allocation fails

move_dirty_folio_in_page_array() may fail if the file is encrypted, the dirty folio is not the first in the batch, and it fails to allocate a bounce buffer to hold the ciphertext. When that happens, ceph_process_folio_batch() simply redirties the folio and flushes the current batch -- it can retry that folio in a future batch.

However, if this failed folio is not contiguous with the last folio that did make it into the batch, then ceph_process_folio_batch() has already incremented ceph_wbc->num_ops; because it doesn't follow through and add the discontiguous folio to the array, ceph_submit_write() -- which expects that ceph_wbc->num_ops accurately reflects the number of contiguous ranges (and therefore the required number of "write extent" ops) in the writeback -- will panic the kernel:

BUG_ON(ceph_wbc->op_idx + 1 != req->r_num_ops);

This issue can be reproduced on affected kernels by writing to fscrypt-enabled CephFS file(s) with a 4KiB-written/4KiB-skipped/repeat pattern (total filesize should not matter) and gradually increasing the system's memory pressure until a bounce buffer allocation fails.

Fix this crash by decrementing ceph_wbc->num_ops back to the correct value when move_dirty_folio_in_page_array() fails, but the folio already started counting a new (i.e. still-empty) extent.

The defect corrected by this patch has existed since 2022 (see first Fixes:), but another bug blocked multi-folio encrypted writeback until recently (see second Fixes:). The second commit made it into 6.18.16, 6.19.6, and 7.0-rc1, unmasking the panic in those versions. This patch therefore fixes a regression (panic) introduced by cac190c7674f.

Affected products

2

Patches

6
ba12c1e57889

ceph: fix num_ops off-by-one when crypto allocation fails

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitSam EdwardsMar 18, 2026Fixed in 7.0.4via kernel-cna
2 files changed · +8 2
  • fs/ceph/addr.c+4 1 modified
    diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c
    index 2090fc78529cb4..44553556ac742b 100644
    --- a/fs/ceph/addr.c
    +++ b/fs/ceph/addr.c
    @@ -1365,6 +1365,10 @@ void ceph_process_folio_batch(struct address_space *mapping,
     		rc = move_dirty_folio_in_page_array(mapping, wbc, ceph_wbc,
     				folio);
     		if (rc) {
    +			/* Did we just begin a new contiguous op? Nevermind! */
    +			if (ceph_wbc->len == 0)
    +				ceph_wbc->num_ops--;
    +
     			folio_redirty_for_writepage(wbc, folio);
     			folio_unlock(folio);
     			break;
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ceph/addr.c+4 1 modified
    diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c
    index 2090fc78529cb4..44553556ac742b 100644
    --- a/fs/ceph/addr.c
    +++ b/fs/ceph/addr.c
    @@ -1365,6 +1365,10 @@ void ceph_process_folio_batch(struct address_space *mapping,
     		rc = move_dirty_folio_in_page_array(mapping, wbc, ceph_wbc,
     				folio);
     		if (rc) {
    +			/* Did we just begin a new contiguous op? Nevermind! */
    +			if (ceph_wbc->len == 0)
    +				ceph_wbc->num_ops--;
    +
     			folio_redirty_for_writepage(wbc, folio);
     			folio_unlock(folio);
     			break;
    -- 
    cgit 1.3-korg
    
    
    
a0d9555bf9ea

ceph: fix num_ops off-by-one when crypto allocation fails

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitSam EdwardsMar 18, 2026Fixed in 7.1-rc1via kernel-cna
2 files changed · +8 2
  • fs/ceph/addr.c+4 1 modified
    diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c
    index 2090fc78529cb4..44553556ac742b 100644
    --- a/fs/ceph/addr.c
    +++ b/fs/ceph/addr.c
    @@ -1365,6 +1365,10 @@ void ceph_process_folio_batch(struct address_space *mapping,
     		rc = move_dirty_folio_in_page_array(mapping, wbc, ceph_wbc,
     				folio);
     		if (rc) {
    +			/* Did we just begin a new contiguous op? Nevermind! */
    +			if (ceph_wbc->len == 0)
    +				ceph_wbc->num_ops--;
    +
     			folio_redirty_for_writepage(wbc, folio);
     			folio_unlock(folio);
     			break;
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ceph/addr.c+4 1 modified
    diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c
    index 2090fc78529cb4..44553556ac742b 100644
    --- a/fs/ceph/addr.c
    +++ b/fs/ceph/addr.c
    @@ -1365,6 +1365,10 @@ void ceph_process_folio_batch(struct address_space *mapping,
     		rc = move_dirty_folio_in_page_array(mapping, wbc, ceph_wbc,
     				folio);
     		if (rc) {
    +			/* Did we just begin a new contiguous op? Nevermind! */
    +			if (ceph_wbc->len == 0)
    +				ceph_wbc->num_ops--;
    +
     			folio_redirty_for_writepage(wbc, folio);
     			folio_unlock(folio);
     			break;
    -- 
    cgit 1.3-korg
    
    
    
6200f41d6fcf

ceph: fix num_ops off-by-one when crypto allocation fails

2 files changed · +8 2
  • fs/ceph/addr.c+4 1 modified
    diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c
    index 390f122feeaaf5..3af6795cb3c15d 100644
    --- a/fs/ceph/addr.c
    +++ b/fs/ceph/addr.c
    @@ -1373,6 +1373,10 @@ int ceph_process_folio_batch(struct address_space *mapping,
     		rc = move_dirty_folio_in_page_array(mapping, wbc, ceph_wbc,
     				folio);
     		if (rc) {
    +			/* Did we just begin a new contiguous op? Nevermind! */
    +			if (ceph_wbc->len == 0)
    +				ceph_wbc->num_ops--;
    +
     			rc = 0;
     			folio_redirty_for_writepage(wbc, folio);
     			folio_unlock(folio);
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ceph/addr.c+4 1 modified
    diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c
    index 390f122feeaaf5..3af6795cb3c15d 100644
    --- a/fs/ceph/addr.c
    +++ b/fs/ceph/addr.c
    @@ -1373,6 +1373,10 @@ int ceph_process_folio_batch(struct address_space *mapping,
     		rc = move_dirty_folio_in_page_array(mapping, wbc, ceph_wbc,
     				folio);
     		if (rc) {
    +			/* Did we just begin a new contiguous op? Nevermind! */
    +			if (ceph_wbc->len == 0)
    +				ceph_wbc->num_ops--;
    +
     			rc = 0;
     			folio_redirty_for_writepage(wbc, folio);
     			folio_unlock(folio);
    -- 
    cgit 1.3-korg
    
    
    
a0d9555bf9ea

ceph: fix num_ops off-by-one when crypto allocation fails

2 files changed · +8 2
  • fs/ceph/addr.c+4 1 modified
    diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c
    index 2090fc78529cb4..44553556ac742b 100644
    --- a/fs/ceph/addr.c
    +++ b/fs/ceph/addr.c
    @@ -1365,6 +1365,10 @@ void ceph_process_folio_batch(struct address_space *mapping,
     		rc = move_dirty_folio_in_page_array(mapping, wbc, ceph_wbc,
     				folio);
     		if (rc) {
    +			/* Did we just begin a new contiguous op? Nevermind! */
    +			if (ceph_wbc->len == 0)
    +				ceph_wbc->num_ops--;
    +
     			folio_redirty_for_writepage(wbc, folio);
     			folio_unlock(folio);
     			break;
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ceph/addr.c+4 1 modified
    diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c
    index 2090fc78529cb4..44553556ac742b 100644
    --- a/fs/ceph/addr.c
    +++ b/fs/ceph/addr.c
    @@ -1365,6 +1365,10 @@ void ceph_process_folio_batch(struct address_space *mapping,
     		rc = move_dirty_folio_in_page_array(mapping, wbc, ceph_wbc,
     				folio);
     		if (rc) {
    +			/* Did we just begin a new contiguous op? Nevermind! */
    +			if (ceph_wbc->len == 0)
    +				ceph_wbc->num_ops--;
    +
     			folio_redirty_for_writepage(wbc, folio);
     			folio_unlock(folio);
     			break;
    -- 
    cgit 1.3-korg
    
    
    
ba12c1e57889

ceph: fix num_ops off-by-one when crypto allocation fails

2 files changed · +8 2
  • fs/ceph/addr.c+4 1 modified
    diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c
    index 2090fc78529cb4..44553556ac742b 100644
    --- a/fs/ceph/addr.c
    +++ b/fs/ceph/addr.c
    @@ -1365,6 +1365,10 @@ void ceph_process_folio_batch(struct address_space *mapping,
     		rc = move_dirty_folio_in_page_array(mapping, wbc, ceph_wbc,
     				folio);
     		if (rc) {
    +			/* Did we just begin a new contiguous op? Nevermind! */
    +			if (ceph_wbc->len == 0)
    +				ceph_wbc->num_ops--;
    +
     			folio_redirty_for_writepage(wbc, folio);
     			folio_unlock(folio);
     			break;
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ceph/addr.c+4 1 modified
    diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c
    index 2090fc78529cb4..44553556ac742b 100644
    --- a/fs/ceph/addr.c
    +++ b/fs/ceph/addr.c
    @@ -1365,6 +1365,10 @@ void ceph_process_folio_batch(struct address_space *mapping,
     		rc = move_dirty_folio_in_page_array(mapping, wbc, ceph_wbc,
     				folio);
     		if (rc) {
    +			/* Did we just begin a new contiguous op? Nevermind! */
    +			if (ceph_wbc->len == 0)
    +				ceph_wbc->num_ops--;
    +
     			folio_redirty_for_writepage(wbc, folio);
     			folio_unlock(folio);
     			break;
    -- 
    cgit 1.3-korg
    
    
    
6200f41d6fcf

ceph: fix num_ops off-by-one when crypto allocation fails

2 files changed · +8 2
  • fs/ceph/addr.c+4 1 modified
    diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c
    index 390f122feeaaf5..3af6795cb3c15d 100644
    --- a/fs/ceph/addr.c
    +++ b/fs/ceph/addr.c
    @@ -1373,6 +1373,10 @@ int ceph_process_folio_batch(struct address_space *mapping,
     		rc = move_dirty_folio_in_page_array(mapping, wbc, ceph_wbc,
     				folio);
     		if (rc) {
    +			/* Did we just begin a new contiguous op? Nevermind! */
    +			if (ceph_wbc->len == 0)
    +				ceph_wbc->num_ops--;
    +
     			rc = 0;
     			folio_redirty_for_writepage(wbc, folio);
     			folio_unlock(folio);
    -- 
    cgit 1.3-korg
    
    
    
  • fs/ceph/addr.c+4 1 modified
    diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c
    index 390f122feeaaf5..3af6795cb3c15d 100644
    --- a/fs/ceph/addr.c
    +++ b/fs/ceph/addr.c
    @@ -1373,6 +1373,10 @@ int ceph_process_folio_batch(struct address_space *mapping,
     		rc = move_dirty_folio_in_page_array(mapping, wbc, ceph_wbc,
     				folio);
     		if (rc) {
    +			/* Did we just begin a new contiguous op? Nevermind! */
    +			if (ceph_wbc->len == 0)
    +				ceph_wbc->num_ops--;
    +
     			rc = 0;
     			folio_redirty_for_writepage(wbc, folio);
     			folio_unlock(folio);
    -- 
    cgit 1.3-korg
    
    
    

Vulnerability mechanics

Root cause

"Off-by-one error in ceph_process_folio_batch() where ceph_wbc->num_ops is incremented for a new contiguous extent but not decremented when move_dirty_folio_in_page_array() subsequently fails, causing a mismatch with the actual number of write extent operations."

Attack vector

An attacker with write access to an fscrypt-enabled CephFS file can trigger this bug by writing a pattern of 4KiB-written/4KiB-skipped/repeat (any total filesize) while gradually increasing system memory pressure until a bounce buffer allocation for ciphertext fails [patch_id=2659994]. When move_dirty_folio_in_page_array() fails on a discontiguous folio that already caused num_ops to be incremented, the kernel panics via BUG_ON(ceph_wbc->op_idx + 1 != req->r_num_ops) in ceph_submit_write() [patch_id=2659994]. The precondition is that the file is encrypted (fscrypt) and the system is under memory pressure.

Affected code

The vulnerable function is ceph_process_folio_batch() in fs/ceph/addr.c [patch_id=2659994]. The bug occurs in the error path after move_dirty_folio_in_page_array() returns a non-zero status.

What the fix does

The patch adds a check in ceph_process_folio_batch() (in fs/ceph/addr.c) after move_dirty_folio_in_page_array() fails: if ceph_wbc->len is 0, meaning the folio had just started a new (still-empty) contiguous extent, it decrements ceph_wbc->num_ops to undo the premature increment [patch_id=2659994]. This ensures that num_ops accurately reflects the number of contiguous ranges that were actually added to the page array, preventing the BUG_ON panic in ceph_submit_write().

Preconditions

  • configThe CephFS file must be encrypted (fscrypt-enabled).
  • inputWrite pattern of 4KiB written / 4KiB skipped, repeated (any total filesize).
  • configSystem must be under sufficient memory pressure that a bounce buffer (GFP_KERNEL) allocation for ciphertext fails.

Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

3

News mentions

0

No linked articles in our index yet.