Re: [PATCH] lockd: refcount NLM_SHARE access/deny modes

"Chuck Lever" <[email protected]>
Newsgroups gmane.linux.nfs
Message-ID <[email protected]>
On Tue, Jun 30, 2026, at 5:38 AM, Oscar Ou wrote:
> When an NFSv3/NLM client issues multiple NLM_SHARE calls from a single
> host for the same (file, owner) tuple, the current implementation
> overwrites the recorded access and deny modes with the latest pair.
> A subsequent NLM_UNSHARE then drops the entire entry, even if other
> grants were implicitly subsumed by the most recent SHARE.  This is
> particularly visible to Windows-style clients that map each open of
> a file to a distinct NLM_SHARE, all carrying the same NLM owner
> handle.

> diff --git a/fs/lockd/svcshare.c b/fs/lockd/svcshare.c
> index 5ac0ec25d62d..4a1a09c209ea 100644
> --- a/fs/lockd/svcshare.c
> +++ b/fs/lockd/svcshare.c
> @@ -25,6 +25,24 @@ nlm_cmp_owner(struct lockd_share *share, struct 
> xdr_netobj *oh)
>  	    && !memcmp(share->s_owner.data, oh->data, oh->len);
>  }
> 
> +/*
> + * Recompute s_access / s_mode as the union of all positive refcount
> + * buckets.  Caller must hold the per-file f_mutex.
> + */

Is the f_mutex statement here accurate, and the matching sentence in the
commit message:

  The per-file f_mutex held by callers in fs/lockd/svcsubs.c continues
  to serialise share manipulation, so the new arrays do not require
  their own locking.

nlm_lookup_file() in fs/lockd/svcsubs.c takes file->f_mutex only around
nlm_do_fopen() and drops it before returning:

  fs/lockd/svcsubs.c:nlm_lookup_file() {
        ...
                mutex_lock(&file->f_mutex);
                nfserr = nlm_do_fopen(rqstp, file, mode);
                mutex_unlock(&file->f_mutex);
        ...
  }

By the time nlmsvc_proc_share() / nlmsvc_proc_unshare() call into
nlmsvc_share_file() / nlmsvc_unshare_file(), f_mutex is no longer held.
What actually serialises these requests is that lockd runs a single
service thread (svc_set_num_threads(serv, 0, 1) in fs/lockd/svc.c), so
the conclusion that the arrays need no extra locking holds.  Would it be
worth pointing the comment and commit message at the single-threaded
service rather than f_mutex?

The loop starts at v = 1, so s_access_counts[0] and s_mode_counts[0] are
incremented and decremented but never read.  That is fine for computing
the union, since index 0 (fsa_NONE / fsm_DN) contributes no bits.  It
does mean the free decision keys on the recomputed s_access / s_mode
rather than on every bucket, so the commit message wording

  the entry is freed only when every bucket has reached zero.

is slightly stronger than the code: an entry can be freed while
s_access_counts[0] or s_mode_counts[0] is still non-zero.  Harmless
given NONE/DN enforce nothing, but the stated invariant and the bucket-0
storage do not quite line up.


> +static void nlm_recompute_share(struct lockd_share *share)
> +{
> +	u32 new_access = 0, new_mode = 0, v;
> +
> +	for (v = 1; v < LOCKD_FSH_NR; v++) {
> +		if (share->s_access_counts[v])
> +			new_access |= v;
> +		if (share->s_mode_counts[v])
> +			new_mode |= v;
> +	}
> +	share->s_access = new_access;
> +	share->s_mode = new_mode;
> +}
> +
>  /**
>   * nlmsvc_share_file - create a share
>   * @host: Network client peer
> @@ -64,12 +82,15 @@ nlmsvc_share_file(struct nlm_host *host, struct 
> nlm_file *file,
>  	share->s_host       = host;
>  	share->s_owner.data = ohdata;
>  	share->s_owner.len  = oh->len;
> +	memset(share->s_access_counts, 0, sizeof(share->s_access_counts));
> +	memset(share->s_mode_counts, 0, sizeof(share->s_mode_counts));
>  	share->s_next       = file->f_shares;
>  	file->f_shares      = share;
> 
>  update:
> -	share->s_access = access;
> -	share->s_mode = mode;
> +	share->s_access_counts[access]++;
> +	share->s_mode_counts[mode]++;
> +	nlm_recompute_share(share);
>  	return nlm_granted;
>  }
> 

A remote NLMv4 client can drive access or mode out of {0..3} here
and write past s_access_counts[] / s_mode_counts[]. There are one
or two other spots with the same issue.

Thus your patch can't be applied as written.

However, today is your lucky day: I have a patch that regenerates
the XDR decoders to prevent exactly this issue, which I will post
forthwith.

I'm wondering about a Fixes tag. This issue has been around since
1da177e4c3f4 ("Linux-2.6.12-rc2"), but the fix depends mechanically
on the xdrgen conversion, so it certainly will not apply cleanly to
kernels earlier than v7.1, and a proper fix will need both the
regeneration patch and this one to be applied.


-- 
Chuck Lever
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.