Skip to content

Commit 28770da

Browse files
committed
netfs: Work around recursion by abandoning retry if nothing read
JIRA: https://issues.redhat.com/browse/RHEL-96872 Conflicts: - Context difference due to backported upstream commit 92941c7 ("smb: fix bytes written value in /proc/fs/cifs/Stats"). commit 4acb665 Author: David Howells <[email protected]> Date: Fri Dec 13 13:50:08 2024 +0000 netfs: Work around recursion by abandoning retry if nothing read syzkaller reported recursion with a loop of three calls (netfs_rreq_assess, netfs_retry_reads and netfs_rreq_terminated) hitting the limit of the stack during an unbuffered or direct I/O read. There are a number of issues: (1) There is no limit on the number of retries. (2) A subrequest is supposed to be abandoned if it does not transfer anything (NETFS_SREQ_NO_PROGRESS), but that isn't checked under all circumstances. (3) The actual root cause, which is this: if (atomic_dec_and_test(&rreq->nr_outstanding)) netfs_rreq_terminated(rreq, ...); When we do a retry, we bump the rreq->nr_outstanding counter to prevent the final cleanup phase running before we've finished dispatching the retries. The problem is if we hit 0, we have to do the cleanup phase - but we're in the cleanup phase and end up repeating the retry cycle, hence the recursion. Work around the problem by limiting the number of retries. This is based on Lizhi Xu's patch[1], and makes the following changes: (1) Replace NETFS_SREQ_NO_PROGRESS with NETFS_SREQ_MADE_PROGRESS and make the filesystem set it if it managed to read or write at least one byte of data. Clear this bit before issuing a subrequest. (2) Add a ->retry_count member to the subrequest and increment it any time we do a retry. (3) Remove the NETFS_SREQ_RETRYING flag as it is superfluous with ->retry_count. If the latter is non-zero, we're doing a retry. (4) Abandon a subrequest if retry_count is non-zero and we made no progress. (5) Use ->retry_count in both the write-side and the read-size. [?] Question: Should I set a hard limit on retry_count in both read and write? Say it hits 50, we always abandon it. The problem is that these changes only mitigate the issue. As long as it made at least one byte of progress, the recursion is still an issue. This patch mitigates the problem, but does not fix the underlying cause. I have patches that will do that, but it's an intrusive fix that's currently pending for the next merge window. The oops generated by KASAN looks something like: BUG: TASK stack guard page was hit at ffffc9000482ff48 (stack is ffffc90004830000..ffffc90004838000) Oops: stack guard page: 0000 [#1] PREEMPT SMP KASAN NOPTI ... RIP: 0010:mark_lock+0x25/0xc60 kernel/locking/lockdep.c:4686 ... mark_usage kernel/locking/lockdep.c:4646 [inline] __lock_acquire+0x906/0x3ce0 kernel/locking/lockdep.c:5156 lock_acquire.part.0+0x11b/0x380 kernel/locking/lockdep.c:5825 local_lock_acquire include/linux/local_lock_internal.h:29 [inline] ___slab_alloc+0x123/0x1880 mm/slub.c:3695 __slab_alloc.constprop.0+0x56/0xb0 mm/slub.c:3908 __slab_alloc_node mm/slub.c:3961 [inline] slab_alloc_node mm/slub.c:4122 [inline] kmem_cache_alloc_noprof+0x2a7/0x2f0 mm/slub.c:4141 radix_tree_node_alloc.constprop.0+0x1e8/0x350 lib/radix-tree.c:253 idr_get_free+0x528/0xa40 lib/radix-tree.c:1506 idr_alloc_u32+0x191/0x2f0 lib/idr.c:46 idr_alloc+0xc1/0x130 lib/idr.c:87 p9_tag_alloc+0x394/0x870 net/9p/client.c:321 p9_client_prepare_req+0x19f/0x4d0 net/9p/client.c:644 p9_client_zc_rpc.constprop.0+0x105/0x880 net/9p/client.c:793 p9_client_read_once+0x443/0x820 net/9p/client.c:1570 p9_client_read+0x13f/0x1b0 net/9p/client.c:1534 v9fs_issue_read+0x115/0x310 fs/9p/vfs_addr.c:74 netfs_retry_read_subrequests fs/netfs/read_retry.c:60 [inline] netfs_retry_reads+0x153a/0x1d00 fs/netfs/read_retry.c:232 netfs_rreq_assess+0x5d3/0x870 fs/netfs/read_collect.c:371 netfs_rreq_terminated+0xe5/0x110 fs/netfs/read_collect.c:407 netfs_retry_reads+0x155e/0x1d00 fs/netfs/read_retry.c:235 netfs_rreq_assess+0x5d3/0x870 fs/netfs/read_collect.c:371 netfs_rreq_terminated+0xe5/0x110 fs/netfs/read_collect.c:407 netfs_retry_reads+0x155e/0x1d00 fs/netfs/read_retry.c:235 netfs_rreq_assess+0x5d3/0x870 fs/netfs/read_collect.c:371 ... netfs_rreq_terminated+0xe5/0x110 fs/netfs/read_collect.c:407 netfs_retry_reads+0x155e/0x1d00 fs/netfs/read_retry.c:235 netfs_rreq_assess+0x5d3/0x870 fs/netfs/read_collect.c:371 netfs_rreq_terminated+0xe5/0x110 fs/netfs/read_collect.c:407 netfs_retry_reads+0x155e/0x1d00 fs/netfs/read_retry.c:235 netfs_rreq_assess+0x5d3/0x870 fs/netfs/read_collect.c:371 netfs_rreq_terminated+0xe5/0x110 fs/netfs/read_collect.c:407 netfs_dispatch_unbuffered_reads fs/netfs/direct_read.c:103 [inline] netfs_unbuffered_read fs/netfs/direct_read.c:127 [inline] netfs_unbuffered_read_iter_locked+0x12f6/0x19b0 fs/netfs/direct_read.c:221 netfs_unbuffered_read_iter+0xc5/0x100 fs/netfs/direct_read.c:256 v9fs_file_read_iter+0xbf/0x100 fs/9p/vfs_file.c:361 do_iter_readv_writev+0x614/0x7f0 fs/read_write.c:832 vfs_readv+0x4cf/0x890 fs/read_write.c:1025 do_preadv fs/read_write.c:1142 [inline] __do_sys_preadv fs/read_write.c:1192 [inline] __se_sys_preadv fs/read_write.c:1187 [inline] __x64_sys_preadv+0x22d/0x310 fs/read_write.c:1187 do_syscall_x64 arch/x86/entry/common.c:52 [inline] do_syscall_64+0xcd/0x250 arch/x86/entry/common.c:83 Fixes: ee4cdf7 ("netfs: Speed up buffered reading") Closes: https://syzkaller.appspot.com/bug?extid=1fc6f64c40a9d143cfb6 Signed-off-by: David Howells <[email protected]> Link: https://lore.kernel.org/r/[email protected]/ [1] Link: https://lore.kernel.org/r/[email protected] Tested-by: [email protected] Suggested-by: Lizhi Xu <[email protected]> cc: Dominique Martinet <[email protected]> cc: Jeff Layton <[email protected]> cc: [email protected] cc: [email protected] cc: [email protected] Reported-by: [email protected] Signed-off-by: Christian Brauner <[email protected]> Signed-off-by: Paulo Alcantara <[email protected]>
1 parent c3adc7c commit 28770da

File tree

9 files changed

+44
-23
lines changed

9 files changed

+44
-23
lines changed

fs/9p/vfs_addr.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ static void v9fs_issue_write(struct netfs_io_subrequest *subreq)
5757
int err, len;
5858

5959
len = p9_client_write(fid, subreq->start, &subreq->io_iter, &err);
60+
if (len > 0)
61+
__set_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags);
6062
netfs_write_subrequest_terminated(subreq, len ?: err, false);
6163
}
6264

@@ -80,8 +82,10 @@ static void v9fs_issue_read(struct netfs_io_subrequest *subreq)
8082
if (pos + total >= i_size_read(rreq->inode))
8183
__set_bit(NETFS_SREQ_HIT_EOF, &subreq->flags);
8284

83-
if (!err)
85+
if (!err) {
8486
subreq->transferred += total;
87+
__set_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags);
88+
}
8589

8690
netfs_read_subreq_terminated(subreq, err, false);
8791
}

fs/afs/write.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ static void afs_issue_write_worker(struct work_struct *work)
122122
if (subreq->debug_index == 3)
123123
return netfs_write_subrequest_terminated(subreq, -ENOANO, false);
124124

125-
if (!test_bit(NETFS_SREQ_RETRYING, &subreq->flags)) {
125+
if (!subreq->retry_count) {
126126
set_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags);
127127
return netfs_write_subrequest_terminated(subreq, -EAGAIN, false);
128128
}
@@ -149,6 +149,9 @@ static void afs_issue_write_worker(struct work_struct *work)
149149
afs_wait_for_operation(op);
150150
ret = afs_put_operation(op);
151151
switch (ret) {
152+
case 0:
153+
__set_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags);
154+
break;
152155
case -EACCES:
153156
case -EPERM:
154157
case -ENOKEY:

fs/netfs/read_collect.c

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ void netfs_read_subreq_progress(struct netfs_io_subrequest *subreq,
440440
rreq->origin == NETFS_READPAGE ||
441441
rreq->origin == NETFS_READ_FOR_WRITE)) {
442442
netfs_consume_read_data(subreq, was_async);
443-
__clear_bit(NETFS_SREQ_NO_PROGRESS, &subreq->flags);
443+
__set_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags);
444444
}
445445
}
446446
EXPORT_SYMBOL(netfs_read_subreq_progress);
@@ -499,7 +499,7 @@ void netfs_read_subreq_terminated(struct netfs_io_subrequest *subreq,
499499
rreq->origin == NETFS_READPAGE ||
500500
rreq->origin == NETFS_READ_FOR_WRITE)) {
501501
netfs_consume_read_data(subreq, was_async);
502-
__clear_bit(NETFS_SREQ_NO_PROGRESS, &subreq->flags);
502+
__set_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags);
503503
}
504504
rreq->transferred += subreq->transferred;
505505
}
@@ -513,10 +513,13 @@ void netfs_read_subreq_terminated(struct netfs_io_subrequest *subreq,
513513
} else {
514514
trace_netfs_sreq(subreq, netfs_sreq_trace_short);
515515
if (subreq->transferred > subreq->consumed) {
516-
__set_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags);
517-
__clear_bit(NETFS_SREQ_NO_PROGRESS, &subreq->flags);
518-
set_bit(NETFS_RREQ_NEED_RETRY, &rreq->flags);
519-
} else if (!__test_and_set_bit(NETFS_SREQ_NO_PROGRESS, &subreq->flags)) {
516+
/* If we didn't read new data, abandon retry. */
517+
if (subreq->retry_count &&
518+
test_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags)) {
519+
__set_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags);
520+
set_bit(NETFS_RREQ_NEED_RETRY, &rreq->flags);
521+
}
522+
} else if (test_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags)) {
520523
__set_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags);
521524
set_bit(NETFS_RREQ_NEED_RETRY, &rreq->flags);
522525
} else {

fs/netfs/read_retry.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ static void netfs_retry_read_subrequests(struct netfs_io_request *rreq)
5656
if (test_bit(NETFS_SREQ_FAILED, &subreq->flags))
5757
break;
5858
if (__test_and_clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) {
59+
__clear_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags);
60+
subreq->retry_count++;
5961
netfs_reset_iter(subreq);
6062
netfs_reissue_read(rreq, subreq);
6163
}
@@ -137,7 +139,8 @@ static void netfs_retry_read_subrequests(struct netfs_io_request *rreq)
137139
stream0->sreq_max_len = subreq->len;
138140

139141
__clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags);
140-
__set_bit(NETFS_SREQ_RETRYING, &subreq->flags);
142+
__clear_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags);
143+
subreq->retry_count++;
141144

142145
spin_lock_bh(&rreq->lock);
143146
list_add_tail(&subreq->rreq_link, &rreq->subrequests);
@@ -213,7 +216,6 @@ static void netfs_retry_read_subrequests(struct netfs_io_request *rreq)
213216
subreq->error = -ENOMEM;
214217
__clear_bit(NETFS_SREQ_FAILED, &subreq->flags);
215218
__clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags);
216-
__clear_bit(NETFS_SREQ_RETRYING, &subreq->flags);
217219
}
218220
spin_lock_bh(&rreq->lock);
219221
list_splice_tail_init(&queue, &rreq->subrequests);

fs/netfs/write_collect.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,6 @@ static void netfs_retry_write_stream(struct netfs_io_request *wreq,
179179
struct iov_iter source = subreq->io_iter;
180180

181181
iov_iter_revert(&source, subreq->len - source.count);
182-
__set_bit(NETFS_SREQ_RETRYING, &subreq->flags);
183182
netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit);
184183
netfs_reissue_write(stream, subreq, &source);
185184
}
@@ -234,7 +233,7 @@ static void netfs_retry_write_stream(struct netfs_io_request *wreq,
234233
/* Renegotiate max_len (wsize) */
235234
trace_netfs_sreq(subreq, netfs_sreq_trace_retry);
236235
__clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags);
237-
__set_bit(NETFS_SREQ_RETRYING, &subreq->flags);
236+
subreq->retry_count++;
238237
stream->prepare_write(subreq);
239238

240239
part = min(len, stream->sreq_max_len);
@@ -279,7 +278,7 @@ static void netfs_retry_write_stream(struct netfs_io_request *wreq,
279278
subreq->start = start;
280279
subreq->debug_index = atomic_inc_return(&wreq->subreq_counter);
281280
subreq->stream_nr = to->stream_nr;
282-
__set_bit(NETFS_SREQ_RETRYING, &subreq->flags);
281+
subreq->retry_count = 1;
283282

284283
trace_netfs_sreq_ref(wreq->debug_id, subreq->debug_index,
285284
refcount_read(&subreq->ref),

fs/netfs/write_issue.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,8 @@ void netfs_reissue_write(struct netfs_io_stream *stream,
244244
iov_iter_advance(source, size);
245245
iov_iter_truncate(&subreq->io_iter, size);
246246

247+
subreq->retry_count++;
248+
__clear_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags);
247249
__set_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags);
248250
netfs_do_issue_write(stream, subreq);
249251
}

fs/smb/client/cifssmb.c

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,14 +1336,16 @@ cifs_readv_callback(struct mid_q_entry *mid)
13361336
}
13371337

13381338
if (rdata->result == -ENODATA) {
1339-
__set_bit(NETFS_SREQ_HIT_EOF, &rdata->subreq.flags);
13401339
rdata->result = 0;
1340+
__set_bit(NETFS_SREQ_HIT_EOF, &rdata->subreq.flags);
13411341
} else {
13421342
size_t trans = rdata->subreq.transferred + rdata->got_bytes;
13431343
if (trans < rdata->subreq.len &&
13441344
rdata->subreq.start + trans == ictx->remote_i_size) {
1345-
__set_bit(NETFS_SREQ_HIT_EOF, &rdata->subreq.flags);
13461345
rdata->result = 0;
1346+
__set_bit(NETFS_SREQ_HIT_EOF, &rdata->subreq.flags);
1347+
} else if (rdata->got_bytes > 0) {
1348+
__set_bit(NETFS_SREQ_MADE_PROGRESS, &rdata->subreq.flags);
13471349
}
13481350
}
13491351

@@ -1687,10 +1689,13 @@ cifs_writev_callback(struct mid_q_entry *mid)
16871689
if (written > wdata->subreq.len)
16881690
written &= 0xFFFF;
16891691

1690-
if (written < wdata->subreq.len)
1692+
if (written < wdata->subreq.len) {
16911693
result = -ENOSPC;
1692-
else
1694+
} else {
16931695
result = written;
1696+
if (written > 0)
1697+
__set_bit(NETFS_SREQ_MADE_PROGRESS, &wdata->subreq.flags);
1698+
}
16941699
break;
16951700
case MID_REQUEST_SUBMITTED:
16961701
case MID_RETRY_NEEDED:

fs/smb/client/smb2pdu.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4603,6 +4603,7 @@ smb2_readv_callback(struct mid_q_entry *mid)
46034603
__set_bit(NETFS_SREQ_HIT_EOF, &rdata->subreq.flags);
46044604
rdata->result = 0;
46054605
}
4606+
__set_bit(NETFS_SREQ_MADE_PROGRESS, &rdata->subreq.flags);
46064607
}
46074608
trace_smb3_rw_credits(rreq_debug_id, subreq_debug_index, rdata->credits.value,
46084609
server->credits, server->in_flight,
@@ -4830,10 +4831,12 @@ smb2_writev_callback(struct mid_q_entry *mid)
48304831

48314832
cifs_stats_bytes_written(tcon, written);
48324833

4833-
if (written < wdata->subreq.len)
4834+
if (written < wdata->subreq.len) {
48344835
wdata->result = -ENOSPC;
4835-
else
4836+
} else if (written > 0) {
48364837
wdata->subreq.len = written;
4838+
__set_bit(NETFS_SREQ_MADE_PROGRESS, &wdata->subreq.flags);
4839+
}
48374840
break;
48384841
case MID_REQUEST_SUBMITTED:
48394842
case MID_RETRY_NEEDED:
@@ -5002,7 +5005,7 @@ smb2_async_writev(struct cifs_io_subrequest *wdata)
50025005
}
50035006
#endif
50045007

5005-
if (test_bit(NETFS_SREQ_RETRYING, &wdata->subreq.flags))
5008+
if (wdata->subreq.retry_count > 0)
50065009
smb2_set_replay(server, &rqst);
50075010

50085011
cifs_dbg(FYI, "async write at %llu %u bytes iter=%zx\n",

include/linux/netfs.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ struct netfs_io_subrequest {
185185
short error; /* 0 or error that occurred */
186186
unsigned short debug_index; /* Index in list (for debugging output) */
187187
unsigned int nr_segs; /* Number of segs in io_iter */
188+
u8 retry_count; /* The number of retries (0 on initial pass) */
188189
enum netfs_io_source source; /* Where to read from/write to */
189190
unsigned char stream_nr; /* I/O stream this belongs to */
190191
unsigned char curr_folioq_slot; /* Folio currently being read */
@@ -194,14 +195,13 @@ struct netfs_io_subrequest {
194195
#define NETFS_SREQ_COPY_TO_CACHE 0 /* Set if should copy the data to the cache */
195196
#define NETFS_SREQ_CLEAR_TAIL 1 /* Set if the rest of the read should be cleared */
196197
#define NETFS_SREQ_SEEK_DATA_READ 3 /* Set if ->read() should SEEK_DATA first */
197-
#define NETFS_SREQ_NO_PROGRESS 4 /* Set if we didn't manage to read any data */
198+
#define NETFS_SREQ_MADE_PROGRESS 4 /* Set if we transferred at least some data */
198199
#define NETFS_SREQ_ONDEMAND 5 /* Set if it's from on-demand read mode */
199200
#define NETFS_SREQ_BOUNDARY 6 /* Set if ends on hard boundary (eg. ceph object) */
200201
#define NETFS_SREQ_HIT_EOF 7 /* Set if short due to EOF */
201202
#define NETFS_SREQ_IN_PROGRESS 8 /* Unlocked when the subrequest completes */
202203
#define NETFS_SREQ_NEED_RETRY 9 /* Set if the filesystem requests a retry */
203-
#define NETFS_SREQ_RETRYING 10 /* Set if we're retrying */
204-
#define NETFS_SREQ_FAILED 11 /* Set if the subreq failed unretryably */
204+
#define NETFS_SREQ_FAILED 10 /* Set if the subreq failed unretryably */
205205
};
206206

207207
enum netfs_io_origin {

0 commit comments

Comments
 (0)