]> exis.tech > repos - linux.git/commitdiff
Merge tag 'nfsd-6.19-2' of git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux
authorLinus Torvalds <torvalds@linux-foundation.org>
Wed, 31 Dec 2025 01:56:26 +0000 (17:56 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 31 Dec 2025 01:56:26 +0000 (17:56 -0800)
Pull nfsd fixes from Chuck Lever:
 "A set of NFSD fixes that arrived just a bit late for the 6.19 merge
  window.

  Regression fix:
   - Avoid unnecessarily breaking a timestamp delegation

  Stable fixes:
   - Fix a crasher in nlm4svc_proc_test()
   - Fix nfsd_file reference leak during write delegation
   - Fix error flow in client_states_open()"

* tag 'nfsd-6.19-2' of git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux:
  nfsd: Drop the client reference in client_states_open()
  nfsd: use ATTR_DELEG in nfsd4_finalize_deleg_timestamps()
  nfsd: fix nfsd_file reference leak in nfsd4_add_rdaccess_to_wrdeleg()
  lockd: fix vfs_test_lock() calls

1  2 
fs/locks.c
fs/nfsd/nfs4state.c

diff --combined fs/locks.c
index 9f565802a88cd34f79541fb156ef6326c0e199f1,bf5e0d05a026911ca1472dc13acfabcc56b9795b..e2036aa4bd3734be415296f9157d8f17166878aa
@@@ -585,7 -585,7 +585,7 @@@ static const struct lease_manager_opera
  /*
   * Initialize a lease, use the default lock manager operations
   */
 -static int lease_init(struct file *filp, int type, struct file_lease *fl)
 +static int lease_init(struct file *filp, unsigned int flags, int type, struct file_lease *fl)
  {
        if (assign_type(&fl->c, type) != 0)
                return -EINVAL;
        fl->c.flc_pid = current->tgid;
  
        fl->c.flc_file = filp;
 -      fl->c.flc_flags = FL_LEASE;
 +      fl->c.flc_flags = flags;
        fl->fl_lmops = &lease_manager_ops;
        return 0;
  }
  
  /* Allocate a file_lock initialised to this type of lease */
 -static struct file_lease *lease_alloc(struct file *filp, int type)
 +static struct file_lease *lease_alloc(struct file *filp, unsigned int flags, int type)
  {
        struct file_lease *fl = locks_alloc_lease();
        int error = -ENOMEM;
        if (fl == NULL)
                return ERR_PTR(error);
  
 -      error = lease_init(filp, type, fl);
 +      error = lease_init(filp, flags, type, fl);
        if (error) {
                locks_free_lease(fl);
                return ERR_PTR(error);
@@@ -1529,35 -1529,29 +1529,35 @@@ any_leases_conflict(struct inode *inode
  /**
   *    __break_lease   -       revoke all outstanding leases on file
   *    @inode: the inode of the file to return
 - *    @mode: O_RDONLY: break only write leases; O_WRONLY or O_RDWR:
 - *        break all leases
 - *    @type: FL_LEASE: break leases and delegations; FL_DELEG: break
 - *        only delegations
 + *    @flags: LEASE_BREAK_* flags
   *
   *    break_lease (inlined for speed) has checked there already is at least
   *    some kind of lock (maybe a lease) on this file.  Leases are broken on
 - *    a call to open() or truncate().  This function can sleep unless you
 - *    specified %O_NONBLOCK to your open().
 + *    a call to open() or truncate().  This function can block waiting for the
 + *    lease break unless you specify LEASE_BREAK_NONBLOCK.
   */
 -int __break_lease(struct inode *inode, unsigned int mode, unsigned int type)
 +int __break_lease(struct inode *inode, unsigned int flags)
  {
 -      int error = 0;
 -      struct file_lock_context *ctx;
        struct file_lease *new_fl, *fl, *tmp;
 +      struct file_lock_context *ctx;
        unsigned long break_time;
 -      int want_write = (mode & O_ACCMODE) != O_RDONLY;
 +      unsigned int type;
        LIST_HEAD(dispose);
 +      bool want_write = !(flags & LEASE_BREAK_OPEN_RDONLY);
 +      int error = 0;
  
 -      new_fl = lease_alloc(NULL, want_write ? F_WRLCK : F_RDLCK);
 +      if (flags & LEASE_BREAK_LEASE)
 +              type = FL_LEASE;
 +      else if (flags & LEASE_BREAK_DELEG)
 +              type = FL_DELEG;
 +      else if (flags & LEASE_BREAK_LAYOUT)
 +              type = FL_LAYOUT;
 +      else
 +              return -EINVAL;
 +
 +      new_fl = lease_alloc(NULL, type, want_write ? F_WRLCK : F_RDLCK);
        if (IS_ERR(new_fl))
                return PTR_ERR(new_fl);
 -      new_fl->c.flc_flags = type;
  
        /* typically we will check that ctx is non-NULL before calling */
        ctx = locks_inode_context(inode);
        if (list_empty(&ctx->flc_lease))
                goto out;
  
 -      if (mode & O_NONBLOCK) {
 +      if (flags & LEASE_BREAK_NONBLOCK) {
                trace_break_lease_noblock(inode, new_fl);
                error = -EWOULDBLOCK;
                goto out;
@@@ -1681,9 -1675,8 +1681,9 @@@ void lease_get_mtime(struct inode *inod
  EXPORT_SYMBOL(lease_get_mtime);
  
  /**
 - *    fcntl_getlease - Enquire what lease is currently active
 + *    __fcntl_getlease - Enquire what lease is currently active
   *    @filp: the file
 + *    @flavor: type of lease flags to check
   *
   *    The value returned by this function will be one of
   *    (if no lease break is pending):
   *    XXX: sfr & willy disagree over whether F_INPROGRESS
   *    should be returned to userspace.
   */
 -int fcntl_getlease(struct file *filp)
 +static int __fcntl_getlease(struct file *filp, unsigned int flavor)
  {
        struct file_lease *fl;
        struct inode *inode = file_inode(filp);
                list_for_each_entry(fl, &ctx->flc_lease, c.flc_list) {
                        if (fl->c.flc_file != filp)
                                continue;
 -                      type = target_leasetype(fl);
 +                      if (fl->c.flc_flags & flavor)
 +                              type = target_leasetype(fl);
                        break;
                }
                spin_unlock(&ctx->flc_lock);
        return type;
  }
  
 +int fcntl_getlease(struct file *filp)
 +{
 +      return __fcntl_getlease(filp, FL_LEASE);
 +}
 +
 +int fcntl_getdeleg(struct file *filp, struct delegation *deleg)
 +{
 +      if (deleg->d_flags != 0 || deleg->__pad != 0)
 +              return -EINVAL;
 +      deleg->d_type = __fcntl_getlease(filp, FL_DELEG);
 +      return 0;
 +}
 +
  /**
   * check_conflicting_open - see if the given file points to an inode that has
   *                        an existing open that would conflict with the
@@@ -1950,19 -1929,11 +1950,19 @@@ static int generic_delete_lease(struct 
  int generic_setlease(struct file *filp, int arg, struct file_lease **flp,
                        void **priv)
  {
 +      struct inode *inode = file_inode(filp);
 +
 +      if (!S_ISREG(inode->i_mode) && !S_ISDIR(inode->i_mode))
 +              return -EINVAL;
 +
        switch (arg) {
        case F_UNLCK:
                return generic_delete_lease(filp, *priv);
 -      case F_RDLCK:
        case F_WRLCK:
 +              if (S_ISDIR(inode->i_mode))
 +                      return -EINVAL;
 +              fallthrough;
 +      case F_RDLCK:
                if (!(*flp)->fl_lmops->lm_break) {
                        WARN_ON_ONCE(1);
                        return -ENOLCK;
@@@ -2047,6 -2018,8 +2047,6 @@@ vfs_setlease(struct file *filp, int arg
  
        if ((!vfsuid_eq_kuid(vfsuid, current_fsuid())) && !capable(CAP_LEASE))
                return -EACCES;
 -      if (!S_ISREG(inode->i_mode))
 -              return -EINVAL;
        error = security_file_lock(filp, arg);
        if (error)
                return error;
  }
  EXPORT_SYMBOL_GPL(vfs_setlease);
  
 -static int do_fcntl_add_lease(unsigned int fd, struct file *filp, int arg)
 +static int do_fcntl_add_lease(unsigned int fd, struct file *filp, unsigned int flavor, int arg)
  {
        struct file_lease *fl;
        struct fasync_struct *new;
        int error;
  
 -      fl = lease_alloc(filp, arg);
 +      fl = lease_alloc(filp, flavor, arg);
        if (IS_ERR(fl))
                return PTR_ERR(fl);
  
   */
  int fcntl_setlease(unsigned int fd, struct file *filp, int arg)
  {
 +      if (S_ISDIR(file_inode(filp)->i_mode))
 +              return -EINVAL;
 +
        if (arg == F_UNLCK)
                return vfs_setlease(filp, F_UNLCK, NULL, (void **)&filp);
 -      return do_fcntl_add_lease(fd, filp, arg);
 +      return do_fcntl_add_lease(fd, filp, FL_LEASE, arg);
 +}
 +
 +/**
 + *    fcntl_setdeleg  -       sets a delegation on an open file
 + *    @fd: open file descriptor
 + *    @filp: file pointer
 + *    @deleg: delegation request from userland
 + *
 + *    Call this fcntl to establish a delegation on the file.
 + *    Note that you also need to call %F_SETSIG to
 + *    receive a signal when the lease is broken.
 + */
 +int fcntl_setdeleg(unsigned int fd, struct file *filp, struct delegation *deleg)
 +{
 +      /* For now, no flags are supported */
 +      if (deleg->d_flags != 0 || deleg->__pad != 0)
 +              return -EINVAL;
 +
 +      if (deleg->d_type == F_UNLCK)
 +              return vfs_setlease(filp, F_UNLCK, NULL, (void **)&filp);
 +      return do_fcntl_add_lease(fd, filp, FL_DELEG, deleg->d_type);
  }
  
  /**
@@@ -2236,13 -2185,21 +2236,21 @@@ SYSCALL_DEFINE2(flock, unsigned int, fd
  /**
   * vfs_test_lock - test file byte range lock
   * @filp: The file to test lock for
-  * @fl: The lock to test; also used to hold result
+  * @fl: The byte-range in the file to test; also used to hold result
   *
+  * On entry, @fl does not contain a lock, but identifies a range (fl_start, fl_end)
+  * in the file (c.flc_file), and an owner (c.flc_owner) for whom existing locks
+  * should be ignored.  c.flc_type and c.flc_flags are ignored.
+  * Both fl_lmops and fl_ops in @fl must be NULL.
   * Returns -ERRNO on failure.  Indicates presence of conflicting lock by
-  * setting conf->fl_type to something other than F_UNLCK.
+  * setting fl->fl_type to something other than F_UNLCK.
+  *
+  * If vfs_test_lock() does find a lock and return it, the caller must
+  * use locks_free_lock() or locks_release_private() on the returned lock.
   */
  int vfs_test_lock(struct file *filp, struct file_lock *fl)
  {
+       WARN_ON_ONCE(fl->fl_ops || fl->fl_lmops);
        WARN_ON_ONCE(filp != fl->c.flc_file);
        if (filp->f_op->lock)
                return filp->f_op->lock(filp, F_GETLK, fl);
diff --combined fs/nfsd/nfs4state.c
index 808c24fb5c9a0b432d3271c051b409fcb75970cd,5b83cb33bf83d0aac539558efa43160d726710d2..a6e8a1f27133c7d9ec4a0c71b44ca6895304b83b
@@@ -1218,13 -1218,15 +1218,15 @@@ static void put_deleg_file(struct nfs4_
  
        if (nf)
                nfsd_file_put(nf);
-       if (rnf)
+       if (rnf) {
+               nfsd_file_put(rnf);
                nfs4_file_put_access(fp, NFS4_SHARE_ACCESS_READ);
+       }
  }
  
  static void nfsd4_finalize_deleg_timestamps(struct nfs4_delegation *dp, struct file *f)
  {
-       struct iattr ia = { .ia_valid = ATTR_ATIME | ATTR_CTIME | ATTR_MTIME };
+       struct iattr ia = { .ia_valid = ATTR_ATIME | ATTR_CTIME | ATTR_MTIME | ATTR_DELEG };
        struct inode *inode = file_inode(f);
        int ret;
  
@@@ -3097,8 -3099,10 +3099,10 @@@ static int client_states_open(struct in
                return -ENXIO;
  
        ret = seq_open(file, &states_seq_ops);
-       if (ret)
+       if (ret) {
+               drop_client(clp);
                return ret;
+       }
        s = file->private_data;
        s->private = clp;
        return 0;
@@@ -6231,10 -6235,14 +6235,14 @@@ nfsd4_add_rdaccess_to_wrdeleg(struct sv
                fp = stp->st_stid.sc_file;
                spin_lock(&fp->fi_lock);
                __nfs4_file_get_access(fp, NFS4_SHARE_ACCESS_READ);
-               fp = stp->st_stid.sc_file;
-               fp->fi_fds[O_RDONLY] = nf;
-               fp->fi_rdeleg_file = nf;
+               if (!fp->fi_fds[O_RDONLY]) {
+                       fp->fi_fds[O_RDONLY] = nf;
+                       nf = NULL;
+               }
+               fp->fi_rdeleg_file = nfsd_file_get(fp->fi_fds[O_RDONLY]);
                spin_unlock(&fp->fi_lock);
+               if (nf)
+                       nfsd_file_put(nf);
        }
        return true;
  }
@@@ -7832,8 -7840,7 +7840,8 @@@ nfsd4_delegreturn(struct svc_rqst *rqst
        __be32 status;
        struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
  
 -      if ((status = fh_verify(rqstp, &cstate->current_fh, S_IFREG, 0)))
 +      status = fh_verify(rqstp, &cstate->current_fh, 0, 0);
 +      if (status)
                return status;
  
        status = nfsd4_lookup_stateid(cstate, stateid, SC_TYPE_DELEG, SC_STATUS_REVOKED, &s, nn);
@@@ -9367,103 -9374,3 +9375,103 @@@ out_status
        nfs4_put_stid(&dp->dl_stid);
        return status;
  }
 +
 +/**
 + * nfsd_get_dir_deleg - attempt to get a directory delegation
 + * @cstate: compound state
 + * @gdd: GET_DIR_DELEGATION arg/resp structure
 + * @nf: nfsd_file opened on the directory
 + *
 + * Given a GET_DIR_DELEGATION request @gdd, attempt to acquire a delegation
 + * on the directory to which @nf refers. Note that this does not set up any
 + * sort of async notifications for the delegation.
 + */
 +struct nfs4_delegation *
 +nfsd_get_dir_deleg(struct nfsd4_compound_state *cstate,
 +                 struct nfsd4_get_dir_delegation *gdd,
 +                 struct nfsd_file *nf)
 +{
 +      struct nfs4_client *clp = cstate->clp;
 +      struct nfs4_delegation *dp;
 +      struct file_lease *fl;
 +      struct nfs4_file *fp, *rfp;
 +      int status = 0;
 +
 +      fp = nfsd4_alloc_file();
 +      if (!fp)
 +              return ERR_PTR(-ENOMEM);
 +
 +      nfsd4_file_init(&cstate->current_fh, fp);
 +
 +      rfp = nfsd4_file_hash_insert(fp, &cstate->current_fh);
 +      if (unlikely(!rfp)) {
 +              put_nfs4_file(fp);
 +              return ERR_PTR(-ENOMEM);
 +      }
 +
 +      if (rfp != fp) {
 +              put_nfs4_file(fp);
 +              fp = rfp;
 +      }
 +
 +      /* if this client already has one, return that it's unavailable */
 +      spin_lock(&state_lock);
 +      spin_lock(&fp->fi_lock);
 +      /* existing delegation? */
 +      if (nfs4_delegation_exists(clp, fp)) {
 +              status = -EAGAIN;
 +      } else if (!fp->fi_deleg_file) {
 +              fp->fi_deleg_file = nfsd_file_get(nf);
 +              fp->fi_delegees = 1;
 +      } else {
 +              ++fp->fi_delegees;
 +      }
 +      spin_unlock(&fp->fi_lock);
 +      spin_unlock(&state_lock);
 +
 +      if (status) {
 +              put_nfs4_file(fp);
 +              return ERR_PTR(status);
 +      }
 +
 +      /* Try to set up the lease */
 +      status = -ENOMEM;
 +      dp = alloc_init_deleg(clp, fp, NULL, NFS4_OPEN_DELEGATE_READ);
 +      if (!dp)
 +              goto out_delegees;
 +
 +      fl = nfs4_alloc_init_lease(dp);
 +      if (!fl)
 +              goto out_put_stid;
 +
 +      status = kernel_setlease(nf->nf_file,
 +                               fl->c.flc_type, &fl, NULL);
 +      if (fl)
 +              locks_free_lease(fl);
 +      if (status)
 +              goto out_put_stid;
 +
 +      /*
 +       * Now, try to hash it. This can fail if we race another nfsd task
 +       * trying to set a delegation on the same file. If that happens,
 +       * then just say UNAVAIL.
 +       */
 +      spin_lock(&state_lock);
 +      spin_lock(&clp->cl_lock);
 +      spin_lock(&fp->fi_lock);
 +      status = hash_delegation_locked(dp, fp);
 +      spin_unlock(&fp->fi_lock);
 +      spin_unlock(&clp->cl_lock);
 +      spin_unlock(&state_lock);
 +
 +      if (!status)
 +              return dp;
 +
 +      /* Something failed. Drop the lease and clean up the stid */
 +      kernel_setlease(fp->fi_deleg_file->nf_file, F_UNLCK, NULL, (void **)&dp);
 +out_put_stid:
 +      nfs4_put_stid(&dp->dl_stid);
 +out_delegees:
 +      put_deleg_file(fp);
 +      return ERR_PTR(status);
 +}