Linux 9p file system development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "David Howells" <dhowells@redhat.com>
Cc: v9fs@lists.linux.dev, "Chuck Lever" <cel@kernel.org>,
	"Jeff Layton" <jlayton@kernel.org>,
	"Anna Schumaker" <anna@kernel.org>
Subject: Re: [PATCH v16 12/15] netfs: Switch folioq to bvecq
Date: Sat, 07 Nov 2026 19:25:40 +0000	[thread overview]
Message-ID: <sashiko-outbox-203207@kernel.org> (raw)
In-Reply-To: <20261107160823.1465553-13-dhowells@redhat.com>

Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [Critical] netfs: use-after-free on bvecq boundary in
  netfs_read_to_pagecache()
- [High] netfs: use-after-free in netfs_reset_iter() on subrequest retry
--

commit 5744e4078be45f40cce041679e9567cd02a0f062
Author: David Howells <dhowells@redhat.com>

netfs: Switch folioq to bvecq

This commit switches netfslib's rolling_buffer and buffered read/writeback
paths from folio_queue to bvecq.

> diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c
> index 69e68ef0652a7..61b44af6774b7 100644
> --- a/fs/netfs/buffered_read.c
> +++ b/fs/netfs/buffered_read.c
> @@ -215,7 +215,7 @@ static void netfs_issue_read(struct netfs_io_request *rreq,
>   * otherwise we set the deprecated PG_private_2.
>   */
>  static void netfs_mark_copy_to_cache(struct netfs_io_request *rreq,
> -				     struct folio_queue **fq,
> +				     struct bvecq **bq,
>  				     unsigned int *offset,
>  				     int *slot,
>  				     size_t len,
> @@ -225,21 +225,21 @@ static void netfs_mark_copy_to_cache(struct netfs_io_request *rreq,
>  		struct folio *folio;
>  		size_t fsize, overlap;
>  
> -		if (!*fq)
> +		if (!*bq)
>  			break;
> -		if (*slot >= folioq_count(*fq)) {
> -			*fq = (*fq)->next;
> +		if (!bvecq_acquire_slot(*bq, *slot)) {
> +			*bq = bvecq_next(*bq);
>  			*slot = 0;
>  			*offset = 0;
>  			continue;
>  		}
>  
>  		/* Determine how much the subreq overlaps the folio, if at all. */
> -		fsize = folioq_folio_size(*fq, *slot);
> +		fsize = (*bq)->bv[*slot].bv_len;
>  		overlap = min(len, fsize - *offset);
>  
>  		if (overlap > 0 && copy) {
> -			folio = folioq_folio(*fq, *slot);
> +			folio = bvec_folio(&(*bq)->bv[*slot]);
>  			if (netfs_using_pgpriv2(rreq)) {
>  				if (!folio_test_private_2(folio))
>  					folio_start_private_2(folio);
> @@ -275,7 +275,7 @@ static void netfs_read_to_pagecache(struct netfs_io_request *rreq)
>  		.cached_to[1]	= ULLONG_MAX,
>  	};
>  	struct fscache_occupancy *occ = &_occ;
> -	struct folio_queue *fq = rreq->buffer.tail;
> +	struct bvecq *bq = rreq->buffer.tail;
>  	unsigned int offset = 0;
>  	ssize_t size = rreq->len;
>  	uoff_t start = rreq->start;
> @@ -408,10 +408,10 @@ static void netfs_read_to_pagecache(struct netfs_io_request *rreq)
>  		if (size <= 0)
>  			netfs_all_subreqs_queued(rreq);
>  
> -		if (fq) {
> +		if (bq) {
>  			/* See if the cache indicated this should be cached. */
>  			copy = test_bit(NETFS_SREQ_COPY_TO_CACHE, &subreq->flags);
> -			netfs_mark_copy_to_cache(rreq, &fq, &slot, &offset, slice, copy);
> +			netfs_mark_copy_to_cache(rreq, &bq, &slot, &offset, slice, copy);
>  		}
>  
>  		trace_netfs_sreq(subreq, netfs_sreq_trace_submit);

[Severity: Critical]
Can a subrequest ending at a bvecq boundary leave rreq->buffer.iter and bq
pointing at a spent bvecq that gets freed before the next subrequest steps
to bq->next?

In netfs_readahead(), rolling_buffer_bulk_load_from_ra() builds a multi-bvecq
chain (bq0 -> bq1 -> ...) upfront with refcount 1 on each segment.  Unlike
iov_iter_folioq_advance(), which stepped to folioq->next as soon as slot hit
folioq_nr_slots(), iov_iter_bvecq_advance() only steps to bq->next at the top
of its do-while (by) loop:

lib/iov_iter.c:iov_iter_bvecq_advance() {
	...
	do {
		if (slot >= bq->nr_slots) {
			if (!bq->next)
				break;
			bq = bq->next;
			slot = 0;
			continue;
		}
		...
		by -= len;
		slot++;
	} while (by);
}

netfs_mark_copy_to_cache() does the same in its while (len > 0) loop.  When a
subrequest ends at the end of bq0, both rreq->buffer.iter.bvecq and bq are
left pointing at bq0 with slot == bq0->nr_slots, and the next subrequest's
subreq->io_iter copies rreq->buffer.iter before advancing:

CPU0 (issuer)                     CPU1 (collector)
netfs_read_to_pagecache()
  subreq1 covers all of bq0
  netfs_prepare_read_iterator(subreq1)
    rolling_buffer_advance()
      /* iter.bvecq == bq0,
         bvecq_slot == bq0->nr_slots */
  netfs_mark_copy_to_cache()
    /* bq == bq0,
       slot == bq0->nr_slots */
  netfs_issue_read(subreq1)
  netfs_maybe_bulk_drop_ra_refs()
                                  netfs_read_unlock_folios()
                                    rolling_buffer_delete_spent()
                                      bq0->next = NULL;
                                      bvecq_put(bq0);  <-- frees bq0
  subreq2 prepared for bq1:
  netfs_prepare_read_iterator(subreq2)
    netfs_limit_bvecq(&rreq->buffer.iter)
      bvecq_next(bq0)             <-- use-after-free, sees NULL
  netfs_mark_copy_to_cache(&bq)
    bvecq_next(bq0)               <-- use-after-free, sees NULL

The same happens on a single CPU when subreq1 is NETFS_FILL_WITH_ZEROES and
completes synchronously inside netfs_issue_read().  Once bq0 is freed and its
next pointer is cleared, subreq2's iterator is truncated to 0 bytes and the
remaining folios in bq1 are never populated or unlocked.

> diff --git a/fs/netfs/rolling_buffer.c b/fs/netfs/rolling_buffer.c
> index 4347c99452d83..76cede046dcb7 100644
> --- a/fs/netfs/rolling_buffer.c
> +++ b/fs/netfs/rolling_buffer.c

[ ... ]

> @@ -205,44 +205,23 @@ ssize_t rolling_buffer_append(struct rolling_buffer *roll, struct folio *folio,
>   * don't return the last buffer to keep the pointers independent, but return
>   * NULL instead.
>   */
> -struct folio_queue *rolling_buffer_delete_spent(struct rolling_buffer *roll)
> +struct bvecq *rolling_buffer_delete_spent(struct rolling_buffer *roll)
>  {
> -	struct folio_queue *spent = roll->tail, *next = READ_ONCE(spent->next);
> +	struct bvecq *spent = roll->tail, *next = bvecq_next(spent);
>  
>  	if (!next)
>  		return NULL;
>  	next->prev = NULL;
> -	netfs_folioq_free(spent, netfs_trace_folioq_delete);
>  	roll->tail = next;
> +	spent->next = NULL;
> +	bvecq_put(spent);
>  	return next;
>  }

[Severity: High]
Does freeing spent bvecq segments during collection cause a use-after-free in
netfs_reset_iter() when a subrequest spanning multiple bvecq segments is
retried?

subreq->io_iter is initialized from rreq->buffer.iter (or wreq->buffer.iter)
without taking a reference on its starting bvecq segment (bq0).  If a
subrequest spans bq0 and bq1, transfers enough data to finish bq0, and then
fails with NETFS_SREQ_NEED_RETRY, both netfs_collect_read_results() and
netfs_collect_write_results() unlock the completed folios in bq0 before
entering the retry path:

fs/netfs/write_collect.c:netfs_collect_write_results() {
	...
	if (notes & NEED_UNLOCK) {
		if (wreq->cleaned_to < wreq->collected_to)
			netfs_writeback_unlock_folios(wreq, &notes);
	} else {
		wreq->cleaned_to = wreq->collected_to;
	}

	// TODO: Discard encryption buffers

	if (notes & NEED_RETRY)
		goto need_retry;
	...
}

netfs_writeback_unlock_folios() (and netfs_read_unlock_folios() on the read
side) calls rolling_buffer_delete_spent(), which clears bq0->next = NULL and
frees bq0.  When netfs_retry_writes() or netfs_retry_reads() then calls
netfs_reset_iter(subreq) -> iov_iter_advance(&subreq->io_iter, ...),
iov_iter_bvecq_advance() dereferences the freed bq0 from subreq->io_iter.bvecq
and cannot advance to bq1 because bq0->next was cleared.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261107160823.1465553-1-dhowells@redhat.com?part=12

  reply	other threads:[~2026-11-07 19:25 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-07 16:08 [PATCH v16 00/15] netfs, iov_iter: Use a chain of bio_vec arrays instead of folio_queue David Howells
2026-11-07 16:08 ` [PATCH v16 01/15] Add a function to kmap one page of a multipage bio_vec David Howells
2026-11-07 16:15   ` sashiko-bot
2026-11-07 16:08 ` [PATCH v16 02/15] iov_iter: Add a segmented queue of bio_vec[] David Howells
2026-11-07 16:31   ` sashiko-bot
2026-11-07 16:08 ` [PATCH v16 03/15] netfs: Add some tools for managing bvecq chains David Howells
2026-11-07 16:46   ` sashiko-bot
2026-11-07 16:08 ` [PATCH v16 04/15] iov_iter: Add an API document for the bvecq structure David Howells
2026-11-07 16:47   ` sashiko-bot
2026-11-07 16:08 ` [PATCH v16 05/15] iov_iter: Add a bvecq kunit test David Howells
2026-11-07 17:01   ` sashiko-bot
2026-11-07 16:08 ` [PATCH v16 06/15] afs: Fix an inconsistency in the symlink code David Howells
2026-11-07 17:09   ` sashiko-bot
2026-11-07 16:08 ` [PATCH v16 07/15] netfs: Explicitly pass tail padding to netfs_writeback_single() David Howells
2026-11-07 17:46   ` sashiko-bot
2026-11-07 16:08 ` [PATCH v16 08/15] netfs: Change netfs_writeback_single() to not look inside the iterator David Howells
2026-11-07 18:09   ` sashiko-bot
2026-11-07 16:08 ` [PATCH v16 09/15] afs: Use a bvecq to hold dir content rather than folioq David Howells
2026-11-07 18:34   ` sashiko-bot
2026-11-07 16:08 ` [PATCH v16 10/15] cifs: Use a bvecq for buffering instead of a folioq David Howells
2026-11-07 18:51   ` sashiko-bot
2026-11-07 16:08 ` [PATCH v16 11/15] smbdirect: Support ITER_BVECQ in smbdirect_map_sges_from_iter() David Howells
2026-11-07 18:57   ` sashiko-bot
2026-11-07 16:08 ` [PATCH v16 12/15] netfs: Switch folioq to bvecq David Howells
2026-11-07 19:25   ` sashiko-bot [this message]
2026-11-07 16:08 ` [PATCH v16 13/15] smbdirect: Remove support for ITER_FOLIOQ from smbdirect_map_sges_from_iter() David Howells
2026-11-07 19:29   ` sashiko-bot
2026-11-07 16:08 ` [PATCH v16 14/15] iov_iter: Remove ITER_FOLIOQ David Howells
2026-11-07 19:31   ` sashiko-bot
2026-11-07 16:08 ` [PATCH v16 15/15] netfs: Remove folio_queue David Howells
2026-11-07 19:35   ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-203207@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=anna@kernel.org \
    --cc=cel@kernel.org \
    --cc=dhowells@redhat.com \
    --cc=jlayton@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=v9fs@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox