Skip to content

Commit e055e74

Browse files
neilbrowngregkh
authored andcommitted
lockd: fix TEST handling when not all permissions are available.
[ Upstream commit 0b47424 ] The F_GETLK fcntl can work with either read access or write access or both. It can query F_RDLCK and F_WRLCK locks in either case. However lockd currently treats F_GETLK similar to F_SETLK in that read access is required to query an F_RDLCK lock and write access is required to query a F_WRLCK lock. This is wrong and can cause problems - e.g. when qemu accesses a read-only (e.g. iso) filesystem image over NFS (though why it queries if it can get a write lock - I don't know. But it does, and this works with local filesystems). So we need TEST requests to be handled differently. To do this: - change nlm_do_fopen() to accept O_RDWR as a mode and in that case succeed if either a O_RDONLY or O_WRONLY file can be opened. - change nlm_lookup_file() to accept a mode argument from caller, instead of deducing base on lock time, and pass that on to nlm_do_fopen() - change nlm4svc_retrieve_args() and nlmsvc_retrieve_args() to detect TEST requests and pass O_RDWR as a mode to nlm_lookup_file, passing the same mode as before for other requests. Also set lock->fl.c.flc_file to whichever file is available for TEST requests. - change nlmsvc_testlock() to also not calculate the mode, but to use whatever was stored in lock->fl.c.flc_file. This behaviour of lockd - requesting O_WRONLY access to TEST for exclusive locks - has been present at least since git history began. However it was hidden until recently because knfsd ignored the access requested by lockd and required only READ access for all locking requests (unless the underlying filesystem provided an f_op->open function which checked access permissions). The commit mentioned in Fixes: below changed nfsd_permission() to NOT override the access request for LOCK requests and this exposed the bug that we are now fixing. Note that there is another issue that this patch does not address. The flock(.., LOCK_EX) call is permitted on a read-only file descriptor. Linux NFS maps this to NLM locking as whole-file byte-range locks. nfsd will see this as though it were fcntl( F_SETLK (F_WRLCK)) and will now require write access, which it might not be able to get. It is not clear if this is a problem in practice, or what the best solution might be. So no attempt is made to address it. Reported-by: Tj <tj.iam.tj@proton.me> Link: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1128861 Fixes: 4cc9b9f ("nfsd: refine and rename NFSD_MAY_LOCK") Reviewed-by: Jeff Layton <jlayton@kernel.org> Signed-off-by: NeilBrown <neil@brown.name> Signed-off-by: Chuck Lever <chuck.lever@oracle.com> Signed-off-by: Sasha Levin <sashal@kernel.org>
1 parent 671ec2e commit e055e74

5 files changed

Lines changed: 49 additions & 20 deletions

File tree

fs/lockd/svc4proc.c

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ nlm4svc_retrieve_args(struct svc_rqst *rqstp, struct nlm_args *argp,
2626
struct nlm_host *host = NULL;
2727
struct nlm_file *file = NULL;
2828
struct nlm_lock *lock = &argp->lock;
29+
bool is_test = (rqstp->rq_proc == NLMPROC_TEST ||
30+
rqstp->rq_proc == NLMPROC_TEST_MSG);
2931
__be32 error = 0;
3032

3133
/* nfsd callbacks must have been installed for this procedure */
@@ -46,15 +48,20 @@ nlm4svc_retrieve_args(struct svc_rqst *rqstp, struct nlm_args *argp,
4648
if (filp != NULL) {
4749
int mode = lock_to_openmode(&lock->fl);
4850

51+
if (is_test)
52+
mode = O_RDWR;
53+
4954
lock->fl.c.flc_flags = FL_POSIX;
5055

51-
error = nlm_lookup_file(rqstp, &file, lock);
56+
error = nlm_lookup_file(rqstp, &file, lock, mode);
5257
if (error)
5358
goto no_locks;
5459
*filp = file;
55-
5660
/* Set up the missing parts of the file_lock structure */
57-
lock->fl.c.flc_file = file->f_file[mode];
61+
if (is_test)
62+
lock->fl.c.flc_file = nlmsvc_file_file(file);
63+
else
64+
lock->fl.c.flc_file = file->f_file[mode];
5865
lock->fl.c.flc_pid = current->tgid;
5966
lock->fl.fl_start = (loff_t)lock->lock_start;
6067
lock->fl.fl_end = lock->lock_len ?

fs/lockd/svclock.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -611,7 +611,6 @@ nlmsvc_testlock(struct svc_rqst *rqstp, struct nlm_file *file,
611611
struct nlm_lock *conflock)
612612
{
613613
int error;
614-
int mode;
615614
__be32 ret;
616615

617616
dprintk("lockd: nlmsvc_testlock(%s/%ld, ty=%d, %Ld-%Ld)\n",
@@ -626,14 +625,13 @@ nlmsvc_testlock(struct svc_rqst *rqstp, struct nlm_file *file,
626625
goto out;
627626
}
628627

629-
mode = lock_to_openmode(&lock->fl);
630628
locks_init_lock(&conflock->fl);
631629
/* vfs_test_lock only uses start, end, and owner, but tests flc_file */
632630
conflock->fl.c.flc_file = lock->fl.c.flc_file;
633631
conflock->fl.fl_start = lock->fl.fl_start;
634632
conflock->fl.fl_end = lock->fl.fl_end;
635633
conflock->fl.c.flc_owner = lock->fl.c.flc_owner;
636-
error = vfs_test_lock(file->f_file[mode], &conflock->fl);
634+
error = vfs_test_lock(lock->fl.c.flc_file, &conflock->fl);
637635
if (error) {
638636
/* We can't currently deal with deferred test requests */
639637
if (error == FILE_LOCK_DEFERRED)

fs/lockd/svcproc.c

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ nlmsvc_retrieve_args(struct svc_rqst *rqstp, struct nlm_args *argp,
5555
struct nlm_host *host = NULL;
5656
struct nlm_file *file = NULL;
5757
struct nlm_lock *lock = &argp->lock;
58+
bool is_test = (rqstp->rq_proc == NLMPROC_TEST ||
59+
rqstp->rq_proc == NLMPROC_TEST_MSG);
5860
int mode;
5961
__be32 error = 0;
6062

@@ -70,15 +72,22 @@ nlmsvc_retrieve_args(struct svc_rqst *rqstp, struct nlm_args *argp,
7072

7173
/* Obtain file pointer. Not used by FREE_ALL call. */
7274
if (filp != NULL) {
73-
error = cast_status(nlm_lookup_file(rqstp, &file, lock));
75+
mode = lock_to_openmode(&lock->fl);
76+
77+
if (is_test)
78+
mode = O_RDWR;
79+
80+
error = cast_status(nlm_lookup_file(rqstp, &file, lock, mode));
7481
if (error != 0)
7582
goto no_locks;
7683
*filp = file;
7784

7885
/* Set up the missing parts of the file_lock structure */
79-
mode = lock_to_openmode(&lock->fl);
8086
lock->fl.c.flc_flags = FL_POSIX;
81-
lock->fl.c.flc_file = file->f_file[mode];
87+
if (is_test)
88+
lock->fl.c.flc_file = nlmsvc_file_file(file);
89+
else
90+
lock->fl.c.flc_file = file->f_file[mode];
8291
lock->fl.c.flc_pid = current->tgid;
8392
lock->fl.fl_lmops = &nlmsvc_lock_operations;
8493
nlmsvc_locks_init_private(&lock->fl, host, (pid_t)lock->svid);

fs/lockd/svcsubs.c

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,18 +82,35 @@ int lock_to_openmode(struct file_lock *lock)
8282
*
8383
* We have to make sure we have the right credential to open
8484
* the file.
85+
*
86+
* mode can be O_RDONLY(0), O_WRONLY(1) or O_RDWR(2). The latter
87+
* means success can be achieved with EITHER O_RDONLY or O_WRONLY.
88+
* It does NOT mean both read and write are required.
8589
*/
8690
static __be32 nlm_do_fopen(struct svc_rqst *rqstp,
8791
struct nlm_file *file, int mode)
8892
{
89-
struct file **fp = &file->f_file[mode];
90-
__be32 nfserr;
93+
__be32 nfserr = nlm_lck_denied_nolocks;
94+
__be32 deferred = 0;
95+
struct file **fp;
96+
int m;
9197

92-
if (*fp)
93-
return 0;
94-
nfserr = nlmsvc_ops->fopen(rqstp, &file->f_handle, fp, mode);
95-
if (nfserr)
96-
dprintk("lockd: open failed (error %d)\n", nfserr);
98+
for (m = O_RDONLY ; m <= O_WRONLY ; m++) {
99+
if (mode != O_RDWR && mode != m)
100+
continue;
101+
102+
fp = &file->f_file[m];
103+
if (*fp)
104+
return 0;
105+
nfserr = nlmsvc_ops->fopen(rqstp, &file->f_handle, fp, m);
106+
if (!nfserr)
107+
return 0;
108+
if (nfserr == nlm_drop_reply)
109+
deferred = nfserr;
110+
}
111+
if (deferred)
112+
return deferred;
113+
dprintk("lockd: open failed (error %d)\n", ntohl(nfserr));
97114
return nfserr;
98115
}
99116

@@ -103,17 +120,15 @@ static __be32 nlm_do_fopen(struct svc_rqst *rqstp,
103120
*/
104121
__be32
105122
nlm_lookup_file(struct svc_rqst *rqstp, struct nlm_file **result,
106-
struct nlm_lock *lock)
123+
struct nlm_lock *lock, int mode)
107124
{
108125
struct nlm_file *file;
109126
unsigned int hash;
110127
__be32 nfserr;
111-
int mode;
112128

113129
nlm_debug_print_fh("nlm_lookup_file", &lock->fh);
114130

115131
hash = file_hash(&lock->fh);
116-
mode = lock_to_openmode(&lock->fl);
117132

118133
/* Lock file table */
119134
mutex_lock(&nlm_file_mutex);

include/linux/lockd/lockd.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ void nlmsvc_locks_init_private(struct file_lock *, struct nlm_host *, pid_t);
293293
* File handling for the server personality
294294
*/
295295
__be32 nlm_lookup_file(struct svc_rqst *, struct nlm_file **,
296-
struct nlm_lock *);
296+
struct nlm_lock *, int);
297297
void nlm_release_file(struct nlm_file *);
298298
void nlmsvc_put_lockowner(struct nlm_lockowner *);
299299
void nlmsvc_release_lockowner(struct nlm_lock *);

0 commit comments

Comments
 (0)