mirror of
https://github.com/torvalds/linux.git
synced 2026-10-07 19:16:02 +02:00
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. For example:
1. SHARE(access=RW, deny=W) -> entry [RW, deny W]
2. SHARE(access=R, deny=N) -> entry [R, deny N] (RW/W overwritten)
3. UNSHARE(access=R, deny=N) -> entry freed
4. UNSHARE(access=RW, deny=W) -> nothing to release
NLM has no duplicate reply cache, so both SHARE and UNSHARE handlers
must be idempotent under UDP retransmit.
Track each (access, deny) pair with a single bit in a u16 bitmap.
fsh_access and fsh_mode are each in {0..3}, so there are 16 possible
pairs; index = (access << 2) | deny. SHARE sets the bit, UNSHARE
clears it, both via idempotent bit operations. s_access and s_mode
are recomputed as the union of the (access, deny) values whose bit
is set, and the entry is freed once s_access_deny_bmap reaches zero.
NLM_UNSHARE gains the access and deny modes as arguments so the
correct bit can be cleared. The two callers in svcproc.c and
svc4proc.c are updated to forward the decoded values.
Signed-off-by: Oscar Ou <oscarou@synology.com>
Link: https://patch.msgid.link/20260703063856.2423734-1-oscarou@synology.com
Signed-off-by: Chuck Lever <cel@kernel.org>
152 lines
3.5 KiB
C
152 lines
3.5 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* linux/fs/lockd/svcshare.c
|
|
*
|
|
* Management of DOS shares.
|
|
*
|
|
* Copyright (C) 1996 Olaf Kirch <okir@monad.swb.de>
|
|
*/
|
|
|
|
#include <linux/time.h>
|
|
#include <linux/unistd.h>
|
|
#include <linux/string.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/sunrpc/clnt.h>
|
|
#include <linux/sunrpc/svc.h>
|
|
|
|
#include "lockd.h"
|
|
#include "share.h"
|
|
|
|
static inline int
|
|
nlm_cmp_owner(struct lockd_share *share, struct xdr_netobj *oh)
|
|
{
|
|
return share->s_owner.len == oh->len
|
|
&& !memcmp(share->s_owner.data, oh->data, oh->len);
|
|
}
|
|
|
|
/*
|
|
* Recompute s_access / s_mode as the union of every (access, deny) pair
|
|
* whose bit is currently set in s_access_deny_bmap.
|
|
*/
|
|
static void nlm_recompute_share(struct lockd_share *share)
|
|
{
|
|
u32 new_access = 0, new_mode = 0;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < 16; i++) {
|
|
if (share->s_access_deny_bmap & BIT(i)) {
|
|
new_access |= i >> 2;
|
|
new_mode |= i & 3;
|
|
}
|
|
}
|
|
share->s_access = new_access;
|
|
share->s_mode = new_mode;
|
|
}
|
|
|
|
/**
|
|
* nlmsvc_share_file - create a share
|
|
* @host: Network client peer
|
|
* @file: File to be shared
|
|
* @oh: Share owner handle
|
|
* @access: Requested access mode
|
|
* @mode: Requested file sharing mode
|
|
*
|
|
* Returns an NLM status code.
|
|
*/
|
|
__be32
|
|
nlmsvc_share_file(struct nlm_host *host, struct nlm_file *file,
|
|
struct xdr_netobj *oh, u32 access, u32 mode)
|
|
{
|
|
struct lockd_share *share;
|
|
u8 *ohdata;
|
|
|
|
if (nlmsvc_file_cannot_lock(file))
|
|
return nlm_lck_denied_nolocks;
|
|
|
|
for (share = file->f_shares; share; share = share->s_next) {
|
|
if (share->s_host == host && nlm_cmp_owner(share, oh))
|
|
goto update;
|
|
if ((access & share->s_mode) || (mode & share->s_access))
|
|
return nlm_lck_denied;
|
|
}
|
|
|
|
share = kmalloc(sizeof(*share) + oh->len, GFP_KERNEL);
|
|
if (share == NULL)
|
|
return nlm_lck_denied_nolocks;
|
|
|
|
/* Copy owner handle */
|
|
ohdata = (u8 *) (share + 1);
|
|
memcpy(ohdata, oh->data, oh->len);
|
|
|
|
share->s_file = file;
|
|
share->s_host = host;
|
|
share->s_owner.data = ohdata;
|
|
share->s_owner.len = oh->len;
|
|
share->s_access_deny_bmap = 0;
|
|
share->s_next = file->f_shares;
|
|
file->f_shares = share;
|
|
|
|
update:
|
|
share->s_access_deny_bmap |= LOCKD_FSH_BIT(access, mode);
|
|
nlm_recompute_share(share);
|
|
return nlm_granted;
|
|
}
|
|
|
|
/**
|
|
* nlmsvc_unshare_file - delete a share
|
|
* @host: Network client peer
|
|
* @file: File to be unshared
|
|
* @oh: Share owner handle
|
|
* @access: Access mode of the SHARE being released
|
|
* @mode: Deny mode of the SHARE being released
|
|
*
|
|
* Returns an NLM status code.
|
|
*/
|
|
__be32
|
|
nlmsvc_unshare_file(struct nlm_host *host, struct nlm_file *file,
|
|
struct xdr_netobj *oh, u32 access, u32 mode)
|
|
{
|
|
struct lockd_share *share, **shpp;
|
|
|
|
if (nlmsvc_file_cannot_lock(file))
|
|
return nlm_lck_denied_nolocks;
|
|
|
|
for (shpp = &file->f_shares; (share = *shpp) != NULL;
|
|
shpp = &share->s_next) {
|
|
if (share->s_host == host && nlm_cmp_owner(share, oh)) {
|
|
share->s_access_deny_bmap &= ~LOCKD_FSH_BIT(access, mode);
|
|
nlm_recompute_share(share);
|
|
if (!share->s_access_deny_bmap) {
|
|
*shpp = share->s_next;
|
|
kfree(share);
|
|
}
|
|
return nlm_granted;
|
|
}
|
|
}
|
|
|
|
/* X/Open spec says return success even if there was no
|
|
* corresponding share. */
|
|
return nlm_granted;
|
|
}
|
|
|
|
/*
|
|
* Traverse all shares for a given file, and delete
|
|
* those owned by the given (type of) host
|
|
*/
|
|
void nlmsvc_traverse_shares(struct nlm_host *host, struct nlm_file *file,
|
|
nlm_host_match_fn_t match)
|
|
{
|
|
struct lockd_share *share, **shpp;
|
|
|
|
shpp = &file->f_shares;
|
|
while ((share = *shpp) != NULL) {
|
|
if (match(share->s_host, host)) {
|
|
*shpp = share->s_next;
|
|
kfree(share);
|
|
continue;
|
|
}
|
|
shpp = &share->s_next;
|
|
}
|
|
}
|