Re: [PATCH 1/1] nfsd: fix NLM access checking

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, Mar 20, 2025 at 1:32 PM Chuck Lever <chuck.lever@xxxxxxxxxx> wrote:
>
> On 3/20/25 12:29 PM, Olga Kornievskaia wrote:
> > On Thu, Mar 20, 2025 at 9:58 AM Chuck Lever <chuck.lever@xxxxxxxxxx> wrote:
> >>
> >> Hi Olga -
> >>
> >> Thanks for taking a stab at this. Comments below.
> >>
> >>
> >> On 3/19/25 5:46 PM, Olga Kornievskaia wrote:
> >>> Since commit 4cc9b9f2bf4df ("nfsd: refine and rename NFSD_MAY_LOCK")
> >>> for export policies with "sec=krb5:..." or "xprtsec=tls:.." NLM
> >>> locking calls on v3 mounts fail. And for "sec=krb5" NLM calls it
> >>> also leads to out-of-bounds reference while in check_nfsd_access().
> >>>
> >>> This patch does 3 fixes.
> >>
> >> That suggests to me this should ideally be three patches. That's a nit,
> >> really, but 3 patches might be better for subsequent bisection.
> >
> > I can break it into 3 patches but all would have "Fixes" for the same
> > patch, right?
>
> Yes.
>
>
> >>> 2 problems are related to sec=krb5.
> >>> First is fixing what "access" content is being passed into
> >>> the inode_permission(). Prior to 4cc9b9f2bf4df, the code would
> >>> explicitly set access to be read/ownership. And after is passes
> >>> access that's set in nlm_fopen but it's lacking read access.
> >>> Second is because previously for NLM check_nfsd_access() was
> >>> never called and thus nfsd4_spo_must_allow() function wasn't
> >>> called. After the patch, this lead to NLM call which has no
> >>> compound state structure created trying to dereference it.
> >>> This patch instead moves the call to after may_bypass_gss
> >>> check which implies NLM and would return there and would
> >>> never get to calling nfsd4_spo_must_allow().
> >>>
> >>> The last problem is related to TLS export policy. NLM dont
> >>> come over TLS and thus dont pass the TLS checks in
> >>> check_nfsd_access() leading to access being denied. Instead
> >>> rely on may_bypass_gss to indicate NLM and allow access
> >>> checking to continue.
> >>
> >> NFSD doesn't check that NLM is TLS protected because:
> >>
> >> a. the current Linux NFS client doesn't use TLS, and
> >> b. NFSD doesn't support NLM over krb5, IIRC.
> >>
> >> But that doesn't mean NLM will /never/ be protected by TLS.
> >
> > But if the future NFSD would support NLM with TLS wouldn't it be a new
> > feature with possible new controls. We'd still need existing code to
> > all interoperability with clients that don't support it (and thus
> > having a configuration that would allow NLM without TLS when xprtsec
> > is TLS-enabled?
> >
> >> I'm thinking aloud about whether the reorganization here might make
> >> adding TLS for NLM easier or more difficult. No conclusions yet.
> >
> > Do we need that complexity now vs if and when such support is added?
>
> The question is would we need to revert some or all of your patch
> to make that work in the future in order to support NLM with TLS.
> But perhaps that is an unanswerable question today.
>
>
> >>> Fixes: 4cc9b9f2bf4df ("nfsd: refine and rename NFSD_MAY_LOCK")
> >>> Signed-off-by: Olga Kornievskaia <okorniev@xxxxxxxxxx>
> >>>
> >>> ---
> >>>  fs/nfsd/export.c | 23 +++++++++++++----------
> >>>  fs/nfsd/vfs.c    |  4 ++++
> >>>  2 files changed, 17 insertions(+), 10 deletions(-)
> >>>
> >>> diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c
> >>> index 0363720280d4..4cbf617efa7c 100644
> >>> --- a/fs/nfsd/export.c
> >>> +++ b/fs/nfsd/export.c
> >>> @@ -1124,7 +1124,8 @@ __be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp,
> >>>                   test_bit(XPT_PEER_AUTH, &xprt->xpt_flags))
> >>>                       goto ok;
> >>>       }
> >>> -     goto denied;
> >>> +     if (!may_bypass_gss)
> >>> +             goto denied;
> >>>
> >>>  ok:
> >>>       /* legacy gss-only clients are always OK: */
> >>> @@ -1142,15 +1143,6 @@ __be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp,
> >>>                       return nfs_ok;
> >>>       }
> >>>
> >>> -     /* If the compound op contains a spo_must_allowed op,
> >>> -      * it will be sent with integrity/protection which
> >>> -      * will have to be expressly allowed on mounts that
> >>> -      * don't support it
> >>> -      */
> >>> -
> >>> -     if (nfsd4_spo_must_allow(rqstp))
> >>> -             return nfs_ok;
> >>> -
> >>>       /* Some calls may be processed without authentication
> >>>        * on GSS exports. For example NFS2/3 calls on root
> >>>        * directory, see section 2.3.2 of rfc 2623.
> >>> @@ -1168,6 +1160,17 @@ __be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp,
> >>>               }
> >>>       }
> >>>
> >>> +     /* If the compound op contains a spo_must_allowed op,
> >>> +      * it will be sent with integrity/protection which
> >>> +      * will have to be expressly allowed on mounts that
> >>> +      * don't support it
> >>> +      */
> >>> +     /* This call must be done after the may_bypass_gss check.
> >>> +      * may_bypass_gss implies NLM(/MOUNT) and not 4.1
> >>> +      */
> >>> +     if (nfsd4_spo_must_allow(rqstp))
> >>> +             return nfs_ok;
> >>> +
> >>
> >> Comment about future work: This is technical debt (that's the 21st
> >> century term for spaghetti), logic that has accrued over time and is
> >> now therefore difficult to reason about. Would be nice to break
> >> check_nfsd_access() apart into "for NLM", "for NFS-legacy", and "for NFS
> >> with COMPOUND". For another day, perhaps.
> >>
> >>
> >>>  denied:
> >>>       return nfserr_wrongsec;
> >>>  }
> >>> diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
> >>> index 4021b047eb18..95d973136079 100644
> >>> --- a/fs/nfsd/vfs.c
> >>> +++ b/fs/nfsd/vfs.c
> >>> @@ -2600,6 +2600,10 @@ nfsd_permission(struct svc_cred *cred, struct svc_export *exp,
> >>
> >> More context shows there is an NFSD_MAY_OWNER_OVERRIDE check just
> >> before the new logic added by this patch:
> >>
> >>         if ((acc & NFSD_MAY_OWNER_OVERRIDE) &&
> >>
> >>>           uid_eq(inode->i_uid, current_fsuid()))
> >>>               return 0;
> >>>
> >>> +     /* If this is NLM, require read or ownership permissions */
> >>> +     if (acc & NFSD_MAY_NLM)
> >>> +             acc = NFSD_MAY_READ | NFSD_MAY_OWNER_OVERRIDE;
> >>> +
> >>
> >> So I'm wondering if this new test needs to come /before/ the existing
> >> MAY_OWNER_OVERRIDE check here? If not, the comment you add here might
> >> explain why it is correct to place the new logic afterwards.
> >
> > Why would you think this check should go before?
>
> Because this code is confusing.
>
> So, for NLM, MAY_OWNER_OVERRIDE is already set for the uid_eq check.
> The order of the new code is not consequential. But it still catches
> the eye.

I just re-checked the original code and the re(set)ing of acc was done
before the MAY_OWNER_OVERRIDE check. So changing my patch as you
suggest would be consistent with how things worked before. I will do
that.

> > NFS_MAY_OWNER_OVERRIDE is actually already set by nlm_fopen() when it
> > arrives in check_nfsd_access() .
>
> This code is /clearing/ the other flags too. That's a little subtle.
>
> The new comment should explain why only these two flags are needed for
> the subsequent code.

inode_permission() only care about READ, WRITE (EXEC)? NFSD_MAY_READ
is supposed to be the same as MAY_READ etc. Thus I believe passing
other values is of no consequence so to be honest I don't understand
why NFSD_MAY_OWNER_OVERRIDE is needed here. But what is of consequence
is (not) passing a NFSD_MAY_WRITE (which is what gets passed in by
nlm_fopen()).

> That is, explain not only why NFSD_MAY_READ is
> getting set, but also why NFSD_MAY_NLM and NFSD_MAY_BYPASS_GSS are
> getting cleared.
>
> Or, if clearing those flags was unintentional, make the code read:
>
>         acc |= NFSD_MAY_READ;
>
> I don't understand this code well enough to figure out why nlm_fopen()
> shouldn't just set NFSD_MAY_READ. Therefore: better code comment needed.

My comment came from the comment that was removed by commit
4cc9b9f2bf4df. I don't have a good understanding of the code to
provide a "better" comment.

nlm_fopen assigns the access based on the requested lock type. an
exclusive lock i'm assuming gets translated to the "write" =
NFSD_MAY_WRITE. But the the user might not have write access to the
file but still be allowed to request an exclusive lock on it, right?

@@ -2531,16 +2531,6 @@ nfsd_permission(struct svc_cred *cred, struct
svc_export *exp,
        if ((acc & NFSD_MAY_TRUNC) && IS_APPEND(inode))
                return nfserr_perm;

-       if (acc & NFSD_MAY_LOCK) {
-               /* If we cannot rely on authentication in NLM requests,
-                * just allow locks, otherwise require read permission, or
-                * ownership
-                */
-               if (exp->ex_flags & NFSEXP_NOAUTHNLM)
-                       return 0;
-               else
-                       acc = NFSD_MAY_READ | NFSD_MAY_OWNER_OVERRIDE;
-       }
        /*
         * The file owner always gets access permission for accesses that
         * would normally be checked at open time. This is to make

>
>
> > Prior to the 4cc9b9f2bf4df inode_permission() would get NFSD_MAY_READ
> > | NFSD_MAY_OWNER_OVERRIDE in access argument. After, it gets
> > NFSD_MAY_WRITE | NFSD_MAY_NLM | NFSD_MAY_OWNER_OVERRIDE |
> > NFSD_MAY_BYPASS_GSS. It made inode_permission() fail the call. Isn't
> > NLM asking for write permissions on the file for locking?
> >
> > My attempt was to return the code to previous functionality which
> > worked (and was only explicitly asking for read permissions on the
> > file).
>
> So the patch is reverting part of 4cc9b9f2bf4df; perhaps that should be
> called out in the patch description (up to you). However, the proposed
> fix doesn't make this code easier to understand, and that's probably my
> main quibble.
>
>
> > Unless you think more is needed here: I would resubmit with 3 patches
> > for each of the chunks and corresponding problems.
>
> Agreed, and I don't think I have any substantial change requests for the
> first two fixes.
>
>
> >>>       /* This assumes  NFSD_MAY_{READ,WRITE,EXEC} == MAY_{READ,WRITE,EXEC} */
> >>>       err = inode_permission(&nop_mnt_idmap, inode,
> >>>                              acc & (MAY_READ | MAY_WRITE | MAY_EXEC));
> >>
> >>
> >> --
> >> Chuck Lever
> >>
>
>
> --
> Chuck Lever
>






[Index of Archives]     [Linux Filesystem Development]     [Linux USB Development]     [Linux Media Development]     [Video for Linux]     [Linux NILFS]     [Linux Audio Users]     [Yosemite Info]     [Linux SCSI]

  Powered by Linux