| From stable+bounces-227532-greg=kroah.com@vger.kernel.org Fri Mar 20 12:49:59 2026 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Fri, 20 Mar 2026 07:48:45 -0400 |
| Subject: nfsd: fix heap overflow in NFSv4.0 LOCK replay cache |
| To: stable@vger.kernel.org |
| Cc: Jeff Layton <jlayton@kernel.org>, stable@kernel.org, Nicholas Carlini <npc@anthropic.com>, Chuck Lever <chuck.lever@oracle.com>, Sasha Levin <sashal@kernel.org> |
| Message-ID: <20260320114846.3998380-1-sashal@kernel.org> |
| |
| From: Jeff Layton <jlayton@kernel.org> |
| |
| [ Upstream commit 5133b61aaf437e5f25b1b396b14242a6bb0508e2 ] |
| |
| The NFSv4.0 replay cache uses a fixed 112-byte inline buffer |
| (rp_ibuf[NFSD4_REPLAY_ISIZE]) to store encoded operation responses. |
| This size was calculated based on OPEN responses and does not account |
| for LOCK denied responses, which include the conflicting lock owner as |
| a variable-length field up to 1024 bytes (NFS4_OPAQUE_LIMIT). |
| |
| When a LOCK operation is denied due to a conflict with an existing lock |
| that has a large owner, nfsd4_encode_operation() copies the full encoded |
| response into the undersized replay buffer via read_bytes_from_xdr_buf() |
| with no bounds check. This results in a slab-out-of-bounds write of up |
| to 944 bytes past the end of the buffer, corrupting adjacent heap memory. |
| |
| This can be triggered remotely by an unauthenticated attacker with two |
| cooperating NFSv4.0 clients: one sets a lock with a large owner string, |
| then the other requests a conflicting lock to provoke the denial. |
| |
| We could fix this by increasing NFSD4_REPLAY_ISIZE to allow for a full |
| opaque, but that would increase the size of every stateowner, when most |
| lockowners are not that large. |
| |
| Instead, fix this by checking the encoded response length against |
| NFSD4_REPLAY_ISIZE before copying into the replay buffer. If the |
| response is too large, set rp_buflen to 0 to skip caching the replay |
| payload. The status is still cached, and the client already received the |
| correct response on the original request. |
| |
| Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") |
| Cc: stable@kernel.org |
| Reported-by: Nicholas Carlini <npc@anthropic.com> |
| Tested-by: Nicholas Carlini <npc@anthropic.com> |
| Signed-off-by: Jeff Layton <jlayton@kernel.org> |
| Signed-off-by: Chuck Lever <chuck.lever@oracle.com> |
| [ replaced `op_status_offset + XDR_UNIT` with existing `post_err_offset` variable ] |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| fs/nfsd/nfs4xdr.c | 9 +++++++-- |
| fs/nfsd/state.h | 17 ++++++++++++----- |
| 2 files changed, 19 insertions(+), 7 deletions(-) |
| |
| --- a/fs/nfsd/nfs4xdr.c |
| +++ b/fs/nfsd/nfs4xdr.c |
| @@ -5438,9 +5438,14 @@ nfsd4_encode_operation(struct nfsd4_comp |
| int len = xdr->buf->len - post_err_offset; |
| |
| so->so_replay.rp_status = op->status; |
| - so->so_replay.rp_buflen = len; |
| - read_bytes_from_xdr_buf(xdr->buf, post_err_offset, |
| + if (len <= NFSD4_REPLAY_ISIZE) { |
| + so->so_replay.rp_buflen = len; |
| + read_bytes_from_xdr_buf(xdr->buf, |
| + post_err_offset, |
| so->so_replay.rp_buf, len); |
| + } else { |
| + so->so_replay.rp_buflen = 0; |
| + } |
| } |
| status: |
| *p = op->status; |
| --- a/fs/nfsd/state.h |
| +++ b/fs/nfsd/state.h |
| @@ -430,11 +430,18 @@ struct nfs4_client_reclaim { |
| struct xdr_netobj cr_princhash; |
| }; |
| |
| -/* A reasonable value for REPLAY_ISIZE was estimated as follows: |
| - * The OPEN response, typically the largest, requires |
| - * 4(status) + 8(stateid) + 20(changeinfo) + 4(rflags) + 8(verifier) + |
| - * 4(deleg. type) + 8(deleg. stateid) + 4(deleg. recall flag) + |
| - * 20(deleg. space limit) + ~32(deleg. ace) = 112 bytes |
| +/* |
| + * REPLAY_ISIZE is sized for an OPEN response with delegation: |
| + * 4(status) + 8(stateid) + 20(changeinfo) + 4(rflags) + |
| + * 8(verifier) + 4(deleg. type) + 8(deleg. stateid) + |
| + * 4(deleg. recall flag) + 20(deleg. space limit) + |
| + * ~32(deleg. ace) = 112 bytes |
| + * |
| + * Some responses can exceed this. A LOCK denial includes the conflicting |
| + * lock owner, which can be up to 1024 bytes (NFS4_OPAQUE_LIMIT). Responses |
| + * larger than REPLAY_ISIZE are not cached in rp_ibuf; only rp_status is |
| + * saved. Enlarging this constant increases the size of every |
| + * nfs4_stateowner. |
| */ |
| |
| #define NFSD4_REPLAY_ISIZE 112 |