In the Linux kernel, the following vulnerability has been resolved:
SUNRPC: harden gss_krb5_unwrap_v2 against short tokens
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:
<pre>
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 */
</pre>
After decryption, the cleanup arithmetic has the same shape:
<pre>
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);
</pre>
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.
CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CVSS Score: 9.8
AV:N - gss_krb5_unwrap_v2() is reached from nfsd RPCSEC_GSS privacy unwrap (svcauth_gss_accept → svcauth_gss_unwrap_priv → gss_unwrap) on TCP/2049 and from the NFS client gss_unwrap_resp_priv() path on a krb5p Reply, so a remote RPCSEC_GSS peer delivers the crafted wrap token over the network.
AC:L - A single crafted RPCSEC_GSS DATA/Reply with a sub-16-byte wrap token (KG2_TOK_WRAP, sealed flags, non-zero RRC) deterministically hits the pre-decrypt ptr+4/ptr+6 reads, rotate_left() u32 wrap, and shift%=0 divide-by-zero; no race or attacker-uncontrollable layout is required.
PR:N - A malicious NFS server on an existing krb5p session needs no account or capability on the victim client to send the crafted Reply into gss_unwrap_resp_priv(). That client-victim path is the highest-severity reachability and requires no privileges on the target.
UI:N - nfsd unwraps the Call in the service thread with no local user action. On the client, gss_unwrap_resp_priv() runs automatically from the RPC receive path once an NFS/RPCSEC_GSS mount exists; no additional mount, click, or open is required at attack time.
S:U - The out-of-bounds accesses, divide-by-zero, and underflowed memmove corrupt host kernel SUNRPC/GSS state on the machine processing the RPC. This is ordinary same-host kernel impact, not a VM escape, IOMMU bypass, or other changed-scope boundary.
C:H - Short tokens make gss_krb5_unwrap_v2() read EC/RRC past the token, and the dead BUG_ON lets unsigned movelen underflow into a ~UINT_MAX-byte memmove() that copies kernel memory beyond the receive head; that unbounded read is C:H per kernel CNA memory-corruption guidance.
I:H - The same underflowed memmove() writes near 4GiB through the RPC receive head, an out-of-bounds write exploitable for control-flow hijack. rotate_left() also ignores xdr_buf_subsegment() failure after u32 wrap and then writes through a subbuf whose length has wrapped.
A:H - _rotate_left() does shift %= buf->len on a zero-length subbuffer (header-only token at the XDR boundary), a divide-by-zero oops/panic; the UINT_MAX memmove and wrapped-length rotate loop hang or oops nfsd/rpciod, and the attacker can repeat the RPC to deny service.
| Attack Vector |
Network |
Scope |
Unchanged |
| Attack Complexity |
Low |
Confidentiality Impact |
High |
| Privileges Required |
None |
Integrity Impact |
High |
| User Interaction |
None |
Availability Impact |
High |
AV:N - gss_krb5_unwrap_v2() is reached from nfsd RPCSEC_GSS privacy unwrap (svcauth_gss_accept → svcauth_gss_unwrap_priv → gss_unwrap) on TCP/2049 and from the NFS client gss_unwrap_resp_priv() path on a krb5p Reply, so a remote RPCSEC_GSS peer delivers the crafted wrap token over the network.
AC:L - A single crafted RPCSEC_GSS DATA/Reply with a sub-16-byte wrap token (KG2_TOK_WRAP, sealed flags, non-zero RRC) deterministically hits the pre-decrypt ptr+4/ptr+6 reads, rotate_left() u32 wrap, and shift%=0 divide-by-zero; no race or attacker-uncontrollable layout is required.
PR:N - A malicious NFS server on an existing krb5p session needs no account or capability on the victim client to send the crafted Reply into gss_unwrap_resp_priv(). That client-victim path is the highest-severity reachability and requires no privileges on the target.
UI:N - nfsd unwraps the Call in the service thread with no local user action. On the client, gss_unwrap_resp_priv() runs automatically from the RPC receive path once an NFS/RPCSEC_GSS mount exists; no additional mount, click, or open is required at attack time.
S:U - The out-of-bounds accesses, divide-by-zero, and underflowed memmove corrupt host kernel SUNRPC/GSS state on the machine processing the RPC. This is ordinary same-host kernel impact, not a VM escape, IOMMU bypass, or other changed-scope boundary.
C:H - Short tokens make gss_krb5_unwrap_v2() read EC/RRC past the token, and the dead BUG_ON lets unsigned movelen underflow into a ~UINT_MAX-byte memmove() that copies kernel memory beyond the receive head; that unbounded read is C:H per kernel CNA memory-corruption guidance.
I:H - The same underflowed memmove() writes near 4GiB through the RPC receive head, an out-of-bounds write exploitable for control-flow hijack. rotate_left() also ignores xdr_buf_subsegment() failure after u32 wrap and then writes through a subbuf whose length has wrapped.
A:H - _rotate_left() does shift %= buf->len on a zero-length subbuffer (header-only token at the XDR boundary), a divide-by-zero oops/panic; the UINT_MAX memmove and wrapped-length rotate loop hang or oops nfsd/rpciod, and the attacker can repeat the RPC to deny service.
CVSS 3.1