Skip to content

Commit 4c91b67

Browse files
pcacjrsmfrench
authored andcommitted
smb: client: fix data corruption due to racy lease checks
Customer reported data corruption in some of their files. It turned out the client would end up calling cacheless IO functions while having RHW lease, bypassing the pagecache and then leaving gaps in the file while writing to it. It was related to concurrent opens changing the lease state while having writes in flight. Lease breaks and re-opens due to reconnect could also cause same issue. Fix this by serialising the lease updates with cifsInodeInfo::open_file_lock. When handling oplock break, make sure to use the downgraded oplock value rather than one in cifsInodeinfo as it could be changed concurrently. Reported-by: Frank Sorenson <sorenson@redhat.com> Signed-off-by: Paulo Alcantara (Red Hat) <pc@manguebit.org> Reviewed-by: David Howells <dhowells@redhat.com> Cc: linux-cifs@vger.kernel.org Signed-off-by: Steve French <stfrench@microsoft.com>
1 parent 3774289 commit 4c91b67

6 files changed

Lines changed: 110 additions & 55 deletions

File tree

fs/smb/client/cifsglob.h

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -515,8 +515,10 @@ struct smb_version_operations {
515515
/* check for STATUS_NETWORK_SESSION_EXPIRED */
516516
bool (*is_session_expired)(char *);
517517
/* send oplock break response */
518-
int (*oplock_response)(struct cifs_tcon *tcon, __u64 persistent_fid, __u64 volatile_fid,
519-
__u16 net_fid, struct cifsInodeInfo *cifs_inode);
518+
int (*oplock_response)(struct cifs_tcon *tcon, __u64 persistent_fid,
519+
__u64 volatile_fid, __u16 net_fid,
520+
struct cifsInodeInfo *cifs_inode,
521+
unsigned int oplock);
520522
/* query remote filesystem */
521523
int (*queryfs)(const unsigned int, struct cifs_tcon *,
522524
const char *, struct cifs_sb_info *, struct kstatfs *);
@@ -1531,10 +1533,6 @@ int cifs_file_set_size(const unsigned int xid, struct dentry *dentry,
15311533
#define CIFS_CACHE_RW_FLG (CIFS_CACHE_READ_FLG | CIFS_CACHE_WRITE_FLG)
15321534
#define CIFS_CACHE_RHW_FLG (CIFS_CACHE_RW_FLG | CIFS_CACHE_HANDLE_FLG)
15331535

1534-
#define CIFS_CACHE_READ(cinode) ((cinode->oplock & CIFS_CACHE_READ_FLG) || (CIFS_SB(cinode->netfs.inode.i_sb)->mnt_cifs_flags & CIFS_MOUNT_RO_CACHE))
1535-
#define CIFS_CACHE_HANDLE(cinode) (cinode->oplock & CIFS_CACHE_HANDLE_FLG)
1536-
#define CIFS_CACHE_WRITE(cinode) ((cinode->oplock & CIFS_CACHE_WRITE_FLG) || (CIFS_SB(cinode->netfs.inode.i_sb)->mnt_cifs_flags & CIFS_MOUNT_RW_CACHE))
1537-
15381536
/*
15391537
* One of these for each file inode
15401538
*/
@@ -2312,4 +2310,30 @@ static inline void cifs_requeue_server_reconn(struct TCP_Server_Info *server)
23122310
queue_delayed_work(cifsiod_wq, &server->reconnect, delay * HZ);
23132311
}
23142312

2313+
static inline bool __cifs_cache_state_check(struct cifsInodeInfo *cinode,
2314+
unsigned int oplock_flags,
2315+
unsigned int sb_flags)
2316+
{
2317+
struct cifs_sb_info *cifs_sb = CIFS_SB(cinode->netfs.inode.i_sb);
2318+
unsigned int oplock = READ_ONCE(cinode->oplock);
2319+
unsigned int sflags = cifs_sb->mnt_cifs_flags;
2320+
2321+
return (oplock & oplock_flags) || (sflags & sb_flags);
2322+
}
2323+
2324+
#define CIFS_CACHE_READ(cinode) \
2325+
__cifs_cache_state_check(cinode, CIFS_CACHE_READ_FLG, \
2326+
CIFS_MOUNT_RO_CACHE)
2327+
#define CIFS_CACHE_HANDLE(cinode) \
2328+
__cifs_cache_state_check(cinode, CIFS_CACHE_HANDLE_FLG, 0)
2329+
#define CIFS_CACHE_WRITE(cinode) \
2330+
__cifs_cache_state_check(cinode, CIFS_CACHE_WRITE_FLG, \
2331+
CIFS_MOUNT_RW_CACHE)
2332+
2333+
static inline void cifs_reset_oplock(struct cifsInodeInfo *cinode)
2334+
{
2335+
scoped_guard(spinlock, &cinode->open_file_lock)
2336+
WRITE_ONCE(cinode->oplock, 0);
2337+
}
2338+
23152339
#endif /* _CIFS_GLOB_H */

fs/smb/client/file.c

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -731,14 +731,14 @@ struct cifsFileInfo *cifs_new_fileinfo(struct cifs_fid *fid, struct file *file,
731731
oplock = fid->pending_open->oplock;
732732
list_del(&fid->pending_open->olist);
733733

734-
fid->purge_cache = false;
735-
server->ops->set_fid(cfile, fid, oplock);
736-
737734
list_add(&cfile->tlist, &tcon->openFileList);
738735
atomic_inc(&tcon->num_local_opens);
739736

740737
/* if readable file instance put first in list*/
741738
spin_lock(&cinode->open_file_lock);
739+
fid->purge_cache = false;
740+
server->ops->set_fid(cfile, fid, oplock);
741+
742742
if (file->f_mode & FMODE_READ)
743743
list_add(&cfile->flist, &cinode->openFileList);
744744
else
@@ -1410,7 +1410,8 @@ cifs_reopen_file(struct cifsFileInfo *cfile, bool can_flush)
14101410
oplock = 0;
14111411
}
14121412

1413-
server->ops->set_fid(cfile, &cfile->fid, oplock);
1413+
scoped_guard(spinlock, &cinode->open_file_lock)
1414+
server->ops->set_fid(cfile, &cfile->fid, oplock);
14141415
if (oparms.reconnect)
14151416
cifs_relock_file(cfile);
14161417

@@ -1437,11 +1438,11 @@ smb2_can_defer_close(struct inode *inode, struct cifs_deferred_close *dclose)
14371438
{
14381439
struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
14391440
struct cifsInodeInfo *cinode = CIFS_I(inode);
1441+
unsigned int oplock = READ_ONCE(cinode->oplock);
14401442

1441-
return (cifs_sb->ctx->closetimeo && cinode->lease_granted && dclose &&
1442-
(cinode->oplock == CIFS_CACHE_RHW_FLG ||
1443-
cinode->oplock == CIFS_CACHE_RH_FLG) &&
1444-
!test_bit(CIFS_INO_CLOSE_ON_LOCK, &cinode->flags));
1443+
return cifs_sb->ctx->closetimeo && cinode->lease_granted && dclose &&
1444+
(oplock == CIFS_CACHE_RHW_FLG || oplock == CIFS_CACHE_RH_FLG) &&
1445+
!test_bit(CIFS_INO_CLOSE_ON_LOCK, &cinode->flags);
14451446

14461447
}
14471448

@@ -2371,7 +2372,7 @@ cifs_setlk(struct file *file, struct file_lock *flock, __u32 type,
23712372
cifs_zap_mapping(inode);
23722373
cifs_dbg(FYI, "Set no oplock for inode=%p due to mand locks\n",
23732374
inode);
2374-
CIFS_I(inode)->oplock = 0;
2375+
cifs_reset_oplock(CIFS_I(inode));
23752376
}
23762377

23772378
rc = server->ops->mand_lock(xid, cfile, flock->fl_start, length,
@@ -2930,7 +2931,7 @@ cifs_strict_writev(struct kiocb *iocb, struct iov_iter *from)
29302931
cifs_zap_mapping(inode);
29312932
cifs_dbg(FYI, "Set Oplock/Lease to NONE for inode=%p after write\n",
29322933
inode);
2933-
cinode->oplock = 0;
2934+
cifs_reset_oplock(cinode);
29342935
}
29352936
out:
29362937
cifs_put_writer(cinode);
@@ -2966,7 +2967,7 @@ ssize_t cifs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
29662967
cifs_dbg(FYI,
29672968
"Set no oplock for inode=%p after a write operation\n",
29682969
inode);
2969-
cinode->oplock = 0;
2970+
cifs_reset_oplock(cinode);
29702971
}
29712972
return written;
29722973
}
@@ -3154,9 +3155,11 @@ void cifs_oplock_break(struct work_struct *work)
31543155
struct super_block *sb = inode->i_sb;
31553156
struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
31563157
struct cifsInodeInfo *cinode = CIFS_I(inode);
3158+
bool cache_read, cache_write, cache_handle;
31573159
struct cifs_tcon *tcon;
31583160
struct TCP_Server_Info *server;
31593161
struct tcon_link *tlink;
3162+
unsigned int oplock;
31603163
int rc = 0;
31613164
bool purge_cache = false, oplock_break_cancelled;
31623165
__u64 persistent_fid, volatile_fid;
@@ -3177,29 +3180,40 @@ void cifs_oplock_break(struct work_struct *work)
31773180
tcon = tlink_tcon(tlink);
31783181
server = tcon->ses->server;
31793182

3180-
server->ops->downgrade_oplock(server, cinode, cfile->oplock_level,
3181-
cfile->oplock_epoch, &purge_cache);
3183+
scoped_guard(spinlock, &cinode->open_file_lock) {
3184+
unsigned int sbflags = cifs_sb->mnt_cifs_flags;
3185+
3186+
server->ops->downgrade_oplock(server, cinode, cfile->oplock_level,
3187+
cfile->oplock_epoch, &purge_cache);
3188+
oplock = READ_ONCE(cinode->oplock);
3189+
cache_read = (oplock & CIFS_CACHE_READ_FLG) ||
3190+
(sbflags & CIFS_MOUNT_RO_CACHE);
3191+
cache_write = (oplock & CIFS_CACHE_WRITE_FLG) ||
3192+
(sbflags & CIFS_MOUNT_RW_CACHE);
3193+
cache_handle = oplock & CIFS_CACHE_HANDLE_FLG;
3194+
}
31823195

3183-
if (!CIFS_CACHE_WRITE(cinode) && CIFS_CACHE_READ(cinode) &&
3184-
cifs_has_mand_locks(cinode)) {
3196+
if (!cache_write && cache_read && cifs_has_mand_locks(cinode)) {
31853197
cifs_dbg(FYI, "Reset oplock to None for inode=%p due to mand locks\n",
31863198
inode);
3187-
cinode->oplock = 0;
3199+
cifs_reset_oplock(cinode);
3200+
oplock = 0;
3201+
cache_read = cache_write = cache_handle = false;
31883202
}
31893203

31903204
if (S_ISREG(inode->i_mode)) {
3191-
if (CIFS_CACHE_READ(cinode))
3205+
if (cache_read)
31923206
break_lease(inode, O_RDONLY);
31933207
else
31943208
break_lease(inode, O_WRONLY);
31953209
rc = filemap_fdatawrite(inode->i_mapping);
3196-
if (!CIFS_CACHE_READ(cinode) || purge_cache) {
3210+
if (!cache_read || purge_cache) {
31973211
rc = filemap_fdatawait(inode->i_mapping);
31983212
mapping_set_error(inode->i_mapping, rc);
31993213
cifs_zap_mapping(inode);
32003214
}
32013215
cifs_dbg(FYI, "Oplock flush inode %p rc %d\n", inode, rc);
3202-
if (CIFS_CACHE_WRITE(cinode))
3216+
if (cache_write)
32033217
goto oplock_break_ack;
32043218
}
32053219

@@ -3214,7 +3228,7 @@ void cifs_oplock_break(struct work_struct *work)
32143228
* So, new open will not use cached handle.
32153229
*/
32163230

3217-
if (!CIFS_CACHE_HANDLE(cinode) && !list_empty(&cinode->deferred_closes))
3231+
if (!cache_handle && !list_empty(&cinode->deferred_closes))
32183232
cifs_close_deferred_file(cinode);
32193233

32203234
persistent_fid = cfile->fid.persistent_fid;
@@ -3232,7 +3246,8 @@ void cifs_oplock_break(struct work_struct *work)
32323246
if (!oplock_break_cancelled && !list_empty(&cinode->openFileList)) {
32333247
spin_unlock(&cinode->open_file_lock);
32343248
rc = server->ops->oplock_response(tcon, persistent_fid,
3235-
volatile_fid, net_fid, cinode);
3249+
volatile_fid, net_fid,
3250+
cinode, oplock);
32363251
cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
32373252
} else
32383253
spin_unlock(&cinode->open_file_lock);

fs/smb/client/smb1ops.c

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ cifs_downgrade_oplock(struct TCP_Server_Info *server,
395395
struct cifsInodeInfo *cinode, __u32 oplock,
396396
__u16 epoch, bool *purge_cache)
397397
{
398+
lockdep_assert_held(&cinode->open_file_lock);
398399
cifs_set_oplock_level(cinode, oplock);
399400
}
400401

@@ -894,6 +895,9 @@ static void
894895
cifs_set_fid(struct cifsFileInfo *cfile, struct cifs_fid *fid, __u32 oplock)
895896
{
896897
struct cifsInodeInfo *cinode = CIFS_I(d_inode(cfile->dentry));
898+
899+
lockdep_assert_held(&cinode->open_file_lock);
900+
897901
cfile->fid.netfid = fid->netfid;
898902
cifs_set_oplock_level(cinode, oplock);
899903
cinode->can_cache_brlcks = CIFS_CACHE_WRITE(cinode);
@@ -1139,12 +1143,16 @@ cifs_close_dir(const unsigned int xid, struct cifs_tcon *tcon,
11391143
return CIFSFindClose(xid, tcon, fid->netfid);
11401144
}
11411145

1142-
static int
1143-
cifs_oplock_response(struct cifs_tcon *tcon, __u64 persistent_fid,
1144-
__u64 volatile_fid, __u16 net_fid, struct cifsInodeInfo *cinode)
1146+
static int cifs_oplock_response(struct cifs_tcon *tcon, __u64 persistent_fid,
1147+
__u64 volatile_fid, __u16 net_fid,
1148+
struct cifsInodeInfo *cinode, unsigned int oplock)
11451149
{
1150+
unsigned int sbflags = CIFS_SB(cinode->netfs.inode.i_sb)->mnt_cifs_flags;
1151+
__u8 op;
1152+
1153+
op = !!((oplock & CIFS_CACHE_READ_FLG) || (sbflags & CIFS_MOUNT_RO_CACHE));
11461154
return CIFSSMBLock(0, tcon, net_fid, current->tgid, 0, 0, 0, 0,
1147-
LOCKING_ANDX_OPLOCK_RELEASE, false, CIFS_CACHE_READ(cinode) ? 1 : 0);
1155+
LOCKING_ANDX_OPLOCK_RELEASE, false, op);
11481156
}
11491157

11501158
static int

fs/smb/client/smb2misc.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -484,16 +484,16 @@ cifs_convert_path_to_utf16(const char *from, struct cifs_sb_info *cifs_sb)
484484
return to;
485485
}
486486

487-
__le32
488-
smb2_get_lease_state(struct cifsInodeInfo *cinode)
487+
__le32 smb2_get_lease_state(struct cifsInodeInfo *cinode, unsigned int oplock)
489488
{
489+
unsigned int sbflags = CIFS_SB(cinode->netfs.inode.i_sb)->mnt_cifs_flags;
490490
__le32 lease = 0;
491491

492-
if (CIFS_CACHE_WRITE(cinode))
492+
if ((oplock & CIFS_CACHE_WRITE_FLG) || (sbflags & CIFS_MOUNT_RW_CACHE))
493493
lease |= SMB2_LEASE_WRITE_CACHING_LE;
494-
if (CIFS_CACHE_HANDLE(cinode))
494+
if (oplock & CIFS_CACHE_HANDLE_FLG)
495495
lease |= SMB2_LEASE_HANDLE_CACHING_LE;
496-
if (CIFS_CACHE_READ(cinode))
496+
if ((oplock & CIFS_CACHE_READ_FLG) || (sbflags & CIFS_MOUNT_RO_CACHE))
497497
lease |= SMB2_LEASE_READ_CACHING_LE;
498498
return lease;
499499
}

fs/smb/client/smb2ops.c

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,6 +1460,8 @@ smb2_set_fid(struct cifsFileInfo *cfile, struct cifs_fid *fid, __u32 oplock)
14601460
struct cifsInodeInfo *cinode = CIFS_I(d_inode(cfile->dentry));
14611461
struct TCP_Server_Info *server = tlink_tcon(cfile->tlink)->ses->server;
14621462

1463+
lockdep_assert_held(&cinode->open_file_lock);
1464+
14631465
cfile->fid.persistent_fid = fid->persistent_fid;
14641466
cfile->fid.volatile_fid = fid->volatile_fid;
14651467
cfile->fid.access = fid->access;
@@ -2684,16 +2686,19 @@ smb2_is_network_name_deleted(char *buf, struct TCP_Server_Info *server)
26842686
return false;
26852687
}
26862688

2687-
static int
2688-
smb2_oplock_response(struct cifs_tcon *tcon, __u64 persistent_fid,
2689-
__u64 volatile_fid, __u16 net_fid, struct cifsInodeInfo *cinode)
2689+
static int smb2_oplock_response(struct cifs_tcon *tcon, __u64 persistent_fid,
2690+
__u64 volatile_fid, __u16 net_fid,
2691+
struct cifsInodeInfo *cinode, unsigned int oplock)
26902692
{
2693+
unsigned int sbflags = CIFS_SB(cinode->netfs.inode.i_sb)->mnt_cifs_flags;
2694+
__u8 op;
2695+
26912696
if (tcon->ses->server->capabilities & SMB2_GLOBAL_CAP_LEASING)
26922697
return SMB2_lease_break(0, tcon, cinode->lease_key,
2693-
smb2_get_lease_state(cinode));
2698+
smb2_get_lease_state(cinode, oplock));
26942699

2695-
return SMB2_oplock_break(0, tcon, persistent_fid, volatile_fid,
2696-
CIFS_CACHE_READ(cinode) ? 1 : 0);
2700+
op = !!((oplock & CIFS_CACHE_READ_FLG) || (sbflags & CIFS_MOUNT_RO_CACHE));
2701+
return SMB2_oplock_break(0, tcon, persistent_fid, volatile_fid, op);
26972702
}
26982703

26992704
void
@@ -4053,6 +4058,7 @@ smb2_downgrade_oplock(struct TCP_Server_Info *server,
40534058
struct cifsInodeInfo *cinode, __u32 oplock,
40544059
__u16 epoch, bool *purge_cache)
40554060
{
4061+
lockdep_assert_held(&cinode->open_file_lock);
40564062
server->ops->set_oplock_level(cinode, oplock, 0, NULL);
40574063
}
40584064

@@ -4093,19 +4099,19 @@ smb2_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock,
40934099
if (oplock == SMB2_OPLOCK_LEVEL_NOCHANGE)
40944100
return;
40954101
if (oplock == SMB2_OPLOCK_LEVEL_BATCH) {
4096-
cinode->oplock = CIFS_CACHE_RHW_FLG;
4102+
WRITE_ONCE(cinode->oplock, CIFS_CACHE_RHW_FLG);
40974103
cifs_dbg(FYI, "Batch Oplock granted on inode %p\n",
40984104
&cinode->netfs.inode);
40994105
} else if (oplock == SMB2_OPLOCK_LEVEL_EXCLUSIVE) {
4100-
cinode->oplock = CIFS_CACHE_RW_FLG;
4106+
WRITE_ONCE(cinode->oplock, CIFS_CACHE_RW_FLG);
41014107
cifs_dbg(FYI, "Exclusive Oplock granted on inode %p\n",
41024108
&cinode->netfs.inode);
41034109
} else if (oplock == SMB2_OPLOCK_LEVEL_II) {
4104-
cinode->oplock = CIFS_CACHE_READ_FLG;
4110+
WRITE_ONCE(cinode->oplock, CIFS_CACHE_READ_FLG);
41054111
cifs_dbg(FYI, "Level II Oplock granted on inode %p\n",
41064112
&cinode->netfs.inode);
41074113
} else
4108-
cinode->oplock = 0;
4114+
WRITE_ONCE(cinode->oplock, 0);
41094115
}
41104116

41114117
static void
@@ -4140,7 +4146,7 @@ smb21_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock,
41404146
if (!new_oplock)
41414147
strscpy(message, "None");
41424148

4143-
cinode->oplock = new_oplock;
4149+
WRITE_ONCE(cinode->oplock, new_oplock);
41444150
cifs_dbg(FYI, "%s Lease granted on inode %p\n", message,
41454151
&cinode->netfs.inode);
41464152
}
@@ -4149,30 +4155,32 @@ static void
41494155
smb3_set_oplock_level(struct cifsInodeInfo *cinode, __u32 oplock,
41504156
__u16 epoch, bool *purge_cache)
41514157
{
4152-
unsigned int old_oplock = cinode->oplock;
4158+
unsigned int old_oplock = READ_ONCE(cinode->oplock);
4159+
unsigned int new_oplock;
41534160

41544161
smb21_set_oplock_level(cinode, oplock, epoch, purge_cache);
4162+
new_oplock = READ_ONCE(cinode->oplock);
41554163

41564164
if (purge_cache) {
41574165
*purge_cache = false;
41584166
if (old_oplock == CIFS_CACHE_READ_FLG) {
4159-
if (cinode->oplock == CIFS_CACHE_READ_FLG &&
4167+
if (new_oplock == CIFS_CACHE_READ_FLG &&
41604168
(epoch - cinode->epoch > 0))
41614169
*purge_cache = true;
4162-
else if (cinode->oplock == CIFS_CACHE_RH_FLG &&
4170+
else if (new_oplock == CIFS_CACHE_RH_FLG &&
41634171
(epoch - cinode->epoch > 1))
41644172
*purge_cache = true;
4165-
else if (cinode->oplock == CIFS_CACHE_RHW_FLG &&
4173+
else if (new_oplock == CIFS_CACHE_RHW_FLG &&
41664174
(epoch - cinode->epoch > 1))
41674175
*purge_cache = true;
4168-
else if (cinode->oplock == 0 &&
4176+
else if (new_oplock == 0 &&
41694177
(epoch - cinode->epoch > 0))
41704178
*purge_cache = true;
41714179
} else if (old_oplock == CIFS_CACHE_RH_FLG) {
4172-
if (cinode->oplock == CIFS_CACHE_RH_FLG &&
4180+
if (new_oplock == CIFS_CACHE_RH_FLG &&
41734181
(epoch - cinode->epoch > 0))
41744182
*purge_cache = true;
4175-
else if (cinode->oplock == CIFS_CACHE_RHW_FLG &&
4183+
else if (new_oplock == CIFS_CACHE_RHW_FLG &&
41764184
(epoch - cinode->epoch > 1))
41774185
*purge_cache = true;
41784186
}

fs/smb/client/smb2proto.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ struct mid_q_entry *smb2_setup_async_request(struct TCP_Server_Info *server,
4242
struct smb_rqst *rqst);
4343
struct cifs_tcon *smb2_find_smb_tcon(struct TCP_Server_Info *server,
4444
__u64 ses_id, __u32 tid);
45-
__le32 smb2_get_lease_state(struct cifsInodeInfo *cinode);
45+
__le32 smb2_get_lease_state(struct cifsInodeInfo *cinode, unsigned int oplock);
4646
bool smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server);
4747
int smb3_handle_read_data(struct TCP_Server_Info *server,
4848
struct mid_q_entry *mid);

0 commit comments

Comments
 (0)