mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: "Anna Schumaker" <anna@kernel.org>
To: "Paula Moutafian" <paula@bynar.io>,
	"Trond Myklebust" <trondmy@kernel.org>
Cc: "Chuck Lever" <cel@kernel.org>,
	"Jeff Layton" <jlayton@kernel.org>, NeilBrown <neil@brown.name>,
	linux-nfs@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH] SUNRPC: fix out-of-bounds write in xdr_inline_pages()
Date: Fri, 21 Aug 2026 08:55:21 -0400	[thread overview]
Message-ID: <b8744803-5b58-4c64-82da-73a802dd4b0f@app.fastmail.com> (raw)
In-Reply-To: <20260820143049.25712-1-paula@bynar.io>

Hi Paula,

On Thu, Aug 20, 2026, at 10:30 AM, Paula Moutafian wrote:
> xdr_inline_pages() re-carves an existing receive buffer into a head, a
> page list and a tail at a caller-supplied offset, computing the tail
> length as buflen - offset.  It does not check that the offset lies
> within the buffer being carved, and iov_len is unsigned, so an offset
> past the end wraps the tail length instead of failing.
>
> The offset reaches it from wire data.  rpc_prepare_reply_pages() derives
> it from the authentication flavour's au_ralign, which is read on every
> encode, while the receive buffer's size is fixed once in call_allocate()
> from au_rslack.  unx_validate() raises au_verfsize, au_rslack and
> au_ralign from the reply verifier's length, and runs before
> rpc_decode_header() reads the accept_stat, so a GARBAGE_ARGS reply
> poisons the alignment and only then selects call_encode().
> call_allocate() short-circuits on the existing buffer, so rq_rcvsize is
> not recomputed and the retry carves a new offset against the old size.
> auth_unix keeps these fields in a file-scope static rpc_auth shared by
> every AUTH_SYS client.
>
> For an NFSv3 READ the design slack is exactly one XDR word, so the
> underflow needs only au_ralign at encode to exceed au_rslack at
> allocation by two -- a five-byte verifier body.  Observed with eight:
> rq_rcvsize is 132 with a 128-byte head and a 4-byte tail, the verifier
> raises au_ralign from 2 to 4, and the retry carves at (26 + 4 + 4) << 2
> = 136 against an unchanged 132-byte buffer.  The in-tree
> sunrpc:rpc_xdr_reply_pages tracepoint shows the transition on one task:
>
>   rpc_xdr_reply_pages: head=[..a18,128] tail=[..a98,4]
>   rpc_xdr_reply_pages: head=[..a18,136] tail=[..aa0,4294967292]
>
> (one task, before and after the re-encode; addresses abbreviated)
>
> xs_read_xdr_buf() then fills the tail against that length, and the TCP
> record length is not capped against rq_rcvsize, so the reply to the
> retried call writes peer-controlled bytes past the object:
>
>   BUG: KASAN: slab-out-of-bounds in _copy_to_iter+0x7c8/0x1538
>   Write of size 1664 at addr ffff0000c7640aa0 by task kworker/u8:0/12
>   Workqueue: xprtiod xs_stream_data_receive_workfn
>    _copy_to_iter+0x7c8/0x1538
>    __skb_datagram_iter+0x33c/0x560
>    skb_copy_datagram_iter+0x3c/0x454
>    tcp_recvmsg_locked+0x110c/0x2308
>    xs_sock_recvmsg.constprop.0+0x34/0xe4
>    xs_read_stream_request.constprop.0+0x410/0x1140
>    xs_read_stream.constprop.0+0x680/0xe9c
>    xs_stream_data_receive_workfn+0xcc/0x420
>   Allocated by task 12:
>    rpc_malloc+0x174/0x2d0
>    call_allocate+0x25c/0x944
>   The buggy address belongs to the object at ffff0000c7640880
>    which belongs to the cache rpc_buffers of size 2048
>   The buggy address is located 544 bytes inside of
>    allocated 2048-byte region [ffff0000c7640880, ffff0000c7641080)
>
> That is 160 bytes past the object, with both the length and every byte
> chosen by the peer.  Under KASAN the interposed __asan_memcpy() returns
> without calling __memcpy once the range check fails, so the report
> establishes that the write is reachable and out of bounds; it does not
> establish that the copy executed.  The enlarged head stays inside the
> same slab object and is not itself a violation.
>
> Reproduced twice on an unmodified v7.2-rc7 arm64 KASAN kernel with
> CONFIG_NFS_V3=y and no other configuration change; the geometry above
> came from tracepoints that already ship in the kernel, not from added
> instrumentation.  The trigger is an ordinary read(2) on an already
> mounted NFSv3 AUTH_SYS share over TCP and needs no client privilege;
> every byte the attacker supplies is a legal server reply.  Under
> RPCSEC_GSS the verifier is authenticated and cannot be forged.  UDP is
> bounded by the datagram length in xs_udp_data_read_skb() and is not
> affected.  NFSv4 does not take the retry path, since
> RPC_TASK_NO_RETRANS_TIMEOUT suppresses the re-encode.
>
> Commit 53bc19f17f21 ("SUNRPC: receive buffer size estimation values
> almost never change") placed the equivalent slack update behind
> RPCAUTH_AUTH_UPDATE_SLACK in gss_update_rslack(), but auth_unix was
> never converted and still assigns unconditionally.  Gating it the same
> way narrows the window without closing it, because the first update can
> still land between the allocation and the re-encode of an in-flight
> request, and it would leave the other caller of xdr_inline_pages()
> unguarded.
>
> Clamp the offset to the length of the buffer being carved so the tail
> length cannot underflow.
>
> Assisted-by: Bynario AI
> Signed-off-by: Paula Moutafian <paula@bynar.io>
> ---
> Note: the defect is reproduced 2/2 on an unmodified v7.2-rc7 arm64 KASAN
> kernel; the patched kernel has not been built or booted.  A reproducer
> exists and can be shared privately on request.

Question: Am I understanding you correctly that you have a reproducer, but
haven't tested or even compiled the code to verify that it works?

Thanks,
Anna

>
> No Fixes: tag: the unbounded carve is original to xdr_inline_pages()
> (unchanged since the initial git import) and unx_validate() has written
> the shared rpc_auth's slack from the reply verifier since at least
> v2.6.32, so there is no commit that introduced this.
>
>  net/sunrpc/xdr.c | 3 +++
>  1 file changed, 3 insertions(+)
>
> diff --git a/net/sunrpc/xdr.c b/net/sunrpc/xdr.c
> index fa6a30b..f77dbdb 100644
> --- a/net/sunrpc/xdr.c
> +++ b/net/sunrpc/xdr.c
> @@ -409,6 +409,9 @@ xdr_inline_pages(struct xdr_buf *xdr, unsigned int offset,
>  	char *buf = (char *)head->iov_base;
>  	unsigned int buflen = head->iov_len;
> 
> +	if (offset > buflen)
> +		offset = buflen;
> +
>  	head->iov_len  = offset;
> 
>  	xdr->pages = pages;
>
> base-commit: a4ff2be345d0abc943da8dd8da98151843b750dc
> -- 
> 2.50.1 (Apple Git-155)

  reply	other threads:[~2026-08-21 12:56 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-20 14:30 Paula Moutafian
2026-08-21 12:55 ` Anna Schumaker [this message]
     [not found]   ` <CANegxYFKBfCKjEEn73n1-C1Ev9MY9RE-zQ4LW2C-BCoRpjAqhw@mail.gmail.com>
2026-08-21 15:36     ` Paula Moutafian

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=b8744803-5b58-4c64-82da-73a802dd4b0f@app.fastmail.com \
    --to=anna@kernel.org \
    --cc=cel@kernel.org \
    --cc=jlayton@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-nfs@vger.kernel.org \
    --cc=neil@brown.name \
    --cc=paula@bynar.io \
    --cc=trondmy@kernel.org \
    /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

all inboxes | Powered by JetHome®