History

netfs: fix reference leak

Commit 20d72b00ca81 ("netfs: Fix the request's work item to not require a ref") modified netfs_alloc_request() to initialize the reference counter to 2 instead of 1. The rationale was that the requet's "work" would release the second reference after completion (via netfs_{read,write}_collection_worker()). That works most of the time if all goes well. However, it leaks this additional reference if the request is released before the I/O operation has been submitted: the error code path only decrements the reference counter once and the work item will never be queued because there will never be a completion. This has caused outages of our whole server cluster today because tasks were blocked in netfs_wait_for_outstanding_io(), leading to deadlocks in Ceph (another bug that I will address soon in another patch). This was caused by a netfs_pgpriv2_begin_copy_to_cache() call which failed in fscache_begin_write_operation(). The leaked netfs_io_request was never completed, leaving `netfs_inode.io_count` with a positive value forever. All of this is super-fragile code. Finding out which code paths will lead to an eventual completion and which do not is hard to see: - Some functions like netfs_create_write_req() allocate a request, but will never submit any I/O. - netfs_unbuffered_read_iter_locked() calls netfs_unbuffered_read() and then netfs_put_request(); however, netfs_unbuffered_read() can also fail early before submitting the I/O request, therefore another netfs_put_request() call must be added there. A rule of thumb is that functions that return a `netfs_io_request` do not submit I/O, and all of their callers must be checked. For my taste, the whole netfs code needs an overhaul to make reference counting easier to understand and less fragile & obscure. But to fix this bug here and now and produce a patch that is adequate for a stable backport, I tried a minimal approach that quickly frees the request object upon early failure. I decided against adding a second netfs_put_request() each time because that would cause code duplication which obscures the code further. Instead, I added the function netfs_put_failed_request() which frees such a failed request synchronously under the assumption that the reference count is exactly 2 (as initially set by netfs_alloc_request() and never touched), verified by a WARN_ON_ONCE(). It then deinitializes the request object (without going through the "cleanup_work" indirection) and frees the allocation (with RCU protection to protect against concurrent access by netfs_requests_seq_start()). All code paths that fail early have been changed to call netfs_put_failed_request() instead of netfs_put_request(). Additionally, I have added a netfs_put_request() call to netfs_unbuffered_read() as explained above because the netfs_put_failed_request() approach does not work there.

Max Kellermann committed 1 year ago

Commit 4d428dc1 parent 9158c6b8 files · +47 −14Browse files

Unified · Split

8 files
  1. fs/netfs/buffered_read.c +5 −5
  2. fs/netfs/direct_read.c +6 −1
  3. fs/netfs/direct_write.c +5 −1
  4. fs/netfs/internal.h +1 −0
  5. fs/netfs/objects.c +27 −3
  6. fs/netfs/read_pgpriv2.c +1 −1
  7. fs/netfs/read_single.c +1 −1
  8. fs/netfs/write_issue.c +1 −2
fs/netfs/buffered_read.c+5−5
@@ -369,7 +369,7 @@ void netfs_readahead(struct readahead_control *ractl) · 368 unmodified lines
369 return netfs_put_request(rreq, netfs_rreq_trace_put_return);369 return netfs_put_request(rreq, netfs_rreq_trace_put_return);
370 370
371cleanup_free:371cleanup_free:
372 return netfs_put_request(rreq, netfs_rreq_trace_put_failed);372 return netfs_put_failed_request(rreq);
373}373}
374EXPORT_SYMBOL(netfs_readahead);374EXPORT_SYMBOL(netfs_readahead);
375 375
@@ -472,7 +472,7 @@ static int netfs_read_gaps(struct file *file, struct folio *folio) · 96 unmodified lines
472 return ret < 0 ? ret : 0;472 return ret < 0 ? ret : 0;
473 473
474discard:474discard:
475 netfs_put_request(rreq, netfs_rreq_trace_put_discard);475 netfs_put_failed_request(rreq);
476alloc_error:476alloc_error:
477 folio_unlock(folio);477 folio_unlock(folio);
478 return ret;478 return ret;
@@ -532,7 +532,7 @@ int netfs_read_folio(struct file *file, struct folio *folio) · 53 unmodified lines
532 return ret < 0 ? ret : 0;532 return ret < 0 ? ret : 0;
533 533
534discard:534discard:
535 netfs_put_request(rreq, netfs_rreq_trace_put_discard);535 netfs_put_failed_request(rreq);
536alloc_error:536alloc_error:
537 folio_unlock(folio);537 folio_unlock(folio);
538 return ret;538 return ret;
@@ -699,7 +699,7 @@ have_folio_no_wait: · 160 unmodified lines
699 return 0;699 return 0;
700 700
701error_put:701error_put:
702 netfs_put_request(rreq, netfs_rreq_trace_put_failed);702 netfs_put_failed_request(rreq);
703error:703error:
704 if (folio) {704 if (folio) {
705 folio_unlock(folio);705 folio_unlock(folio);
@@ -754,7 +754,7 @@ int netfs_prefetch_for_write(struct file *file, struct folio *folio, · 48 unmodified lines
754 return ret < 0 ? ret : 0;754 return ret < 0 ? ret : 0;
755 755
756error_put:756error_put:
757 netfs_put_request(rreq, netfs_rreq_trace_put_discard);757 netfs_put_failed_request(rreq);
758error:758error:
759 _leave(" = %d", ret);759 _leave(" = %d", ret);
760 return ret;760 return ret;
fs/netfs/direct_read.c+6−1
@@ -131,6 +131,7 @@ static ssize_t netfs_unbuffered_read(struct netfs_io_request *rreq, bool sync) · 130 unmodified lines
131 131
132 if (rreq->len == 0) {132 if (rreq->len == 0) {
133 pr_err("Zero-sized read [R=%x]\n", rreq->debug_id);133 pr_err("Zero-sized read [R=%x]\n", rreq->debug_id);
134 netfs_put_request(rreq, netfs_rreq_trace_put_discard);
134 return -EIO;135 return -EIO;
135 }136 }
136 137
@@ -205,7 +206,7 @@ ssize_t netfs_unbuffered_read_iter_locked(struct kiocb *iocb, struct iov_iter *i · 68 unmodified lines
205 if (user_backed_iter(iter)) {206 if (user_backed_iter(iter)) {
206 ret = netfs_extract_user_iter(iter, rreq->len, &rreq->buffer.iter, 0);207 ret = netfs_extract_user_iter(iter, rreq->len, &rreq->buffer.iter, 0);
207 if (ret < 0)208 if (ret < 0)
208 goto out;209 goto error_put;
209 rreq->direct_bv = (struct bio_vec *)rreq->buffer.iter.bvec;210 rreq->direct_bv = (struct bio_vec *)rreq->buffer.iter.bvec;
210 rreq->direct_bv_count = ret;211 rreq->direct_bv_count = ret;
211 rreq->direct_bv_unpin = iov_iter_extract_will_pin(iter);212 rreq->direct_bv_unpin = iov_iter_extract_will_pin(iter);
@@ -238,6 +239,10 @@ out: · 26 unmodified lines
238 if (ret > 0)239 if (ret > 0)
239 orig_count -= ret;240 orig_count -= ret;
240 return ret;241 return ret;
242
243error_put:
244 netfs_put_failed_request(rreq);
245 return ret;
241}246}
242EXPORT_SYMBOL(netfs_unbuffered_read_iter_locked);247EXPORT_SYMBOL(netfs_unbuffered_read_iter_locked);
243 248
fs/netfs/direct_write.c+5−1
@@ -57,7 +57,7 @@ ssize_t netfs_unbuffered_write_iter_locked(struct kiocb *iocb, struct iov_iter * · 56 unmodified lines
57 n = netfs_extract_user_iter(iter, len, &wreq->buffer.iter, 0);57 n = netfs_extract_user_iter(iter, len, &wreq->buffer.iter, 0);
58 if (n < 0) {58 if (n < 0) {
59 ret = n;59 ret = n;
60 goto out;60 goto error_put;
61 }61 }
62 wreq->direct_bv = (struct bio_vec *)wreq->buffer.iter.bvec;62 wreq->direct_bv = (struct bio_vec *)wreq->buffer.iter.bvec;
63 wreq->direct_bv_count = n;63 wreq->direct_bv_count = n;
@@ -101,6 +101,10 @@ ssize_t netfs_unbuffered_write_iter_locked(struct kiocb *iocb, struct iov_iter * · 37 unmodified lines
101out:101out:
102 netfs_put_request(wreq, netfs_rreq_trace_put_return);102 netfs_put_request(wreq, netfs_rreq_trace_put_return);
103 return ret;103 return ret;
104
105error_put:
106 netfs_put_failed_request(wreq);
107 return ret;
104}108}
105EXPORT_SYMBOL(netfs_unbuffered_write_iter_locked);109EXPORT_SYMBOL(netfs_unbuffered_write_iter_locked);
106 110
fs/netfs/internal.h+1−0
@@ -87,6 +87,7 @@ struct netfs_io_request *netfs_alloc_request(struct address_space *mapping, · 86 unmodified lines
87void netfs_get_request(struct netfs_io_request *rreq, enum netfs_rreq_ref_trace what);87void netfs_get_request(struct netfs_io_request *rreq, enum netfs_rreq_ref_trace what);
88void netfs_clear_subrequests(struct netfs_io_request *rreq);88void netfs_clear_subrequests(struct netfs_io_request *rreq);
89void netfs_put_request(struct netfs_io_request *rreq, enum netfs_rreq_ref_trace what);89void netfs_put_request(struct netfs_io_request *rreq, enum netfs_rreq_ref_trace what);
90void netfs_put_failed_request(struct netfs_io_request *rreq);
90struct netfs_io_subrequest *netfs_alloc_subrequest(struct netfs_io_request *rreq);91struct netfs_io_subrequest *netfs_alloc_subrequest(struct netfs_io_request *rreq);
91 92
92static inline void netfs_see_request(struct netfs_io_request *rreq,93static inline void netfs_see_request(struct netfs_io_request *rreq,
fs/netfs/objects.c+27−3
@@ -116,10 +116,8 @@ static void netfs_free_request_rcu(struct rcu_head *rcu) · 115 unmodified lines
116 netfs_stat_d(&netfs_n_rh_rreq);116 netfs_stat_d(&netfs_n_rh_rreq);
117}117}
118 118
119static void netfs_free_request(struct work_struct *work)119static void netfs_deinit_request(struct netfs_io_request *rreq)
120{120{
121 struct netfs_io_request *rreq =
122 container_of(work, struct netfs_io_request, cleanup_work);
123 struct netfs_inode *ictx = netfs_inode(rreq->inode);121 struct netfs_inode *ictx = netfs_inode(rreq->inode);
124 unsigned int i;122 unsigned int i;
125 123
@@ -149,6 +147,14 @@ static void netfs_free_request(struct work_struct *work) · 23 unmodified lines
149 147
150 if (atomic_dec_and_test(&ictx->io_count))148 if (atomic_dec_and_test(&ictx->io_count))
151 wake_up_var(&ictx->io_count);149 wake_up_var(&ictx->io_count);
150}
151
152static void netfs_free_request(struct work_struct *work)
153{
154 struct netfs_io_request *rreq =
155 container_of(work, struct netfs_io_request, cleanup_work);
156
157 netfs_deinit_request(rreq);
152 call_rcu(&rreq->rcu, netfs_free_request_rcu);158 call_rcu(&rreq->rcu, netfs_free_request_rcu);
153}159}
154 160
@@ -167,6 +173,24 @@ void netfs_put_request(struct netfs_io_request *rreq, enum netfs_rreq_ref_trace · 12 unmodified lines
167 }173 }
168}174}
169 175
176/*
177 * Free a request (synchronously) that was just allocated but has
178 * failed before it could be submitted.
179 */
180void netfs_put_failed_request(struct netfs_io_request *rreq)
181{
182 int r = refcount_read(&rreq->ref);
183
184 /* new requests have two references (see
185 * netfs_alloc_request(), and this function is only allowed on
186 * new request objects
187 */
188 WARN_ON_ONCE(r != 2);
189
190 trace_netfs_rreq_ref(rreq->debug_id, r, netfs_rreq_trace_put_failed);
191 netfs_free_request(&rreq->cleanup_work);
192}
193
170/*194/*
171 * Allocate and partially initialise an I/O request structure.195 * Allocate and partially initialise an I/O request structure.
172 */196 */
fs/netfs/read_pgpriv2.c+1−1
@@ -118,7 +118,7 @@ static struct netfs_io_request *netfs_pgpriv2_begin_copy_to_cache( · 117 unmodified lines
118 return creq;118 return creq;
119 119
120cancel_put:120cancel_put:
121 netfs_put_request(creq, netfs_rreq_trace_put_return);121 netfs_put_failed_request(creq);
122cancel:122cancel:
123 rreq->copy_to_cache = ERR_PTR(-ENOBUFS);123 rreq->copy_to_cache = ERR_PTR(-ENOBUFS);
124 clear_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &rreq->flags);124 clear_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &rreq->flags);
fs/netfs/read_single.c+1−1
@@ -189,7 +189,7 @@ ssize_t netfs_read_single(struct inode *inode, struct file *file, struct iov_ite · 188 unmodified lines
189 return ret;189 return ret;
190 190
191cleanup_free:191cleanup_free:
192 netfs_put_request(rreq, netfs_rreq_trace_put_failed);192 netfs_put_failed_request(rreq);
193 return ret;193 return ret;
194}194}
195EXPORT_SYMBOL(netfs_read_single);195EXPORT_SYMBOL(netfs_read_single);
fs/netfs/write_issue.c+1−2
@@ -133,8 +133,7 @@ struct netfs_io_request *netfs_create_write_req(struct address_space *mapping, · 132 unmodified lines
133 133
134 return wreq;134 return wreq;
135nomem:135nomem:
136 wreq->error = -ENOMEM;136 netfs_put_failed_request(wreq);
137 netfs_put_request(wreq, netfs_rreq_trace_put_failed);
138 return ERR_PTR(-ENOMEM);137 return ERR_PTR(-ENOMEM);
139}138}
140 139