Skip to content

Commit bb8c6de

Browse files
author
CKI KWF Bot
committed
Merge: smb: client: fix UAF when handling cached directory handles [rhel-9.8]
MR: https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-9/-/merge_requests/7585 - fix UAF when handling cached directory handles JIRA: https://issues.redhat.com/browse/RHEL-114699 Signed-off-by: Paulo Alcantara <[email protected]> Approved-by: Jay Shin <[email protected]> Approved-by: Scott Mayhew <[email protected]> Approved-by: Olga Kornievskaia <[email protected]> Approved-by: David Howells <[email protected]> Approved-by: CKI KWF Bot <[email protected]> Merged-by: CKI GitLab Kmaint Pipeline Bot <[email protected]>
2 parents 4cd8546 + 1efde04 commit bb8c6de

File tree

13 files changed

+331
-105
lines changed

13 files changed

+331
-105
lines changed

fs/smb/client/cached_dir.c

Lines changed: 49 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,8 @@ static struct cached_fid *find_or_create_cached_dir(struct cached_fids *cfids,
3636
* fully cached or it may be in the process of
3737
* being deleted due to a lease break.
3838
*/
39-
if (!cfid->time || !cfid->has_lease) {
39+
if (!is_valid_cached_dir(cfid))
4040
return NULL;
41-
}
4241
kref_get(&cfid->refcount);
4342
return cfid;
4443
}
@@ -193,7 +192,7 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
193192
* Otherwise, it is either a new entry or laundromat worker removed it
194193
* from @cfids->entries. Caller will put last reference if the latter.
195194
*/
196-
if (cfid->has_lease && cfid->time) {
195+
if (is_valid_cached_dir(cfid)) {
197196
cfid->last_access_time = jiffies;
198197
spin_unlock(&cfids->cfid_list_lock);
199198
*ret_cfid = cfid;
@@ -232,7 +231,7 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
232231
list_for_each_entry(parent_cfid, &cfids->entries, entry) {
233232
if (parent_cfid->dentry == dentry->d_parent) {
234233
cifs_dbg(FYI, "found a parent cached file handle\n");
235-
if (parent_cfid->has_lease && parent_cfid->time) {
234+
if (is_valid_cached_dir(parent_cfid)) {
236235
lease_flags
237236
|= SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE;
238237
memcpy(pfid->parent_lease_key,
@@ -388,11 +387,11 @@ int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,
388387
* lease. Release one here, and the second below.
389388
*/
390389
cfid->has_lease = false;
391-
kref_put(&cfid->refcount, smb2_close_cached_fid);
390+
close_cached_dir(cfid);
392391
}
393392
spin_unlock(&cfids->cfid_list_lock);
394393

395-
kref_put(&cfid->refcount, smb2_close_cached_fid);
394+
close_cached_dir(cfid);
396395
} else {
397396
*ret_cfid = cfid;
398397
atomic_inc(&tcon->num_remote_opens);
@@ -416,12 +415,18 @@ int open_cached_dir_by_dentry(struct cifs_tcon *tcon,
416415
if (cfids == NULL)
417416
return -EOPNOTSUPP;
418417

418+
if (!dentry)
419+
return -ENOENT;
420+
419421
spin_lock(&cfids->cfid_list_lock);
420422
list_for_each_entry(cfid, &cfids->entries, entry) {
421-
if (dentry && cfid->dentry == dentry) {
423+
if (cfid->dentry == dentry) {
424+
if (!is_valid_cached_dir(cfid))
425+
break;
422426
cifs_dbg(FYI, "found a cached file handle by dentry\n");
423427
kref_get(&cfid->refcount);
424428
*ret_cfid = cfid;
429+
cfid->last_access_time = jiffies;
425430
spin_unlock(&cfids->cfid_list_lock);
426431
return 0;
427432
}
@@ -432,12 +437,14 @@ int open_cached_dir_by_dentry(struct cifs_tcon *tcon,
432437

433438
static void
434439
smb2_close_cached_fid(struct kref *ref)
440+
__releases(&cfid->cfids->cfid_list_lock)
435441
{
436442
struct cached_fid *cfid = container_of(ref, struct cached_fid,
437443
refcount);
438444
int rc;
439445

440-
spin_lock(&cfid->cfids->cfid_list_lock);
446+
lockdep_assert_held(&cfid->cfids->cfid_list_lock);
447+
441448
if (cfid->on_list) {
442449
list_del(&cfid->entry);
443450
cfid->on_list = false;
@@ -472,7 +479,7 @@ void drop_cached_dir_by_name(const unsigned int xid, struct cifs_tcon *tcon,
472479
spin_lock(&cfid->cfids->cfid_list_lock);
473480
if (cfid->has_lease) {
474481
cfid->has_lease = false;
475-
kref_put(&cfid->refcount, smb2_close_cached_fid);
482+
close_cached_dir(cfid);
476483
}
477484
spin_unlock(&cfid->cfids->cfid_list_lock);
478485
close_cached_dir(cfid);
@@ -481,7 +488,7 @@ void drop_cached_dir_by_name(const unsigned int xid, struct cifs_tcon *tcon,
481488

482489
void close_cached_dir(struct cached_fid *cfid)
483490
{
484-
kref_put(&cfid->refcount, smb2_close_cached_fid);
491+
kref_put_lock(&cfid->refcount, smb2_close_cached_fid, &cfid->cfids->cfid_list_lock);
485492
}
486493

487494
/*
@@ -521,10 +528,9 @@ void close_all_cached_dirs(struct cifs_sb_info *cifs_sb)
521528
spin_unlock(&cifs_sb->tlink_tree_lock);
522529
goto done;
523530
}
524-
spin_lock(&cfid->fid_lock);
531+
525532
tmp_list->dentry = cfid->dentry;
526533
cfid->dentry = NULL;
527-
spin_unlock(&cfid->fid_lock);
528534

529535
list_add_tail(&tmp_list->entry, &entry);
530536
}
@@ -557,8 +563,8 @@ void invalidate_all_cached_dirs(struct cifs_tcon *tcon)
557563

558564
/*
559565
* Mark all the cfids as closed, and move them to the cfids->dying list.
560-
* They'll be cleaned up later by cfids_invalidation_worker. Take
561-
* a reference to each cfid during this process.
566+
* They'll be cleaned up by laundromat. Take a reference to each cfid
567+
* during this process.
562568
*/
563569
spin_lock(&cfids->cfid_list_lock);
564570
list_for_each_entry_safe(cfid, q, &cfids->entries, entry) {
@@ -575,12 +581,11 @@ void invalidate_all_cached_dirs(struct cifs_tcon *tcon)
575581
} else
576582
kref_get(&cfid->refcount);
577583
}
578-
/*
579-
* Queue dropping of the dentries once locks have been dropped
580-
*/
581-
if (!list_empty(&cfids->dying))
582-
queue_work(cfid_put_wq, &cfids->invalidation_work);
583584
spin_unlock(&cfids->cfid_list_lock);
585+
586+
/* run laundromat unconditionally now as there might have been previously queued work */
587+
mod_delayed_work(cfid_put_wq, &cfids->laundromat_work, 0);
588+
flush_delayed_work(&cfids->laundromat_work);
584589
}
585590

586591
static void
@@ -592,7 +597,7 @@ cached_dir_offload_close(struct work_struct *work)
592597

593598
WARN_ON(cfid->on_list);
594599

595-
kref_put(&cfid->refcount, smb2_close_cached_fid);
600+
close_cached_dir(cfid);
596601
cifs_put_tcon(tcon, netfs_trace_tcon_ref_put_cached_close);
597602
}
598603

@@ -607,14 +612,9 @@ static void cached_dir_put_work(struct work_struct *work)
607612
{
608613
struct cached_fid *cfid = container_of(work, struct cached_fid,
609614
put_work);
610-
struct dentry *dentry;
611-
612-
spin_lock(&cfid->fid_lock);
613-
dentry = cfid->dentry;
615+
dput(cfid->dentry);
614616
cfid->dentry = NULL;
615-
spin_unlock(&cfid->fid_lock);
616617

617-
dput(dentry);
618618
queue_work(serverclose_wq, &cfid->close_work);
619619
}
620620

@@ -672,7 +672,6 @@ static struct cached_fid *init_cached_dir(const char *path)
672672
INIT_LIST_HEAD(&cfid->entry);
673673
INIT_LIST_HEAD(&cfid->dirents.entries);
674674
mutex_init(&cfid->dirents.de_mutex);
675-
spin_lock_init(&cfid->fid_lock);
676675
kref_init(&cfid->refcount);
677676
return cfid;
678677
}
@@ -696,40 +695,38 @@ static void free_cached_dir(struct cached_fid *cfid)
696695
kfree(dirent);
697696
}
698697

698+
/* adjust tcon-level counters and reset per-dir accounting */
699+
if (cfid->cfids) {
700+
if (cfid->dirents.entries_count)
701+
atomic_long_sub((long)cfid->dirents.entries_count,
702+
&cfid->cfids->total_dirents_entries);
703+
if (cfid->dirents.bytes_used) {
704+
atomic64_sub((long long)cfid->dirents.bytes_used,
705+
&cfid->cfids->total_dirents_bytes);
706+
atomic64_sub((long long)cfid->dirents.bytes_used,
707+
&cifs_dircache_bytes_used);
708+
}
709+
}
710+
cfid->dirents.entries_count = 0;
711+
cfid->dirents.bytes_used = 0;
712+
699713
kfree(cfid->path);
700714
cfid->path = NULL;
701715
kfree(cfid);
702716
}
703717

704-
static void cfids_invalidation_worker(struct work_struct *work)
705-
{
706-
struct cached_fids *cfids = container_of(work, struct cached_fids,
707-
invalidation_work);
708-
struct cached_fid *cfid, *q;
709-
LIST_HEAD(entry);
710-
711-
spin_lock(&cfids->cfid_list_lock);
712-
/* move cfids->dying to the local list */
713-
list_cut_before(&entry, &cfids->dying, &cfids->dying);
714-
spin_unlock(&cfids->cfid_list_lock);
715-
716-
list_for_each_entry_safe(cfid, q, &entry, entry) {
717-
list_del(&cfid->entry);
718-
/* Drop the ref-count acquired in invalidate_all_cached_dirs */
719-
kref_put(&cfid->refcount, smb2_close_cached_fid);
720-
}
721-
}
722-
723718
static void cfids_laundromat_worker(struct work_struct *work)
724719
{
725720
struct cached_fids *cfids;
726721
struct cached_fid *cfid, *q;
727-
struct dentry *dentry;
728722
LIST_HEAD(entry);
729723

730724
cfids = container_of(work, struct cached_fids, laundromat_work.work);
731725

732726
spin_lock(&cfids->cfid_list_lock);
727+
/* move cfids->dying to the local list */
728+
list_cut_before(&entry, &cfids->dying, &cfids->dying);
729+
733730
list_for_each_entry_safe(cfid, q, &cfids->entries, entry) {
734731
if (cfid->last_access_time &&
735732
time_after(jiffies, cfid->last_access_time + HZ * dir_cache_timeout)) {
@@ -751,12 +748,9 @@ static void cfids_laundromat_worker(struct work_struct *work)
751748
list_for_each_entry_safe(cfid, q, &entry, entry) {
752749
list_del(&cfid->entry);
753750

754-
spin_lock(&cfid->fid_lock);
755-
dentry = cfid->dentry;
751+
dput(cfid->dentry);
756752
cfid->dentry = NULL;
757-
spin_unlock(&cfid->fid_lock);
758753

759-
dput(dentry);
760754
if (cfid->is_open) {
761755
spin_lock(&cifs_tcp_ses_lock);
762756
++cfid->tcon->tc_count;
@@ -769,7 +763,7 @@ static void cfids_laundromat_worker(struct work_struct *work)
769763
* Drop the ref-count from above, either the lease-ref (if there
770764
* was one) or the extra one acquired.
771765
*/
772-
kref_put(&cfid->refcount, smb2_close_cached_fid);
766+
close_cached_dir(cfid);
773767
}
774768
queue_delayed_work(cfid_put_wq, &cfids->laundromat_work,
775769
dir_cache_timeout * HZ);
@@ -786,11 +780,13 @@ struct cached_fids *init_cached_dirs(void)
786780
INIT_LIST_HEAD(&cfids->entries);
787781
INIT_LIST_HEAD(&cfids->dying);
788782

789-
INIT_WORK(&cfids->invalidation_work, cfids_invalidation_worker);
790783
INIT_DELAYED_WORK(&cfids->laundromat_work, cfids_laundromat_worker);
791784
queue_delayed_work(cfid_put_wq, &cfids->laundromat_work,
792785
dir_cache_timeout * HZ);
793786

787+
atomic_long_set(&cfids->total_dirents_entries, 0);
788+
atomic64_set(&cfids->total_dirents_bytes, 0);
789+
794790
return cfids;
795791
}
796792

@@ -807,7 +803,6 @@ void free_cached_dirs(struct cached_fids *cfids)
807803
return;
808804

809805
cancel_delayed_work_sync(&cfids->laundromat_work);
810-
cancel_work_sync(&cfids->invalidation_work);
811806

812807
spin_lock(&cfids->cfid_list_lock);
813808
list_for_each_entry_safe(cfid, q, &cfids->entries, entry) {

fs/smb/client/cached_dir.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ struct cached_dirents {
2727
struct mutex de_mutex;
2828
loff_t pos; /* Expected ctx->pos */
2929
struct list_head entries;
30+
/* accounting for cached entries in this directory */
31+
unsigned long entries_count;
32+
unsigned long bytes_used;
3033
};
3134

3235
struct cached_fid {
@@ -41,7 +44,6 @@ struct cached_fid {
4144
unsigned long last_access_time; /* jiffies of when last accessed */
4245
struct kref refcount;
4346
struct cifs_fid fid;
44-
spinlock_t fid_lock;
4547
struct cifs_tcon *tcon;
4648
struct dentry *dentry;
4749
struct work_struct put_work;
@@ -60,10 +62,21 @@ struct cached_fids {
6062
int num_entries;
6163
struct list_head entries;
6264
struct list_head dying;
63-
struct work_struct invalidation_work;
6465
struct delayed_work laundromat_work;
66+
/* aggregate accounting for all cached dirents under this tcon */
67+
atomic_long_t total_dirents_entries;
68+
atomic64_t total_dirents_bytes;
6569
};
6670

71+
/* Module-wide directory cache accounting (defined in cifsfs.c) */
72+
extern atomic64_t cifs_dircache_bytes_used; /* bytes across all mounts */
73+
74+
static inline bool
75+
is_valid_cached_dir(struct cached_fid *cfid)
76+
{
77+
return cfid->time && cfid->has_lease;
78+
}
79+
6780
extern struct cached_fids *init_cached_dirs(void);
6881
extern void free_cached_dirs(struct cached_fids *cfids);
6982
extern int open_cached_dir(unsigned int xid, struct cifs_tcon *tcon,

0 commit comments

Comments
 (0)