Re: kernel svc_rpc_gss_update_seq missing 2017 Coverity fix + TOCTOU with check_replay

Stanislav Fort <[email protected]>
Newsgroups gmane.os.freebsd.devel.net
Message-ID <CA+uRpKQFjrF7zHsXdCg5f3_JURgUsLXcAmbsh7LUhp=T1kYtUg@mail.gmail.com>
Author is even better, thanks! -Stan

On Sun, Apr 12, 2026 at 2:23 PM Rick Macklem <[email protected]> wrote:

> On Sun, Apr 12, 2026 at 12:08 AM Stanislav Fort
> <[email protected]> wrote:
> >
> > Hi Rick,
> >
> > Thanks for picking up the first fix. Could you please include a Reported
> by: tag for me in the commit? (Stanislav Fort of Aisle Research <
> [email protected]>)
> I was going to list you as author, but if you prefer
> reported by, I can do that.
>
> >
> > For the TOCTOU issue, I went ahead and wrote a minimal patch = just a
> bounds check in update_seq rather than widening the lock scope, so it
> shouldn't affect the validate/nextverf path at all:
> >
> > --- a/sys/rpc/rpcsec_gss/svc_rpcsec_gss.c
> > +++ b/sys/rpc/rpcsec_gss/svc_rpcsec_gss.c
> > @@ -1387,6 +1387,8 @@
> >   } else {
> >   offset = client->cl_seqlast - seq;
> > + if (offset >= SVC_RPC_GSS_SEQWINDOW)
> > + goto out;
> >   word = offset / 32;
> >   bit = offset % 32;
> >   client->cl_seqmask[word] |= (1 << bit);
> > @@ -1392,6 +1394,8 @@
> >   }
> > +out:
> >   sx_xunlock(&client->cl_lock);
> >  }
> >
> > The idea: if another thread advanced cl_seqlast past the window between
> check_replay and update_seq, the sequence number is already too old to
> track, so we just skip the write. This avoids the OOB on cl_seqmask without
> changing the locking around validate/nextverf.
> >
> > Happy to also put this up on Phabricator if that's easier for review.
> >
> > Best wishes,
> > Stanislav Fort
> > Aisle Research
> >
> > On Sun, Apr 12, 2026 at 2:24 AM Rick Macklem <[email protected]>
> wrote:
> >>
> >> On Sat, Apr 11, 2026 at 4:59 PM Rick Macklem <[email protected]>
> wrote:
> >> >
> >> > On Sat, Apr 11, 2026 at 3:42 PM Stanislav Fort <
> [email protected]> wrote:
> >> > >
> >> > > Hi there,
> >> > >
> >> > > I think there might be two bugs in
> sys/rpc/rpcsec_gss/svc_rpcsec_gss.c in svc_rpc_gss_update_seq():
> >> > >
> >> > > 1. Missing port of lib/librpcsec_gss fix from 11bc2c1ca77e
> >> > >
> >> > > The userspace lib/librpcsec_gss/svc_rpcsec_gss.c was fixed in 2017
> (commit 11bc2c1ca77e, Coverity CID 1198859) to change "while (offset > 32)"
> to "while (offset >= 32)" and guard the subsequent bit-shift loop with "if
> (offset > 0)". The kernel copy still has the old pattern:
> >> > >
> >> > >     while (offset > 32) {          /* should be >= 32 */
> >> > >         /* ... word shift ... */
> >> > >         offset -= 32;
> >> > >     }
> >> > >                                     /* missing: if (offset > 0) { */
> >> > >     carry = 0;
> >> > >     for (i = 0; i < SVC_RPC_GSS_SEQWINDOW / 32; i++) {
> >> > >         newcarry = client->cl_seqmask[i] >> (32 - offset);
> >> > >         client->cl_seqmask[i] =
> >> > >             (client->cl_seqmask[i] << offset) | carry;  /* UB when
> offset==32 */
> >> > >         carry = newcarry;
> >> > >     }
> >> > >
> >> > > When offset is exactly 32, the while loop doesn't execute and the
> shift loop does << 32 on a uint32_t, which is undefined behavior(?).
> >> Btw, I tried "s << 32", where s is uint32_t on amd64 with clang and it
> >> does not change value of s.
> >>
> >> Therefore, the loop ends up setting extra bits (or of the adjacent
> >> seqmask with the next seqmask) and, if I
> >> recall RPCSEC_GSS correctly, that will result in RPCs
> >> which should be valid being discarded as retries.
> >> (This window of seq#s is meant to subvert replay attempts.)
> >>
> >> rick
> >>
> >> > The fix is to match what lib/librpcsec_gss already does: use >= 32
> and wrap the shift loop in if (offset > 0).
> >> >
> >> > This makes sense. I will commit the patch.
> >> > I think the most this bug can do is result in a bogus
> >> > retry of an RPC. Since kerberized NFS isn't used a
> >> > lot and almost always over TCP (where the sequence#
> >> > increments by 1 for each RPC and they generally stay
> >> > in order), I don't think this bug introduced a serious issue.
> >> > (The seq# in RPCSEC_GSS is basically for UDP.)
> >> >
> >> > >
> >> > > 2. TOCTOU between check_replay and update_seq
> >> > >
> >> > > In svc_rpc_gss(), svc_rpc_gss_check_replay() and
> svc_rpc_gss_update_seq() each independently acquire and release cl_lock.
> Between the two calls, svc_rpc_gss_validate() and svc_rpc_gss_nextverf()
> run without the lock held. Since svc_rpc_gss_find_client() matches clients
> by opaque handle and doesn't bind them to a specific transport, two
> connections presenting the same GSS handle can drive concurrent threads
> through svc_rpc_gss() on the same client struct.
> >> > >
> >> > > If thread A passes check_replay for a low sequence number, then
> thread B advances cl_seqlast significantly before thread A enters
> update_seq, the else branch in update_seq computes:
> >> > >
> >> > >     offset = client->cl_seqlast - seq;
> >> > >     word = offset / 32;
> >> > >     bit = offset % 32;
> >> > >     client->cl_seqmask[word] |= (1 << bit);
> >> > >
> >> > > Word can exceed SVC_RPC_GSS_SEQWINDOW/32 - 1 (i.e., >= 4), causing
> an OOB write to cl_seqmask. This requires authenticated access (GSS MIC
> verification still gates both paths) and winning a race, so practical
> impact is limited.
> >> > >
> >> > > A simple fix would be to hold cl_lock across both check and update,
> or to re-validate offset < SVC_RPC_GSS_SEQWINDOW inside update_seq before
> writing.
> >> >
> >> > I'll look at fixing this one later, but as you note, it is also
> unlikely
> >> > to cause serious trouble, imho.
> >> >
> >> > rick
> >> >
> >> > >
> >> > > Let me know what you think.
> >> > >
> >> > > Best wishes,
> >> > > Stanislav Fort
> >> > > Aisle Research
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.