* [PATCH 0/4] sunrpc: close length validation gaps in krb5p unwrap
@ 2026-05-24 1:02 Chuck Lever
2026-05-24 1:02 ` [PATCH 1/4] SUNRPC: svcauth_gss: enforce krb5 token minimum length Chuck Lever
` (4 more replies)
0 siblings, 5 replies; 6+ messages in thread
From: Chuck Lever @ 2026-05-24 1:02 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, NeilBrown, Jeff Layton,
Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: linux-nfs, Chuck Lever
From: Chuck Lever <chuck.lever@oracle.com>
Neither svcauth_gss_unwrap_priv() nor gss_unwrap_resp_priv()
enforces a minimum on the wire-supplied opaque length before
forwarding it to the krb5 unwrap core. An authenticated krb5p
peer -- NFS client or server -- can send a token shorter than the
16-byte RFC 4121 header and reach gss_krb5_unwrap_v2() with a
length that drives pre-decrypt out-of-bounds reads, a
divide-by-zero in _rotate_left(), and a post-decrypt unsigned
underflow in the movelen computation that feeds a multi-gigabyte
memmove.
The fixes are layered so that neither the caller boundary nor
the unwrap core depends on the other for safety. The caller-side
floor checks (patches 1, 2) are sufficient to close the attack,
but gss_krb5_unwrap_v2() must also be self-defending because it
is reachable through gss_unwrap() from any future caller that
might not enforce the same floor. The xdr_buf_trim() clamp
(patch 3) is independent of krb5 -- it corrects a generic
invariant violation that gss_krb5_unwrap_v2() happens to be the
only current trigger for, but that any caller could hit if
buf->len and the component iov_lens get out of sync.
The client side (patch 2) has an additional u32 integer-overflow
bypass that the server side does not: the original
`offset + opaque_len > rcv_buf->len` wraps when opaque_len is
near UINT_MAX, accepting a huge token length that re-enters the
same underflow chain. The replacement splits the check into
separate offset and length guards that are safe in u32 arithmetic.
Chris Mason (4):
SUNRPC: svcauth_gss: enforce krb5 token minimum length
SUNRPC: harden gss_unwrap_resp_priv length checks
SUNRPC: xdr_buf_trim: clamp buf->len to avoid underflow
SUNRPC: harden gss_krb5_unwrap_v2 against short tokens
net/sunrpc/auth_gss/auth_gss.c | 6 +++++-
net/sunrpc/auth_gss/gss_krb5_wrap.c | 11 +++++++++--
net/sunrpc/auth_gss/svcauth_gss.c | 2 ++
net/sunrpc/xdr.c | 2 +-
4 files changed, 17 insertions(+), 4 deletions(-)
--
2.54.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 1/4] SUNRPC: svcauth_gss: enforce krb5 token minimum length
2026-05-24 1:02 [PATCH 0/4] sunrpc: close length validation gaps in krb5p unwrap Chuck Lever
@ 2026-05-24 1:02 ` Chuck Lever
2026-05-24 1:02 ` [PATCH 2/4] SUNRPC: harden gss_unwrap_resp_priv length checks Chuck Lever
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Chuck Lever @ 2026-05-24 1:02 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, NeilBrown, Jeff Layton,
Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: linux-nfs, Chris Mason
From: Chris Mason <clm@meta.com>
svcauth_gss_unwrap_priv() validates only an upper bound on the
wire-supplied opaque length before handing the buffer to
gss_unwrap():
if (len > xdr_stream_remaining(xdr))
goto unwrap_failed;
offset = xdr_stream_pos(xdr);
...
maj_stat = gss_unwrap(ctx, offset, offset + len, buf);
The wire value `len` flows unchanged as the upper bound into the
krb5 unwrap path, so a len in [0, 16] passes this check and is
handed to gss_unwrap(). For a krb5 v2 context that lands in
gss_krb5_unwrap_v2(), which reads the 16-byte RFC 4121 token
header fields at ptr+4 and ptr+6 and then calls rotate_left()
before any integrity check. With a sub-header length the header
reads run past the token, and _rotate_left()'s `shift %= buf->len`
path can divide by zero when buf->len has been driven to zero by
the truncated token. A header-only token (len == 16) is equally
invalid: with a non-zero RRC field and the opaque blob ending at
the XDR buffer boundary, rotate_left() builds a zero-length
subbuffer, reaching the same division.
Reject the token at the server entry point before it reaches the
krb5 unwrap core. A valid sealed RFC 4121 token must contain
the 16-byte header plus at least some encrypted payload.
Fix by adding a minimum-length check immediately after the
existing upper-bound check:
if (len <= GSS_KRB5_TOK_HDR_LEN)
goto unwrap_failed;
Fixes: 7c9fdcfb1b64 ("[PATCH] knfsd: svcrpc: gss: server-side implementation of rpcsec_gss privacy")
Assisted-by: kres (claude-opus-4-7)
Signed-off-by: Chris Mason <clm@meta.com>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
net/sunrpc/auth_gss/svcauth_gss.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/net/sunrpc/auth_gss/svcauth_gss.c b/net/sunrpc/auth_gss/svcauth_gss.c
index d14209031e18..8e8aceb31270 100644
--- a/net/sunrpc/auth_gss/svcauth_gss.c
+++ b/net/sunrpc/auth_gss/svcauth_gss.c
@@ -949,6 +949,8 @@ svcauth_gss_unwrap_priv(struct svc_rqst *rqstp, u32 seq, struct gss_ctx *ctx)
}
if (len > xdr_stream_remaining(xdr))
goto unwrap_failed;
+ if (len <= GSS_KRB5_TOK_HDR_LEN)
+ goto unwrap_failed;
offset = xdr_stream_pos(xdr);
saved_len = buf->len;
--
2.54.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 2/4] SUNRPC: harden gss_unwrap_resp_priv length checks
2026-05-24 1:02 [PATCH 0/4] sunrpc: close length validation gaps in krb5p unwrap Chuck Lever
2026-05-24 1:02 ` [PATCH 1/4] SUNRPC: svcauth_gss: enforce krb5 token minimum length Chuck Lever
@ 2026-05-24 1:02 ` Chuck Lever
2026-05-24 1:02 ` [PATCH 3/4] SUNRPC: xdr_buf_trim: clamp buf->len to avoid underflow Chuck Lever
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Chuck Lever @ 2026-05-24 1:02 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, NeilBrown, Jeff Layton,
Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: linux-nfs, Chris Mason
From: Chris Mason <clm@meta.com>
gss_unwrap_resp_priv() validates the RPCSEC_GSS opaque length with
offset = (u8 *)(p) - (u8 *)head->iov_base;
if (offset + opaque_len > rcv_buf->len)
goto unwrap_failed;
maj_stat = gss_unwrap(ctx->gc_gss_ctx, offset,
offset + opaque_len, rcv_buf);
Both operands are u32 and the sum is computed in u32. A reply with
opaque_len near 0xffffffff makes offset + opaque_len wrap to a small
value that is below rcv_buf->len, so the bound check passes and
gss_unwrap() is called with end < begin. The check also lacks a
lower bound, so any opaque_len in [0, GSS_KRB5_TOK_HDR_LEN) is
accepted and forwarded to gss_krb5_unwrap_v2(), whose pre-decrypt
header reads at ptr+4 and ptr+6 then run past the token.
A krb5p NFS server returning a crafted RPCSEC_GSS reply can drive
the client into out-of-bounds reads in gss_krb5_unwrap_v2() and the
rotate_left() loop that follows.
Fix by replacing the single combined check with three guards that
are safe in u32 arithmetic and that enforce the RFC 4121 minimum
outer token length:
if (offset > rcv_buf->len)
goto unwrap_failed;
if (opaque_len > rcv_buf->len - offset)
goto unwrap_failed;
if (opaque_len < GSS_KRB5_TOK_HDR_LEN)
goto unwrap_failed;
The first guard makes the subtraction in the second guard
unconditionally safe; offset is derived from a successful
xdr_inline_decode() in the head kvec, so in practice it already
satisfies the bound. The floor mirrors the server-side check added
in commit 7507012eeb98 ("SUNRPC: svcauth_gss: enforce krb5 token
minimum length").
Fixes: 2d2da60c63b6 ("RPCSEC_GSS: client-side privacy support")
Assisted-by: kres (claude-opus-4-7)
Signed-off-by: Chris Mason <clm@meta.com>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
net/sunrpc/auth_gss/auth_gss.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/net/sunrpc/auth_gss/auth_gss.c b/net/sunrpc/auth_gss/auth_gss.c
index 9d3fb6848f40..8ddc65e894da 100644
--- a/net/sunrpc/auth_gss/auth_gss.c
+++ b/net/sunrpc/auth_gss/auth_gss.c
@@ -2072,7 +2072,11 @@ gss_unwrap_resp_priv(struct rpc_task *task, struct rpc_cred *cred,
goto unwrap_failed;
opaque_len = be32_to_cpup(p++);
offset = (u8 *)(p) - (u8 *)head->iov_base;
- if (offset + opaque_len > rcv_buf->len)
+ if (offset > rcv_buf->len)
+ goto unwrap_failed;
+ if (opaque_len > rcv_buf->len - offset)
+ goto unwrap_failed;
+ if (opaque_len <= GSS_KRB5_TOK_HDR_LEN)
goto unwrap_failed;
maj_stat = gss_unwrap(ctx->gc_gss_ctx, offset,
--
2.54.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 3/4] SUNRPC: xdr_buf_trim: clamp buf->len to avoid underflow
2026-05-24 1:02 [PATCH 0/4] sunrpc: close length validation gaps in krb5p unwrap Chuck Lever
2026-05-24 1:02 ` [PATCH 1/4] SUNRPC: svcauth_gss: enforce krb5 token minimum length Chuck Lever
2026-05-24 1:02 ` [PATCH 2/4] SUNRPC: harden gss_unwrap_resp_priv length checks Chuck Lever
@ 2026-05-24 1:02 ` Chuck Lever
2026-05-24 1:02 ` [PATCH 4/4] SUNRPC: harden gss_krb5_unwrap_v2 against short tokens Chuck Lever
2026-05-24 10:56 ` [PATCH 0/4] sunrpc: close length validation gaps in krb5p unwrap Jeff Layton
4 siblings, 0 replies; 6+ messages in thread
From: Chuck Lever @ 2026-05-24 1:02 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, NeilBrown, Jeff Layton,
Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: linux-nfs, Chris Mason
From: Chris Mason <clm@meta.com>
xdr_buf_trim() trims `len` bytes from the tail of an xdr_buf by
walking the tail, pages, and head iovecs. Each per-section step
uses min_t() so it never removes more bytes than that section
holds, but the final accounting at the fix_len label subtracts the
total bytes actually consumed from buf->len without any clamp:
fix_len:
buf->len -= (len - trim);
When the caller has set buf->len to a value smaller than the sum
of the iov_lens, (len - trim) can exceed buf->len and the unsigned
subtraction wraps to near UINT_MAX. gss_krb5_unwrap_v2() reaches
xdr_buf_trim() in exactly that state:
buf->head[0].iov_len -= GSS_KRB5_TOK_HDR_LEN + headskip;
buf->len = len - (GSS_KRB5_TOK_HDR_LEN + headskip);
xdr_buf_trim(buf, ec + GSS_KRB5_TOK_HDR_LEN + tailskip);
buf->len is a small wire-derived value while the iov_lens are at
page scale, so the per-section loops legitimately consume far more
bytes than buf->len records. The wrapped buf->len then propagates
as the authoritative stream bound into every downstream XDR
decoder.
Fix by clamping the decrement so buf->len bottoms out at zero:
buf->len -= min_t(unsigned int, buf->len, len - trim);
On the normal path where the iov_lens sum to buf->len, (len - trim)
is always <= buf->len and the result is identical to before. No
callers change behavior outside the underflow case.
Fixes: 0a8e7b7d0846 ("SUNRPC: Revert 241b1f419f0e (\"SUNRPC: Remove xdr_buf_trim()\")")
Assisted-by: kres (claude-opus-4-7)
Signed-off-by: Chris Mason <clm@meta.com>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
net/sunrpc/xdr.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/sunrpc/xdr.c b/net/sunrpc/xdr.c
index fa6a30b5f046..cb2ef428651f 100644
--- a/net/sunrpc/xdr.c
+++ b/net/sunrpc/xdr.c
@@ -2049,7 +2049,7 @@ void xdr_buf_trim(struct xdr_buf *buf, unsigned int len)
trim -= cur;
}
fix_len:
- buf->len -= (len - trim);
+ buf->len -= min_t(unsigned int, buf->len, len - trim);
}
EXPORT_SYMBOL_GPL(xdr_buf_trim);
--
2.54.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 4/4] SUNRPC: harden gss_krb5_unwrap_v2 against short tokens
2026-05-24 1:02 [PATCH 0/4] sunrpc: close length validation gaps in krb5p unwrap Chuck Lever
` (2 preceding siblings ...)
2026-05-24 1:02 ` [PATCH 3/4] SUNRPC: xdr_buf_trim: clamp buf->len to avoid underflow Chuck Lever
@ 2026-05-24 1:02 ` Chuck Lever
2026-05-24 10:56 ` [PATCH 0/4] sunrpc: close length validation gaps in krb5p unwrap Jeff Layton
4 siblings, 0 replies; 6+ messages in thread
From: Chuck Lever @ 2026-05-24 1:02 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, NeilBrown, Jeff Layton,
Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: linux-nfs, Chris Mason
From: Chris Mason <clm@meta.com>
gss_krb5_unwrap_v2() reads the EC and RRC header fields at ptr+4 and
ptr+6 before validating that the token is at least GSS_KRB5_TOK_HDR_LEN
(16) bytes long, and its rotate_left() helper passes buf->len - base
to xdr_buf_subsegment() without verifying that base <= buf->len. When
a caller hands in a sub-16-byte token, or a token whose declared len
leaves base past the end of the buffer, three distinct failures follow:
gss_krb5_unwrap_v2(offset, len, buf)
ptr = buf->head[0].iov_base + offset
ec = *(ptr + 4) /* OOB read on short head */
rrc = *(ptr + 6) /* OOB read on short head */
rotate_left(offset + 16, buf, rrc)
xdr_buf_subsegment(buf, &subbuf,
base, buf->len - base) /* u32 wrap when base > len */
_rotate_left(&subbuf, shift)
shift %= buf->len /* divide-by-zero when base == len */
After decryption, the cleanup arithmetic has the same shape:
movelen = min_t(unsigned int, buf->head[0].iov_len, len);
movelen -= offset + GSS_KRB5_TOK_HDR_LEN + headskip;
BUG_ON(offset + GSS_KRB5_TOK_HDR_LEN + headskip + movelen >
buf->head[0].iov_len);
The BUG_ON re-adds the value just subtracted, so it reduces to
min(A, B) > A and is permanently false; it cannot catch the unsigned
underflow of movelen, which then drives a ~UINT_MAX-byte memmove().
Add four defense-in-depth guards inside the unwrap core so it is safe
regardless of what its callers validate:
- reject tokens with len - offset < GSS_KRB5_TOK_HDR_LEN before
touching ptr+4/ptr+6;
- bail from rotate_left() when buf->len <= base, covering both the
underflow and zero-length cases;
- return early from _rotate_left() when buf->len is zero, so the
shift %= buf->len modulo cannot fault;
- replace the dead BUG_ON with a live check that returns
GSS_S_DEFECTIVE_TOKEN before the movelen subtraction.
Fixes: de9c17eb4a91 ("gss_krb5: add support for new token formats in rfc4121")
Assisted-by: kres (claude-opus-4-7)
Signed-off-by: Chris Mason <clm@meta.com>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
net/sunrpc/auth_gss/gss_krb5_wrap.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/net/sunrpc/auth_gss/gss_krb5_wrap.c b/net/sunrpc/auth_gss/gss_krb5_wrap.c
index ac4b32df42b9..d84c35f779f5 100644
--- a/net/sunrpc/auth_gss/gss_krb5_wrap.c
+++ b/net/sunrpc/auth_gss/gss_krb5_wrap.c
@@ -73,6 +73,8 @@ static void _rotate_left(struct xdr_buf *buf, unsigned int shift)
int shifted = 0;
int this_shift;
+ if (!buf->len)
+ return;
shift %= buf->len;
while (shifted < shift) {
this_shift = min(shift - shifted, LOCAL_BUF_LEN);
@@ -85,6 +87,8 @@ static void rotate_left(u32 base, struct xdr_buf *buf, unsigned int shift)
{
struct xdr_buf subbuf;
+ if (buf->len <= base)
+ return;
xdr_buf_subsegment(buf, &subbuf, base, buf->len - base);
_rotate_left(&subbuf, shift);
}
@@ -154,6 +158,9 @@ gss_krb5_unwrap_v2(struct krb5_ctx *kctx, int offset, int len,
dprintk("RPC: %s\n", __func__);
+ if (len - offset <= GSS_KRB5_TOK_HDR_LEN)
+ return GSS_S_DEFECTIVE_TOKEN;
+
ptr = buf->head[0].iov_base + offset;
if (be16_to_cpu(*((__be16 *)ptr)) != KG2_TOK_WRAP)
@@ -220,9 +227,9 @@ gss_krb5_unwrap_v2(struct krb5_ctx *kctx, int offset, int len,
* head buffer space rather than that actually occupied.
*/
movelen = min_t(unsigned int, buf->head[0].iov_len, len);
+ if (movelen < offset + GSS_KRB5_TOK_HDR_LEN + headskip)
+ return GSS_S_DEFECTIVE_TOKEN;
movelen -= offset + GSS_KRB5_TOK_HDR_LEN + headskip;
- BUG_ON(offset + GSS_KRB5_TOK_HDR_LEN + headskip + movelen >
- buf->head[0].iov_len);
memmove(ptr, ptr + GSS_KRB5_TOK_HDR_LEN + headskip, movelen);
buf->head[0].iov_len -= GSS_KRB5_TOK_HDR_LEN + headskip;
buf->len = len - (GSS_KRB5_TOK_HDR_LEN + headskip);
--
2.54.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH 0/4] sunrpc: close length validation gaps in krb5p unwrap
2026-05-24 1:02 [PATCH 0/4] sunrpc: close length validation gaps in krb5p unwrap Chuck Lever
` (3 preceding siblings ...)
2026-05-24 1:02 ` [PATCH 4/4] SUNRPC: harden gss_krb5_unwrap_v2 against short tokens Chuck Lever
@ 2026-05-24 10:56 ` Jeff Layton
4 siblings, 0 replies; 6+ messages in thread
From: Jeff Layton @ 2026-05-24 10:56 UTC (permalink / raw)
To: Chuck Lever, Trond Myklebust, Anna Schumaker, NeilBrown,
Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: linux-nfs, Chuck Lever
On Sat, 2026-05-23 at 21:02 -0400, Chuck Lever wrote:
> From: Chuck Lever <chuck.lever@oracle.com>
>
> Neither svcauth_gss_unwrap_priv() nor gss_unwrap_resp_priv()
> enforces a minimum on the wire-supplied opaque length before
> forwarding it to the krb5 unwrap core. An authenticated krb5p
> peer -- NFS client or server -- can send a token shorter than the
> 16-byte RFC 4121 header and reach gss_krb5_unwrap_v2() with a
> length that drives pre-decrypt out-of-bounds reads, a
> divide-by-zero in _rotate_left(), and a post-decrypt unsigned
> underflow in the movelen computation that feeds a multi-gigabyte
> memmove.
>
> The fixes are layered so that neither the caller boundary nor
> the unwrap core depends on the other for safety. The caller-side
> floor checks (patches 1, 2) are sufficient to close the attack,
> but gss_krb5_unwrap_v2() must also be self-defending because it
> is reachable through gss_unwrap() from any future caller that
> might not enforce the same floor. The xdr_buf_trim() clamp
> (patch 3) is independent of krb5 -- it corrects a generic
> invariant violation that gss_krb5_unwrap_v2() happens to be the
> only current trigger for, but that any caller could hit if
> buf->len and the component iov_lens get out of sync.
>
> The client side (patch 2) has an additional u32 integer-overflow
> bypass that the server side does not: the original
> `offset + opaque_len > rcv_buf->len` wraps when opaque_len is
> near UINT_MAX, accepting a huge token length that re-enters the
> same underflow chain. The replacement splits the check into
> separate offset and length guards that are safe in u32 arithmetic.
>
> Chris Mason (4):
> SUNRPC: svcauth_gss: enforce krb5 token minimum length
> SUNRPC: harden gss_unwrap_resp_priv length checks
> SUNRPC: xdr_buf_trim: clamp buf->len to avoid underflow
> SUNRPC: harden gss_krb5_unwrap_v2 against short tokens
>
> net/sunrpc/auth_gss/auth_gss.c | 6 +++++-
> net/sunrpc/auth_gss/gss_krb5_wrap.c | 11 +++++++++--
> net/sunrpc/auth_gss/svcauth_gss.c | 2 ++
> net/sunrpc/xdr.c | 2 +-
> 4 files changed, 17 insertions(+), 4 deletions(-)
Looks good. The unsigned offset math in this parsing code is just so
nasty. I wonder if we'd be better off using signed math here for some
of this. Oh well...
Reviewed-by: Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-05-24 10:56 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-24 1:02 [PATCH 0/4] sunrpc: close length validation gaps in krb5p unwrap Chuck Lever
2026-05-24 1:02 ` [PATCH 1/4] SUNRPC: svcauth_gss: enforce krb5 token minimum length Chuck Lever
2026-05-24 1:02 ` [PATCH 2/4] SUNRPC: harden gss_unwrap_resp_priv length checks Chuck Lever
2026-05-24 1:02 ` [PATCH 3/4] SUNRPC: xdr_buf_trim: clamp buf->len to avoid underflow Chuck Lever
2026-05-24 1:02 ` [PATCH 4/4] SUNRPC: harden gss_krb5_unwrap_v2 against short tokens Chuck Lever
2026-05-24 10:56 ` [PATCH 0/4] sunrpc: close length validation gaps in krb5p unwrap Jeff Layton
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.