* [BUG] [~6.6 Kernel] Corruption when retrying encrypted sync writes
@ 2026-02-18 18:00 Bharath SM
2026-02-19 11:05 ` David Howells
2026-02-27 10:25 ` Shyam Prasad N
0 siblings, 2 replies; 7+ messages in thread
From: Bharath SM @ 2026-02-18 18:00 UTC (permalink / raw)
To: David Howells, Shyam Prasad N, Shyam Prasad, Steve French, CIFS,
Paulo Alcantara, Enzo Matsumiya, Henrique Carvalho, Bharath S M
[-- Attachment #1: Type: text/plain, Size: 5260 bytes --]
We are noticing a data corruption issue in kernels based on stable
6.6.y. Especially, when a synchronous writes retried after a
connection reset.
Based on investigation so far, it looks like we are having issue in
the following code path:
When SMB3 encryption is enabled, partial-page buffered writes hit the
synchronous write path in cifs_write_end() when the folio is not
uptodate (!folio_test_uptodate(folio)), it calls cifs_write() directly
with the kmap()'d page cache buffer, bypassing the async writeback
path.
cifs_write() calls SMB2_write(), which places the write payload in
rq_iov[1], pointing directly at the page cache buffer. When
smb3_init_transform_rq() builds the encryption request, it shares
rq_iov by pointer (new->rq_iov = old->rq_iov), and crypt_message()
encrypts in-place via aead_request_set_crypt(req, sg, sg, ...). This
destroys the original page cache data. If the write gets -EAGAIN after
encryption (e.g., connection reset), cifs_write() re-sends the
now-ciphertext buffer as if it were plaintext, resulting in
double-encrypted garbage on the server. The server accepts it and
returns success.
Please let me know if you have seen this issue in the past, your
comments on the analysis and probable fixes.
Repro steps: Attached repro.zip with repro scripts and instructions:
1) Mount with SMB3 encryption enabled
2) Perform buffered writes in a loop (e.g., echo "known_pattern" >> file)
3) Kill the TCP connection during writes (ss -K dport 445) to force
retryable errors
4) Read the file back and compare against expected content
Issue can occur when all below conditions met in buffered writes:
1) SMB2 encryption is active
2) Sync write path: Writes reached SMB2_write via cifs_write
3) Retryable network error for writes: When EAGAIN or ECONABORTED
returned from cifs_send_recv().
Here is a the sequence of operations leading to issue:
write(2) syscall
└─ cifs_write_end() [file.c]
└─ cifs_write() [file.c]
│ iov[1].iov_base = write_data ← page cache pointer enters iov[1]
│
└─ server->ops->sync_write() [file.c]
└─ smb2_sync_write() [smb2ops.c:]
└─ SMB2_write() [smb2pdu.c:]
│ rqst.rq_iov = iov ← rqst points to iov[]
(with page cache in [1])
│ rqst.rq_nvec = n_vec+1 ← BUG: payload in
rq_iov, not rq_iter
│
└─ cifs_send_recv() [transport.c:1305]
└─ compound_send_recv() [transport.c:1071]
│
└─ smb_send_rqst() [transport.c:427]
│ if (flags & CIFS_TRANSFORM_REQ) ←
YES for SMB3 encryption
│
└─ server->ops->init_transform_rq()
[smb2ops.c:~4398]
│ = smb3_init_transform_rq()
│ new->rq_iov = old->rq_iov ←
SHARES pointer (not copied!)
│ size =
iov_iter_count(old->rq_iter) = 0 ← empty, no copy
│
└─ __smb_send_rqst() [transport.c:272]
│ → crypt_message() [smb2ops.c:~4280]
│ → smb2_get_aead_req()
[smb2ops.c:~4196]
│ sg = scatterwalk from rq_iov[0..n]
│
aead_request_set_crypt(req, sg, sg, ...)
│
^^^ ^^^
│
src=dst → IN-PLACE encrypt
│
│ iov[1] (= page cache) is now
AES ciphertext
│
└─ kernel_sendmsg() / sock_sendmsg()
→ sends encrypted data on wire
← rc = -EAGAIN (connection dropped)
is_replayable_error(rc) == true or cifs_write while loop detects EAGAIN
goto replay_again ← loops back with corrupted iov[1]
└─ SMB2_write() re-sends...
└─ smb3_init_transform_rq() ← encrypts ciphertext AGAIN
└─ crypt_message() ← double-encrypted garbage
└─ server writes it to disk ← CORRUPTION
Modifying SMB2_write function by adding payload to rq_iter seems to
help here. Need to further test.
With below fix, when rq_iter size > 0 code in smb3_init_transform_rq
allocates fresh pages, copies the data via copy_page_from_iter(), and
encrypts the copy instead of the original.
Please let me know your comments.
rqst.rq_iov = iov;
-rqst.rq_nvec = n_vec + 1;
+rqst.rq_nvec = 1;
+iov_iter_kvec(&rqst.rq_iter, ITER_SOURCE, &iov[1], n_vec,
+ io_parms->length);
+rqst.rq_iter_size = io_parms->length;
[-- Attachment #2: repro.zip --]
[-- Type: application/x-zip-compressed, Size: 2484 bytes --]
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [BUG] [~6.6 Kernel] Corruption when retrying encrypted sync writes 2026-02-18 18:00 [BUG] [~6.6 Kernel] Corruption when retrying encrypted sync writes Bharath SM @ 2026-02-19 11:05 ` David Howells 2026-02-19 11:14 ` Bharath SM 2026-02-27 10:25 ` Shyam Prasad N 1 sibling, 1 reply; 7+ messages in thread From: David Howells @ 2026-02-19 11:05 UTC (permalink / raw) To: Bharath SM Cc: dhowells, Shyam Prasad N, Shyam Prasad, Steve French, CIFS, Paulo Alcantara, Enzo Matsumiya, Henrique Carvalho, Bharath S M Bharath SM <bharathsm.hsk@gmail.com> wrote: > We are noticing a data corruption issue in kernels based on stable > 6.6.y. Especially, when a synchronous writes retried after a > connection reset. > ... > When SMB3 encryption is enabled, partial-page buffered writes hit the > synchronous write path in cifs_write_end() This is pre-netfslib, right? David ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [BUG] [~6.6 Kernel] Corruption when retrying encrypted sync writes 2026-02-19 11:05 ` David Howells @ 2026-02-19 11:14 ` Bharath SM 0 siblings, 0 replies; 7+ messages in thread From: Bharath SM @ 2026-02-19 11:14 UTC (permalink / raw) To: David Howells Cc: Shyam Prasad N, Shyam Prasad, Steve French, CIFS, Paulo Alcantara, Enzo Matsumiya, Henrique Carvalho, Bharath S M On Thu, Feb 19, 2026 at 3:05 AM David Howells <dhowells@redhat.com> wrote: > > Bharath SM <bharathsm.hsk@gmail.com> wrote: > > > We are noticing a data corruption issue in kernels based on stable > > 6.6.y. Especially, when a synchronous writes retried after a > > connection reset. > > ... > > When SMB3 encryption is enabled, partial-page buffered writes hit the > > synchronous write path in cifs_write_end() > > This is pre-netfslib, right? Yes, its v6.6 stable kernel which has folio changes but not netfs. netfs cut happened in the ~6.10 kernel for SMB clients. ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [BUG] [~6.6 Kernel] Corruption when retrying encrypted sync writes 2026-02-18 18:00 [BUG] [~6.6 Kernel] Corruption when retrying encrypted sync writes Bharath SM 2026-02-19 11:05 ` David Howells @ 2026-02-27 10:25 ` Shyam Prasad N 2026-03-03 18:03 ` Bharath SM 1 sibling, 1 reply; 7+ messages in thread From: Shyam Prasad N @ 2026-02-27 10:25 UTC (permalink / raw) To: Bharath SM Cc: David Howells, Shyam Prasad N, Shyam Prasad, Steve French, CIFS, Paulo Alcantara, Enzo Matsumiya, Henrique Carvalho, Bharath S M On Wed, Feb 18, 2026 at 11:30 PM Bharath SM <bharathsm.hsk@gmail.com> wrote: > > We are noticing a data corruption issue in kernels based on stable > 6.6.y. Especially, when a synchronous writes retried after a > connection reset. > > Based on investigation so far, it looks like we are having issue in > the following code path: > When SMB3 encryption is enabled, partial-page buffered writes hit the > synchronous write path in cifs_write_end() when the folio is not > uptodate (!folio_test_uptodate(folio)), it calls cifs_write() directly > with the kmap()'d page cache buffer, bypassing the async writeback > path. > cifs_write() calls SMB2_write(), which places the write payload in > rq_iov[1], pointing directly at the page cache buffer. When > smb3_init_transform_rq() builds the encryption request, it shares > rq_iov by pointer (new->rq_iov = old->rq_iov), and crypt_message() > encrypts in-place via aead_request_set_crypt(req, sg, sg, ...). This > destroys the original page cache data. If the write gets -EAGAIN after > encryption (e.g., connection reset), cifs_write() re-sends the > now-ciphertext buffer as if it were plaintext, resulting in > double-encrypted garbage on the server. The server accepts it and > returns success. > Please let me know if you have seen this issue in the past, your > comments on the analysis and probable fixes. > > Repro steps: Attached repro.zip with repro scripts and instructions: > 1) Mount with SMB3 encryption enabled > 2) Perform buffered writes in a loop (e.g., echo "known_pattern" >> file) > 3) Kill the TCP connection during writes (ss -K dport 445) to force > retryable errors > 4) Read the file back and compare against expected content > Looking at the callers, it looks like simple_fallocate_range* functions that make use of sync writes are also susceptible to this issue. > Issue can occur when all below conditions met in buffered writes: > 1) SMB2 encryption is active > 2) Sync write path: Writes reached SMB2_write via cifs_write > 3) Retryable network error for writes: When EAGAIN or ECONABORTED > returned from cifs_send_recv(). > > Here is a the sequence of operations leading to issue: > write(2) syscall > └─ cifs_write_end() [file.c] > └─ cifs_write() [file.c] > │ iov[1].iov_base = write_data ← page cache pointer enters iov[1] > │ > └─ server->ops->sync_write() [file.c] > └─ smb2_sync_write() [smb2ops.c:] > └─ SMB2_write() [smb2pdu.c:] > │ rqst.rq_iov = iov ← rqst points to iov[] > (with page cache in [1]) > │ rqst.rq_nvec = n_vec+1 ← BUG: payload in > rq_iov, not rq_iter > │ > └─ cifs_send_recv() [transport.c:1305] > └─ compound_send_recv() [transport.c:1071] > │ > └─ smb_send_rqst() [transport.c:427] > │ if (flags & CIFS_TRANSFORM_REQ) ← > YES for SMB3 encryption > │ > └─ server->ops->init_transform_rq() > [smb2ops.c:~4398] > │ = smb3_init_transform_rq() > │ new->rq_iov = old->rq_iov ← > SHARES pointer (not copied!) > │ size = > iov_iter_count(old->rq_iter) = 0 ← empty, no copy > │ > └─ __smb_send_rqst() [transport.c:272] > │ → crypt_message() [smb2ops.c:~4280] > │ → smb2_get_aead_req() > [smb2ops.c:~4196] > │ sg = scatterwalk from rq_iov[0..n] > │ > aead_request_set_crypt(req, sg, sg, ...) > │ > ^^^ ^^^ > │ > src=dst → IN-PLACE encrypt > │ > │ iov[1] (= page cache) is now > AES ciphertext > │ > └─ kernel_sendmsg() / sock_sendmsg() > → sends encrypted data on wire > > ← rc = -EAGAIN (connection dropped) > > is_replayable_error(rc) == true or cifs_write while loop detects EAGAIN > goto replay_again ← loops back with corrupted iov[1] > └─ SMB2_write() re-sends... > └─ smb3_init_transform_rq() ← encrypts ciphertext AGAIN > └─ crypt_message() ← double-encrypted garbage > └─ server writes it to disk ← CORRUPTION > > > > Modifying SMB2_write function by adding payload to rq_iter seems to > help here. Need to further test. > With below fix, when rq_iter size > 0 code in smb3_init_transform_rq > allocates fresh pages, copies the data via copy_page_from_iter(), and > encrypts the copy instead of the original. > Please let me know your comments. > > > rqst.rq_iov = iov; > -rqst.rq_nvec = n_vec + 1; > +rqst.rq_nvec = 1; > +iov_iter_kvec(&rqst.rq_iter, ITER_SOURCE, &iov[1], n_vec, > + io_parms->length); > +rqst.rq_iter_size = io_parms->length; Another option is to initialize iov_iter_xarray with rqst.rq_buffer, similar to what smb3_init_transform_rq does. But this should work too. Changes look good to me. Please submit a formal patch. -- Regards, Shyam ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [BUG] [~6.6 Kernel] Corruption when retrying encrypted sync writes 2026-02-27 10:25 ` Shyam Prasad N @ 2026-03-03 18:03 ` Bharath SM 2026-03-04 8:09 ` Shyam Prasad N 0 siblings, 1 reply; 7+ messages in thread From: Bharath SM @ 2026-03-03 18:03 UTC (permalink / raw) To: Shyam Prasad N, David Howells, Steve French, CIFS, Paulo Alcantara, Enzo Matsumiya, Henrique Carvalho Cc: Shyam Prasad N, Shyam Prasad, Bharath S M, Greg KH [-- Attachment #1: Type: text/plain, Size: 7183 bytes --] On Fri, Feb 27, 2026 at 2:25 AM Shyam Prasad N <nspmangalore@gmail.com> wrote: > > On Wed, Feb 18, 2026 at 11:30 PM Bharath SM <bharathsm.hsk@gmail.com> wrote: > > > > We are noticing a data corruption issue in kernels based on stable > > 6.6.y. Especially, when a synchronous writes retried after a > > connection reset. > > > > Based on investigation so far, it looks like we are having issue in > > the following code path: > > When SMB3 encryption is enabled, partial-page buffered writes hit the > > synchronous write path in cifs_write_end() when the folio is not > > uptodate (!folio_test_uptodate(folio)), it calls cifs_write() directly > > with the kmap()'d page cache buffer, bypassing the async writeback > > path. > > cifs_write() calls SMB2_write(), which places the write payload in > > rq_iov[1], pointing directly at the page cache buffer. When > > smb3_init_transform_rq() builds the encryption request, it shares > > rq_iov by pointer (new->rq_iov = old->rq_iov), and crypt_message() > > encrypts in-place via aead_request_set_crypt(req, sg, sg, ...). This > > destroys the original page cache data. If the write gets -EAGAIN after > > encryption (e.g., connection reset), cifs_write() re-sends the > > now-ciphertext buffer as if it were plaintext, resulting in > > double-encrypted garbage on the server. The server accepts it and > > returns success. > > Please let me know if you have seen this issue in the past, your > > comments on the analysis and probable fixes. > > > > Repro steps: Attached repro.zip with repro scripts and instructions: > > 1) Mount with SMB3 encryption enabled > > 2) Perform buffered writes in a loop (e.g., echo "known_pattern" >> file) > > 3) Kill the TCP connection during writes (ss -K dport 445) to force > > retryable errors > > 4) Read the file back and compare against expected content > > > Looking at the callers, it looks like simple_fallocate_range* > functions that make use of sync writes are also susceptible to this > issue. > > > Issue can occur when all below conditions met in buffered writes: > > 1) SMB2 encryption is active > > 2) Sync write path: Writes reached SMB2_write via cifs_write > > 3) Retryable network error for writes: When EAGAIN or ECONABORTED > > returned from cifs_send_recv(). > > > > Here is a the sequence of operations leading to issue: > > write(2) syscall > > └─ cifs_write_end() [file.c] > > └─ cifs_write() [file.c] > > │ iov[1].iov_base = write_data ← page cache pointer enters iov[1] > > │ > > └─ server->ops->sync_write() [file.c] > > └─ smb2_sync_write() [smb2ops.c:] > > └─ SMB2_write() [smb2pdu.c:] > > │ rqst.rq_iov = iov ← rqst points to iov[] > > (with page cache in [1]) > > │ rqst.rq_nvec = n_vec+1 ← BUG: payload in > > rq_iov, not rq_iter > > │ > > └─ cifs_send_recv() [transport.c:1305] > > └─ compound_send_recv() [transport.c:1071] > > │ > > └─ smb_send_rqst() [transport.c:427] > > │ if (flags & CIFS_TRANSFORM_REQ) ← > > YES for SMB3 encryption > > │ > > └─ server->ops->init_transform_rq() > > [smb2ops.c:~4398] > > │ = smb3_init_transform_rq() > > │ new->rq_iov = old->rq_iov ← > > SHARES pointer (not copied!) > > │ size = > > iov_iter_count(old->rq_iter) = 0 ← empty, no copy > > │ > > └─ __smb_send_rqst() [transport.c:272] > > │ → crypt_message() [smb2ops.c:~4280] > > │ → smb2_get_aead_req() > > [smb2ops.c:~4196] > > │ sg = scatterwalk from rq_iov[0..n] > > │ > > aead_request_set_crypt(req, sg, sg, ...) > > │ > > ^^^ ^^^ > > │ > > src=dst → IN-PLACE encrypt > > │ > > │ iov[1] (= page cache) is now > > AES ciphertext > > │ > > └─ kernel_sendmsg() / sock_sendmsg() > > → sends encrypted data on wire > > > > ← rc = -EAGAIN (connection dropped) > > > > is_replayable_error(rc) == true or cifs_write while loop detects EAGAIN > > goto replay_again ← loops back with corrupted iov[1] > > └─ SMB2_write() re-sends... > > └─ smb3_init_transform_rq() ← encrypts ciphertext AGAIN > > └─ crypt_message() ← double-encrypted garbage > > └─ server writes it to disk ← CORRUPTION > > > > > > > > Modifying SMB2_write function by adding payload to rq_iter seems to > > help here. Need to further test. > > With below fix, when rq_iter size > 0 code in smb3_init_transform_rq > > allocates fresh pages, copies the data via copy_page_from_iter(), and > > encrypts the copy instead of the original. > > Please let me know your comments. > > > > > > rqst.rq_iov = iov; > > -rqst.rq_nvec = n_vec + 1; > > +rqst.rq_nvec = 1; > > +iov_iter_kvec(&rqst.rq_iter, ITER_SOURCE, &iov[1], n_vec, > > + io_parms->length); > > +rqst.rq_iter_size = io_parms->length; > > Another option is to initialize iov_iter_xarray with rqst.rq_buffer, > similar to what smb3_init_transform_rq does. But this should work too. > Changes look good to me. Please submit a formal patch. Thank you, Attached the patch please review. Also created the minimal repro script with network disconnects. Further investigation on this issue indicates that the issue is not specific to the 6.6 Kernel; instead, the issue can happen in kernels <6.10 including 6.1, 6.6 and 5.15 and beyond. On older kernels ~5.15 with deferred closes may reduce exposure because deferred handles might keep help pages in memory and which may help writes to avoid sync_write path. But commit 262b73ef442e (smb3 client: fix open hardlink on deferred close file error, backported till 6.1) appears to increase the likelihood of taking the sync_write path because we close deferred handles aggressively in some cases, which makes the existing encryption write corruption bug easier to trigger. The commit does not introduce the bug; It increases trigger frequency for writes taking sync_write path. I will send out this patch to the stable mailing list separately. [-- Attachment #2: 0001-smb-client-fix-page-cache-corruption-from-in-place-e.patch --] [-- Type: application/octet-stream, Size: 2333 bytes --] From d9df05cb5eddaf160a2947f4510728095f62e873 Mon Sep 17 00:00:00 2001 From: Bharath SM <bharathsm@microsoft.com> Date: Tue, 3 Mar 2026 14:05:30 +0000 Subject: [PATCH] smb: client: fix page cache corruption from in-place encryption in SMB2_write SMB2_write() passes data kvecs inline in rq_iov by setting rqst.rq_nvec = n_vec + 1. When SMB3 encryption is negotiated, smb3_init_transform_rq() -> crypt_message() encrypts data in the kvec buffers in-place. For synchronous writes through cifs_write(), the kvec buffers point directly into the page cache via kmap(). In-place encryption overwrites the page cache with ciphertext. If the send fails with a replayable error such as -EAGAIN (e.g., from a connection reset), SMB2_write() retries the write using the same iov[1] buffer. Since iov[1] now contains ciphertext from the first attempt, the retry encrypts and sends ciphertext-as-data to the server, resulting in data corruption. The corruption is most likely to be observed when connections are unstable, as reconnects trigger write retries that re-send the already-encrypted page cache data. The sync path can be reached during partial-page O_WRONLY writes when the page is not in cache (common for append workloads with repeated open/write/close patterns). The async write path (smb2_async_writev) is not affected because it passes data via rqst.rq_iter, which the encryption layer handles without modifying the source buffers. Fix by setting rq_nvec = 1 (header only) and moving data kvecs into rq_iter via iov_iter_kvec(). Signed-off-by: Bharath SM <bharathsm@microsoft.com> Reviewed-by: Shyam Prasad N <sprasad@microsoft.com> Cc: stable@vger.kernel.org #v6.1~v6.9 --- fs/smb/client/smb2pdu.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index a8890ae21714..a88a19dec494 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -5072,7 +5072,11 @@ SMB2_write(const unsigned int xid, struct cifs_io_parms *io_parms, memset(&rqst, 0, sizeof(struct smb_rqst)); rqst.rq_iov = iov; - rqst.rq_nvec = n_vec + 1; + rqst.rq_nvec = 1; + iov_iter_kvec(&rqst.rq_iter, ITER_SOURCE, &iov[1], n_vec, + io_parms->length); + rqst.rq_iter_size = io_parms->length; + if (retries) smb2_set_replay(server, &rqst); -- 2.45.4 [-- Attachment #3: repro_corruption.sh --] [-- Type: text/x-sh, Size: 4390 bytes --] #!/bin/bash # # Reproducer for page cache corruption caused by in-place encryption # in SMB2_write(). # # SMB2_write() passes page cache data inline in rq_iov (rq_nvec = n_vec + 1). # When SMB3 encryption is active, crypt_message() encrypts these buffers # in-place, overwriting page cache with ciphertext. On replay after a # replayable send failure, the ciphertext is re-sent as plaintext. # The server stores ciphertext as file content. No error to userspace. # # The sync write path (cifs_write_end -> cifs_write -> SMB2_write) is taken # when O_WRONLY partial writes hit a non-uptodate page at a mid-file offset. # # Strategy: 10 concurrent O_WRONLY mid-file writers + periodic ss -K to # kill the socket mid-encrypt. Typically reproduces in 1-3 cycles. # # Usage: sudo ./test_smb2_write_encrypt_corruption.sh /mnt/smbshare/testdir # set -u TESTDIR="${1:-.}" NFILES=10 ERRORS=0 cleanup() { kill -9 $PIDS 2>/dev/null wait 2>/dev/null } trap cleanup EXIT echo "=== SMB2_write() encryption page cache corruption reproducer ===" echo "Dir: $TESTDIR" echo "Kernel: $(uname -r)" echo "Start: $(date)" echo "" mkdir -p "$TESTDIR" # Create files filled with a known letter: f1='A', f2='B', ..., f10='J' echo "Creating test files..." for f in $(seq 1 $NFILES); do BYTE=$((0x40 + f)) # 0x41='A', 0x42='B', ..., 0x4a='J' python3 -c "open('$TESTDIR/f${f}.dat','wb').write(bytes([$BYTE])*65536)" done sync; sleep 1 echo "Done." # Writer: O_WRONLY mid-file partial writes (triggers sync path) # - O_WRONLY: write_begin() skips reading the page -> page stays not-uptodate # - Mid-file: write_begin()'s EOF zero-fill shortcut does not apply # - 200 bytes: partial page, page never marked uptodate # -> cifs_write_end() takes sync path: kmap -> cifs_write -> SMB2_write writer() { local fname=$1 byte=$2 while true; do python3 -c " import os, random try: fd = os.open('$fname', os.O_WRONLY) off = random.randint(100, 60000) os.lseek(fd, off, os.SEEK_SET) os.write(fd, bytes([$byte]) * 200) os.close(fd) except: pass " 2>/dev/null done } echo "Starting $NFILES writers..." PIDS="" for f in $(seq 1 $NFILES); do BYTE=$((0x40 + f)) writer "$TESTDIR/f${f}.dat" $BYTE & PIDS="$PIDS $!" done echo "Running disruption cycles..." echo "" for cycle in $(seq 1 15); do # Warm up: let writers run normally sleep 3 # Cold cache: evict pages so next writes hit non-uptodate pages echo 3 > /proc/sys/vm/drop_caches 2>/dev/null # Let writes accumulate on cold cache (all go through sync path) sleep 2 # Kill socket: if a write is between crypt_message() and TCP send, # iov[1] (page cache) is already encrypted in-place. The send fails, # replay_again re-sends ciphertext as plaintext to the server. # we can also replace ss -K with tcpkill or similar to target the socket more precisely, but ss -K is simpler and works well enough. ss -K dport = 445 > /dev/null 2>&1 echo " [cycle $cycle] socket killed at $(date +%T)" # Wait for reconnect, then re-read from server (not local cache) sleep 4 echo 3 > /proc/sys/vm/drop_caches 2>/dev/null sleep 1 # Verify: every byte should match expected value CYCLE_ERRORS=0 for f in $(seq 1 $NFILES); do BYTE=$((0x40 + f)) python3 -c " import sys try: data = open('$TESTDIR/f${f}.dat','rb').read() except OSError as e: print(f' SKIP f${f}.dat: {e}') sys.exit(0) for i,b in enumerate(data): if b != $BYTE: print(f' CORRUPTION f${f}.dat offset=0x{i:x} exp=0x{$BYTE:02x}({chr($BYTE)}) got=0x{b:02x}') sys.exit(1) " if [ $? -ne 0 ]; then CYCLE_ERRORS=$((CYCLE_ERRORS + 1)) ERRORS=$((ERRORS + 1)) fi done echo " [cycle $cycle] verified ($CYCLE_ERRORS errors)" done echo "" echo "Stopping writers..." kill $PIDS 2>/dev/null wait 2>/dev/null echo "" echo "=== Results ===" echo "Errors: $ERRORS" echo "End: $(date)" if [ $ERRORS -gt 0 ]; then echo "FAIL: In-place encryption corrupted page cache data." echo "Fix: pass data via rq_iter instead of inline in rq_iov." else echo "PASS: No corruption detected. Try running again." fi rm -f "$TESTDIR"/f*.dat ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [BUG] [~6.6 Kernel] Corruption when retrying encrypted sync writes 2026-03-03 18:03 ` Bharath SM @ 2026-03-04 8:09 ` Shyam Prasad N 2026-03-09 10:39 ` Bharath SM 0 siblings, 1 reply; 7+ messages in thread From: Shyam Prasad N @ 2026-03-04 8:09 UTC (permalink / raw) To: Bharath SM Cc: David Howells, Steve French, CIFS, Paulo Alcantara, Enzo Matsumiya, Henrique Carvalho, Shyam Prasad N, Shyam Prasad, Bharath S M, Greg KH On Tue, Mar 3, 2026 at 11:34 PM Bharath SM <bharathsm.hsk@gmail.com> wrote: > > On Fri, Feb 27, 2026 at 2:25 AM Shyam Prasad N <nspmangalore@gmail.com> wrote: > > > > On Wed, Feb 18, 2026 at 11:30 PM Bharath SM <bharathsm.hsk@gmail.com> wrote: > > > > > > We are noticing a data corruption issue in kernels based on stable > > > 6.6.y. Especially, when a synchronous writes retried after a > > > connection reset. > > > > > > Based on investigation so far, it looks like we are having issue in > > > the following code path: > > > When SMB3 encryption is enabled, partial-page buffered writes hit the > > > synchronous write path in cifs_write_end() when the folio is not > > > uptodate (!folio_test_uptodate(folio)), it calls cifs_write() directly > > > with the kmap()'d page cache buffer, bypassing the async writeback > > > path. > > > cifs_write() calls SMB2_write(), which places the write payload in > > > rq_iov[1], pointing directly at the page cache buffer. When > > > smb3_init_transform_rq() builds the encryption request, it shares > > > rq_iov by pointer (new->rq_iov = old->rq_iov), and crypt_message() > > > encrypts in-place via aead_request_set_crypt(req, sg, sg, ...). This > > > destroys the original page cache data. If the write gets -EAGAIN after > > > encryption (e.g., connection reset), cifs_write() re-sends the > > > now-ciphertext buffer as if it were plaintext, resulting in > > > double-encrypted garbage on the server. The server accepts it and > > > returns success. > > > Please let me know if you have seen this issue in the past, your > > > comments on the analysis and probable fixes. > > > > > > Repro steps: Attached repro.zip with repro scripts and instructions: > > > 1) Mount with SMB3 encryption enabled > > > 2) Perform buffered writes in a loop (e.g., echo "known_pattern" >> file) > > > 3) Kill the TCP connection during writes (ss -K dport 445) to force > > > retryable errors > > > 4) Read the file back and compare against expected content > > > > > Looking at the callers, it looks like simple_fallocate_range* > > functions that make use of sync writes are also susceptible to this > > issue. > > > > > Issue can occur when all below conditions met in buffered writes: > > > 1) SMB2 encryption is active > > > 2) Sync write path: Writes reached SMB2_write via cifs_write > > > 3) Retryable network error for writes: When EAGAIN or ECONABORTED > > > returned from cifs_send_recv(). > > > > > > Here is a the sequence of operations leading to issue: > > > write(2) syscall > > > └─ cifs_write_end() [file.c] > > > └─ cifs_write() [file.c] > > > │ iov[1].iov_base = write_data ← page cache pointer enters iov[1] > > > │ > > > └─ server->ops->sync_write() [file.c] > > > └─ smb2_sync_write() [smb2ops.c:] > > > └─ SMB2_write() [smb2pdu.c:] > > > │ rqst.rq_iov = iov ← rqst points to iov[] > > > (with page cache in [1]) > > > │ rqst.rq_nvec = n_vec+1 ← BUG: payload in > > > rq_iov, not rq_iter > > > │ > > > └─ cifs_send_recv() [transport.c:1305] > > > └─ compound_send_recv() [transport.c:1071] > > > │ > > > └─ smb_send_rqst() [transport.c:427] > > > │ if (flags & CIFS_TRANSFORM_REQ) ← > > > YES for SMB3 encryption > > > │ > > > └─ server->ops->init_transform_rq() > > > [smb2ops.c:~4398] > > > │ = smb3_init_transform_rq() > > > │ new->rq_iov = old->rq_iov ← > > > SHARES pointer (not copied!) > > > │ size = > > > iov_iter_count(old->rq_iter) = 0 ← empty, no copy > > > │ > > > └─ __smb_send_rqst() [transport.c:272] > > > │ → crypt_message() [smb2ops.c:~4280] > > > │ → smb2_get_aead_req() > > > [smb2ops.c:~4196] > > > │ sg = scatterwalk from rq_iov[0..n] > > > │ > > > aead_request_set_crypt(req, sg, sg, ...) > > > │ > > > ^^^ ^^^ > > > │ > > > src=dst → IN-PLACE encrypt > > > │ > > > │ iov[1] (= page cache) is now > > > AES ciphertext > > > │ > > > └─ kernel_sendmsg() / sock_sendmsg() > > > → sends encrypted data on wire > > > > > > ← rc = -EAGAIN (connection dropped) > > > > > > is_replayable_error(rc) == true or cifs_write while loop detects EAGAIN > > > goto replay_again ← loops back with corrupted iov[1] > > > └─ SMB2_write() re-sends... > > > └─ smb3_init_transform_rq() ← encrypts ciphertext AGAIN > > > └─ crypt_message() ← double-encrypted garbage > > > └─ server writes it to disk ← CORRUPTION > > > > > > > > > > > > Modifying SMB2_write function by adding payload to rq_iter seems to > > > help here. Need to further test. > > > With below fix, when rq_iter size > 0 code in smb3_init_transform_rq > > > allocates fresh pages, copies the data via copy_page_from_iter(), and > > > encrypts the copy instead of the original. > > > Please let me know your comments. > > > > > > > > > rqst.rq_iov = iov; > > > -rqst.rq_nvec = n_vec + 1; > > > +rqst.rq_nvec = 1; > > > +iov_iter_kvec(&rqst.rq_iter, ITER_SOURCE, &iov[1], n_vec, > > > + io_parms->length); > > > +rqst.rq_iter_size = io_parms->length; > > > > Another option is to initialize iov_iter_xarray with rqst.rq_buffer, > > similar to what smb3_init_transform_rq does. But this should work too. > > Changes look good to me. Please submit a formal patch. > > Thank you, Attached the patch please review. Also created the minimal > repro script with network disconnects. > > Further investigation on this issue indicates that the issue is not > specific to the 6.6 Kernel; instead, > the issue can happen in kernels <6.10 including 6.1, 6.6 and 5.15 and beyond. > > On older kernels ~5.15 with deferred closes may reduce exposure > because deferred handles might keep help pages > in memory and which may help writes to avoid sync_write path. > But commit 262b73ef442e (smb3 client: fix open hardlink on deferred > close file error, backported till 6.1) appears to > increase the likelihood of taking the sync_write path because we close > deferred handles aggressively in some cases, > which makes the existing encryption write corruption bug easier to > trigger. The commit does not introduce the bug; > It increases trigger frequency for writes taking sync_write path. > > I will send out this patch to the stable mailing list separately. Looks good to me. My RB is already added -- Regards, Shyam ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [BUG] [~6.6 Kernel] Corruption when retrying encrypted sync writes 2026-03-04 8:09 ` Shyam Prasad N @ 2026-03-09 10:39 ` Bharath SM 0 siblings, 0 replies; 7+ messages in thread From: Bharath SM @ 2026-03-09 10:39 UTC (permalink / raw) To: Shyam Prasad N Cc: David Howells, Steve French, CIFS, Paulo Alcantara, Enzo Matsumiya, Henrique Carvalho, Shyam Prasad N, Shyam Prasad, Bharath S M, Greg KH Functions in mainline kernel, such as cifs_sfu_make_node() and smb3_create_mf_symlink() use SMB2_write() to send data and can be affected by an in-place encryption corruption bug on retry. Submitted a slightly modified patch titled 'smb: client: fix in-place encryption corruption in SMB2_write()' to mainline. Please take a look. On Wed, Mar 4, 2026 at 12:09 AM Shyam Prasad N <nspmangalore@gmail.com> wrote: > > On Tue, Mar 3, 2026 at 11:34 PM Bharath SM <bharathsm.hsk@gmail.com> wrote: > > > > On Fri, Feb 27, 2026 at 2:25 AM Shyam Prasad N <nspmangalore@gmail.com> wrote: > > > > > > On Wed, Feb 18, 2026 at 11:30 PM Bharath SM <bharathsm.hsk@gmail.com> wrote: > > > > > > > > We are noticing a data corruption issue in kernels based on stable > > > > 6.6.y. Especially, when a synchronous writes retried after a > > > > connection reset. > > > > > > > > Based on investigation so far, it looks like we are having issue in > > > > the following code path: > > > > When SMB3 encryption is enabled, partial-page buffered writes hit the > > > > synchronous write path in cifs_write_end() when the folio is not > > > > uptodate (!folio_test_uptodate(folio)), it calls cifs_write() directly > > > > with the kmap()'d page cache buffer, bypassing the async writeback > > > > path. > > > > cifs_write() calls SMB2_write(), which places the write payload in > > > > rq_iov[1], pointing directly at the page cache buffer. When > > > > smb3_init_transform_rq() builds the encryption request, it shares > > > > rq_iov by pointer (new->rq_iov = old->rq_iov), and crypt_message() > > > > encrypts in-place via aead_request_set_crypt(req, sg, sg, ...). This > > > > destroys the original page cache data. If the write gets -EAGAIN after > > > > encryption (e.g., connection reset), cifs_write() re-sends the > > > > now-ciphertext buffer as if it were plaintext, resulting in > > > > double-encrypted garbage on the server. The server accepts it and > > > > returns success. > > > > Please let me know if you have seen this issue in the past, your > > > > comments on the analysis and probable fixes. > > > > > > > > Repro steps: Attached repro.zip with repro scripts and instructions: > > > > 1) Mount with SMB3 encryption enabled > > > > 2) Perform buffered writes in a loop (e.g., echo "known_pattern" >> file) > > > > 3) Kill the TCP connection during writes (ss -K dport 445) to force > > > > retryable errors > > > > 4) Read the file back and compare against expected content > > > > > > > Looking at the callers, it looks like simple_fallocate_range* > > > functions that make use of sync writes are also susceptible to this > > > issue. > > > > > > > Issue can occur when all below conditions met in buffered writes: > > > > 1) SMB2 encryption is active > > > > 2) Sync write path: Writes reached SMB2_write via cifs_write > > > > 3) Retryable network error for writes: When EAGAIN or ECONABORTED > > > > returned from cifs_send_recv(). > > > > > > > > Here is a the sequence of operations leading to issue: > > > > write(2) syscall > > > > └─ cifs_write_end() [file.c] > > > > └─ cifs_write() [file.c] > > > > │ iov[1].iov_base = write_data ← page cache pointer enters iov[1] > > > > │ > > > > └─ server->ops->sync_write() [file.c] > > > > └─ smb2_sync_write() [smb2ops.c:] > > > > └─ SMB2_write() [smb2pdu.c:] > > > > │ rqst.rq_iov = iov ← rqst points to iov[] > > > > (with page cache in [1]) > > > > │ rqst.rq_nvec = n_vec+1 ← BUG: payload in > > > > rq_iov, not rq_iter > > > > │ > > > > └─ cifs_send_recv() [transport.c:1305] > > > > └─ compound_send_recv() [transport.c:1071] > > > > │ > > > > └─ smb_send_rqst() [transport.c:427] > > > > │ if (flags & CIFS_TRANSFORM_REQ) ← > > > > YES for SMB3 encryption > > > > │ > > > > └─ server->ops->init_transform_rq() > > > > [smb2ops.c:~4398] > > > > │ = smb3_init_transform_rq() > > > > │ new->rq_iov = old->rq_iov ← > > > > SHARES pointer (not copied!) > > > > │ size = > > > > iov_iter_count(old->rq_iter) = 0 ← empty, no copy > > > > │ > > > > └─ __smb_send_rqst() [transport.c:272] > > > > │ → crypt_message() [smb2ops.c:~4280] > > > > │ → smb2_get_aead_req() > > > > [smb2ops.c:~4196] > > > > │ sg = scatterwalk from rq_iov[0..n] > > > > │ > > > > aead_request_set_crypt(req, sg, sg, ...) > > > > │ > > > > ^^^ ^^^ > > > > │ > > > > src=dst → IN-PLACE encrypt > > > > │ > > > > │ iov[1] (= page cache) is now > > > > AES ciphertext > > > > │ > > > > └─ kernel_sendmsg() / sock_sendmsg() > > > > → sends encrypted data on wire > > > > > > > > ← rc = -EAGAIN (connection dropped) > > > > > > > > is_replayable_error(rc) == true or cifs_write while loop detects EAGAIN > > > > goto replay_again ← loops back with corrupted iov[1] > > > > └─ SMB2_write() re-sends... > > > > └─ smb3_init_transform_rq() ← encrypts ciphertext AGAIN > > > > └─ crypt_message() ← double-encrypted garbage > > > > └─ server writes it to disk ← CORRUPTION > > > > > > > > > > > > > > > > Modifying SMB2_write function by adding payload to rq_iter seems to > > > > help here. Need to further test. > > > > With below fix, when rq_iter size > 0 code in smb3_init_transform_rq > > > > allocates fresh pages, copies the data via copy_page_from_iter(), and > > > > encrypts the copy instead of the original. > > > > Please let me know your comments. > > > > > > > > > > > > rqst.rq_iov = iov; > > > > -rqst.rq_nvec = n_vec + 1; > > > > +rqst.rq_nvec = 1; > > > > +iov_iter_kvec(&rqst.rq_iter, ITER_SOURCE, &iov[1], n_vec, > > > > + io_parms->length); > > > > +rqst.rq_iter_size = io_parms->length; > > > > > > Another option is to initialize iov_iter_xarray with rqst.rq_buffer, > > > similar to what smb3_init_transform_rq does. But this should work too. > > > Changes look good to me. Please submit a formal patch. > > > > Thank you, Attached the patch please review. Also created the minimal > > repro script with network disconnects. > > > > Further investigation on this issue indicates that the issue is not > > specific to the 6.6 Kernel; instead, > > the issue can happen in kernels <6.10 including 6.1, 6.6 and 5.15 and beyond. > > > > On older kernels ~5.15 with deferred closes may reduce exposure > > because deferred handles might keep help pages > > in memory and which may help writes to avoid sync_write path. > > But commit 262b73ef442e (smb3 client: fix open hardlink on deferred > > close file error, backported till 6.1) appears to > > increase the likelihood of taking the sync_write path because we close > > deferred handles aggressively in some cases, > > which makes the existing encryption write corruption bug easier to > > trigger. The commit does not introduce the bug; > > It increases trigger frequency for writes taking sync_write path. > > > > I will send out this patch to the stable mailing list separately. > > Looks good to me. My RB is already added > > -- > Regards, > Shyam ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-03-09 10:39 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-02-18 18:00 [BUG] [~6.6 Kernel] Corruption when retrying encrypted sync writes Bharath SM 2026-02-19 11:05 ` David Howells 2026-02-19 11:14 ` Bharath SM 2026-02-27 10:25 ` Shyam Prasad N 2026-03-03 18:03 ` Bharath SM 2026-03-04 8:09 ` Shyam Prasad N 2026-03-09 10:39 ` Bharath SM
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox