Skip to content

Commit 5a89d81

Browse files
pcacjrgregkh
authored andcommitted
cifs: fix use-after-free bug in refresh_cache_worker()
commit 396935d upstream. The UAF bug occurred because we were putting DFS root sessions in cifs_umount() while DFS cache refresher was being executed. Make DFS root sessions have same lifetime as DFS tcons so we can avoid the use-after-free bug is DFS cache refresher and other places that require IPCs to get new DFS referrals on. Also, get rid of mount group handling in DFS cache as we no longer need it. This fixes below use-after-free bug catched by KASAN [ 379.946955] BUG: KASAN: use-after-free in __refresh_tcon.isra.0+0x10b/0xc10 [cifs] [ 379.947642] Read of size 8 at addr ffff888018f57030 by task kworker/u4:3/56 [ 379.948096] [ 379.948208] CPU: 0 PID: 56 Comm: kworker/u4:3 Not tainted 6.2.0-rc7-lku #23 [ 379.948661] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.16.0-0-gd239552-rebuilt.opensuse.org 04/01/2014 [ 379.949368] Workqueue: cifs-dfscache refresh_cache_worker [cifs] [ 379.949942] Call Trace: [ 379.950113] <TASK> [ 379.950260] dump_stack_lvl+0x50/0x67 [ 379.950510] print_report+0x16a/0x48e [ 379.950759] ? __virt_addr_valid+0xd8/0x160 [ 379.951040] ? __phys_addr+0x41/0x80 [ 379.951285] kasan_report+0xdb/0x110 [ 379.951533] ? __refresh_tcon.isra.0+0x10b/0xc10 [cifs] [ 379.952056] ? __refresh_tcon.isra.0+0x10b/0xc10 [cifs] [ 379.952585] __refresh_tcon.isra.0+0x10b/0xc10 [cifs] [ 379.953096] ? __pfx___refresh_tcon.isra.0+0x10/0x10 [cifs] [ 379.953637] ? __pfx___mutex_lock+0x10/0x10 [ 379.953915] ? lock_release+0xb6/0x720 [ 379.954167] ? __pfx_lock_acquire+0x10/0x10 [ 379.954443] ? refresh_cache_worker+0x34e/0x6d0 [cifs] [ 379.954960] ? __pfx_wb_workfn+0x10/0x10 [ 379.955239] refresh_cache_worker+0x4ad/0x6d0 [cifs] [ 379.955755] ? __pfx_refresh_cache_worker+0x10/0x10 [cifs] [ 379.956323] ? __pfx_lock_acquired+0x10/0x10 [ 379.956615] ? read_word_at_a_time+0xe/0x20 [ 379.956898] ? lockdep_hardirqs_on_prepare+0x12/0x220 [ 379.957235] process_one_work+0x535/0x990 [ 379.957509] ? __pfx_process_one_work+0x10/0x10 [ 379.957812] ? lock_acquired+0xb7/0x5f0 [ 379.958069] ? __list_add_valid+0x37/0xd0 [ 379.958341] ? __list_add_valid+0x37/0xd0 [ 379.958611] worker_thread+0x8e/0x630 [ 379.958861] ? __pfx_worker_thread+0x10/0x10 [ 379.959148] kthread+0x17d/0x1b0 [ 379.959369] ? __pfx_kthread+0x10/0x10 [ 379.959630] ret_from_fork+0x2c/0x50 [ 379.959879] </TASK> Signed-off-by: Paulo Alcantara (SUSE) <[email protected]> Cc: [email protected] # 6.2 Signed-off-by: Steve French <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent ce82404 commit 5a89d81

File tree

8 files changed

+67
-164
lines changed

8 files changed

+67
-164
lines changed

fs/cifs/cifs_fs_sb.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ struct cifs_sb_info {
6161
/* only used when CIFS_MOUNT_USE_PREFIX_PATH is set */
6262
char *prepath;
6363

64-
/* randomly generated 128-bit number for indexing dfs mount groups in referral cache */
65-
uuid_t dfs_mount_id;
6664
/*
6765
* Indicate whether serverino option was turned off later
6866
* (cifs_autodisable_serverino) in order to match new mounts.

fs/cifs/cifsglob.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1239,6 +1239,7 @@ struct cifs_tcon {
12391239
/* BB add field for back pointer to sb struct(s)? */
12401240
#ifdef CONFIG_CIFS_DFS_UPCALL
12411241
struct list_head ulist; /* cache update list */
1242+
struct list_head dfs_ses_list;
12421243
#endif
12431244
struct delayed_work query_interfaces; /* query interfaces workqueue job */
12441245
};
@@ -1767,8 +1768,8 @@ struct cifs_mount_ctx {
17671768
struct TCP_Server_Info *server;
17681769
struct cifs_ses *ses;
17691770
struct cifs_tcon *tcon;
1770-
uuid_t mount_id;
17711771
char *origin_fullpath, *leaf_fullpath;
1772+
struct list_head dfs_ses_list;
17721773
};
17731774

17741775
static inline void free_dfs_info_param(struct dfs_info3_param *param)

fs/cifs/connect.c

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3457,7 +3457,8 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
34573457
bool isdfs;
34583458
int rc;
34593459

3460-
uuid_gen(&mnt_ctx.mount_id);
3460+
INIT_LIST_HEAD(&mnt_ctx.dfs_ses_list);
3461+
34613462
rc = dfs_mount_share(&mnt_ctx, &isdfs);
34623463
if (rc)
34633464
goto error;
@@ -3477,7 +3478,6 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
34773478
kfree(cifs_sb->prepath);
34783479
cifs_sb->prepath = ctx->prepath;
34793480
ctx->prepath = NULL;
3480-
uuid_copy(&cifs_sb->dfs_mount_id, &mnt_ctx.mount_id);
34813481

34823482
out:
34833483
cifs_try_adding_channels(cifs_sb, mnt_ctx.ses);
@@ -3489,7 +3489,7 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
34893489
return rc;
34903490

34913491
error:
3492-
dfs_cache_put_refsrv_sessions(&mnt_ctx.mount_id);
3492+
dfs_put_root_smb_sessions(&mnt_ctx.dfs_ses_list);
34933493
kfree(mnt_ctx.origin_fullpath);
34943494
kfree(mnt_ctx.leaf_fullpath);
34953495
cifs_mount_put_conns(&mnt_ctx);
@@ -3687,9 +3687,6 @@ cifs_umount(struct cifs_sb_info *cifs_sb)
36873687
spin_unlock(&cifs_sb->tlink_tree_lock);
36883688

36893689
kfree(cifs_sb->prepath);
3690-
#ifdef CONFIG_CIFS_DFS_UPCALL
3691-
dfs_cache_put_refsrv_sessions(&cifs_sb->dfs_mount_id);
3692-
#endif
36933690
call_rcu(&cifs_sb->rcu, delayed_free);
36943691
}
36953692

fs/cifs/dfs.c

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -99,26 +99,36 @@ static int get_session(struct cifs_mount_ctx *mnt_ctx, const char *full_path)
9999
return rc;
100100
}
101101

102-
static void set_root_ses(struct cifs_mount_ctx *mnt_ctx)
102+
static int get_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
103103
{
104104
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
105+
struct dfs_root_ses *root_ses;
105106
struct cifs_ses *ses = mnt_ctx->ses;
106107

107108
if (ses) {
109+
root_ses = kmalloc(sizeof(*root_ses), GFP_KERNEL);
110+
if (!root_ses)
111+
return -ENOMEM;
112+
113+
INIT_LIST_HEAD(&root_ses->list);
114+
108115
spin_lock(&cifs_tcp_ses_lock);
109116
ses->ses_count++;
110117
spin_unlock(&cifs_tcp_ses_lock);
111-
dfs_cache_add_refsrv_session(&mnt_ctx->mount_id, ses);
118+
root_ses->ses = ses;
119+
list_add_tail(&root_ses->list, &mnt_ctx->dfs_ses_list);
112120
}
113-
ctx->dfs_root_ses = mnt_ctx->ses;
121+
ctx->dfs_root_ses = ses;
122+
return 0;
114123
}
115124

116125
static int get_dfs_conn(struct cifs_mount_ctx *mnt_ctx, const char *ref_path, const char *full_path,
117126
const struct dfs_cache_tgt_iterator *tit)
118127
{
119128
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
120129
struct dfs_info3_param ref = {};
121-
int rc;
130+
bool is_refsrv = false;
131+
int rc, rc2;
122132

123133
rc = dfs_cache_get_tgt_referral(ref_path + 1, tit, &ref);
124134
if (rc)
@@ -133,8 +143,7 @@ static int get_dfs_conn(struct cifs_mount_ctx *mnt_ctx, const char *ref_path, co
133143
if (rc)
134144
goto out;
135145

136-
if (ref.flags & DFSREF_REFERRAL_SERVER)
137-
set_root_ses(mnt_ctx);
146+
is_refsrv = !!(ref.flags & DFSREF_REFERRAL_SERVER);
138147

139148
rc = -EREMOTE;
140149
if (ref.flags & DFSREF_STORAGE_SERVER) {
@@ -143,13 +152,17 @@ static int get_dfs_conn(struct cifs_mount_ctx *mnt_ctx, const char *ref_path, co
143152
goto out;
144153

145154
/* some servers may not advertise referral capability under ref.flags */
146-
if (!(ref.flags & DFSREF_REFERRAL_SERVER) &&
147-
is_tcon_dfs(mnt_ctx->tcon))
148-
set_root_ses(mnt_ctx);
155+
is_refsrv |= is_tcon_dfs(mnt_ctx->tcon);
149156

150157
rc = cifs_is_path_remote(mnt_ctx);
151158
}
152159

160+
if (rc == -EREMOTE && is_refsrv) {
161+
rc2 = get_root_smb_session(mnt_ctx);
162+
if (rc2)
163+
rc = rc2;
164+
}
165+
153166
out:
154167
free_dfs_info_param(&ref);
155168
return rc;
@@ -162,6 +175,7 @@ static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
162175
char *ref_path = NULL, *full_path = NULL;
163176
struct dfs_cache_tgt_iterator *tit;
164177
struct TCP_Server_Info *server;
178+
struct cifs_tcon *tcon;
165179
char *origin_fullpath = NULL;
166180
int num_links = 0;
167181
int rc;
@@ -231,12 +245,22 @@ static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
231245

232246
if (!rc) {
233247
server = mnt_ctx->server;
248+
tcon = mnt_ctx->tcon;
234249

235250
mutex_lock(&server->refpath_lock);
236-
server->origin_fullpath = origin_fullpath;
237-
server->current_fullpath = server->leaf_fullpath;
251+
if (!server->origin_fullpath) {
252+
server->origin_fullpath = origin_fullpath;
253+
server->current_fullpath = server->leaf_fullpath;
254+
origin_fullpath = NULL;
255+
}
238256
mutex_unlock(&server->refpath_lock);
239-
origin_fullpath = NULL;
257+
258+
if (list_empty(&tcon->dfs_ses_list)) {
259+
list_replace_init(&mnt_ctx->dfs_ses_list,
260+
&tcon->dfs_ses_list);
261+
} else {
262+
dfs_put_root_smb_sessions(&mnt_ctx->dfs_ses_list);
263+
}
240264
}
241265

242266
out:
@@ -277,7 +301,9 @@ int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
277301
}
278302

279303
*isdfs = true;
280-
set_root_ses(mnt_ctx);
304+
rc = get_root_smb_session(mnt_ctx);
305+
if (rc)
306+
return rc;
281307

282308
return __dfs_mount_share(mnt_ctx);
283309
}

fs/cifs/dfs.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
#include "fs_context.h"
1111
#include "cifs_unicode.h"
1212

13+
struct dfs_root_ses {
14+
struct list_head list;
15+
struct cifs_ses *ses;
16+
};
17+
1318
int dfs_parse_target_referral(const char *full_path, const struct dfs_info3_param *ref,
1419
struct smb3_fs_context *ctx);
1520
int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs);
@@ -44,4 +49,15 @@ static inline char *dfs_get_automount_devname(struct dentry *dentry, void *page)
4449
true);
4550
}
4651

52+
static inline void dfs_put_root_smb_sessions(struct list_head *head)
53+
{
54+
struct dfs_root_ses *root, *tmp;
55+
56+
list_for_each_entry_safe(root, tmp, head, list) {
57+
list_del_init(&root->list);
58+
cifs_put_smb_ses(root->ses);
59+
kfree(root);
60+
}
61+
}
62+
4763
#endif /* _CIFS_DFS_H */

fs/cifs/dfs_cache.c

Lines changed: 0 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,6 @@ struct cache_entry {
4949
struct cache_dfs_tgt *tgthint;
5050
};
5151

52-
/* List of referral server sessions per dfs mount */
53-
struct mount_group {
54-
struct list_head list;
55-
uuid_t id;
56-
struct cifs_ses *sessions[CACHE_MAX_ENTRIES];
57-
int num_sessions;
58-
spinlock_t lock;
59-
struct list_head refresh_list;
60-
struct kref refcount;
61-
};
62-
6352
static struct kmem_cache *cache_slab __read_mostly;
6453
static struct workqueue_struct *dfscache_wq __read_mostly;
6554

@@ -76,85 +65,10 @@ static atomic_t cache_count;
7665
static struct hlist_head cache_htable[CACHE_HTABLE_SIZE];
7766
static DECLARE_RWSEM(htable_rw_lock);
7867

79-
static LIST_HEAD(mount_group_list);
80-
static DEFINE_MUTEX(mount_group_list_lock);
81-
8268
static void refresh_cache_worker(struct work_struct *work);
8369

8470
static DECLARE_DELAYED_WORK(refresh_task, refresh_cache_worker);
8571

86-
static void __mount_group_release(struct mount_group *mg)
87-
{
88-
int i;
89-
90-
for (i = 0; i < mg->num_sessions; i++)
91-
cifs_put_smb_ses(mg->sessions[i]);
92-
kfree(mg);
93-
}
94-
95-
static void mount_group_release(struct kref *kref)
96-
{
97-
struct mount_group *mg = container_of(kref, struct mount_group, refcount);
98-
99-
mutex_lock(&mount_group_list_lock);
100-
list_del(&mg->list);
101-
mutex_unlock(&mount_group_list_lock);
102-
__mount_group_release(mg);
103-
}
104-
105-
static struct mount_group *find_mount_group_locked(const uuid_t *id)
106-
{
107-
struct mount_group *mg;
108-
109-
list_for_each_entry(mg, &mount_group_list, list) {
110-
if (uuid_equal(&mg->id, id))
111-
return mg;
112-
}
113-
return ERR_PTR(-ENOENT);
114-
}
115-
116-
static struct mount_group *__get_mount_group_locked(const uuid_t *id)
117-
{
118-
struct mount_group *mg;
119-
120-
mg = find_mount_group_locked(id);
121-
if (!IS_ERR(mg))
122-
return mg;
123-
124-
mg = kmalloc(sizeof(*mg), GFP_KERNEL);
125-
if (!mg)
126-
return ERR_PTR(-ENOMEM);
127-
kref_init(&mg->refcount);
128-
uuid_copy(&mg->id, id);
129-
mg->num_sessions = 0;
130-
spin_lock_init(&mg->lock);
131-
list_add(&mg->list, &mount_group_list);
132-
return mg;
133-
}
134-
135-
static struct mount_group *get_mount_group(const uuid_t *id)
136-
{
137-
struct mount_group *mg;
138-
139-
mutex_lock(&mount_group_list_lock);
140-
mg = __get_mount_group_locked(id);
141-
if (!IS_ERR(mg))
142-
kref_get(&mg->refcount);
143-
mutex_unlock(&mount_group_list_lock);
144-
145-
return mg;
146-
}
147-
148-
static void free_mount_group_list(void)
149-
{
150-
struct mount_group *mg, *tmp_mg;
151-
152-
list_for_each_entry_safe(mg, tmp_mg, &mount_group_list, list) {
153-
list_del_init(&mg->list);
154-
__mount_group_release(mg);
155-
}
156-
}
157-
15872
/**
15973
* dfs_cache_canonical_path - get a canonical DFS path
16074
*
@@ -704,7 +618,6 @@ void dfs_cache_destroy(void)
704618
{
705619
cancel_delayed_work_sync(&refresh_task);
706620
unload_nls(cache_cp);
707-
free_mount_group_list();
708621
flush_cache_ents();
709622
kmem_cache_destroy(cache_slab);
710623
destroy_workqueue(dfscache_wq);
@@ -1111,54 +1024,6 @@ int dfs_cache_get_tgt_referral(const char *path, const struct dfs_cache_tgt_iter
11111024
return rc;
11121025
}
11131026

1114-
/**
1115-
* dfs_cache_add_refsrv_session - add SMB session of referral server
1116-
*
1117-
* @mount_id: mount group uuid to lookup.
1118-
* @ses: reference counted SMB session of referral server.
1119-
*/
1120-
void dfs_cache_add_refsrv_session(const uuid_t *mount_id, struct cifs_ses *ses)
1121-
{
1122-
struct mount_group *mg;
1123-
1124-
if (WARN_ON_ONCE(!mount_id || uuid_is_null(mount_id) || !ses))
1125-
return;
1126-
1127-
mg = get_mount_group(mount_id);
1128-
if (WARN_ON_ONCE(IS_ERR(mg)))
1129-
return;
1130-
1131-
spin_lock(&mg->lock);
1132-
if (mg->num_sessions < ARRAY_SIZE(mg->sessions))
1133-
mg->sessions[mg->num_sessions++] = ses;
1134-
spin_unlock(&mg->lock);
1135-
kref_put(&mg->refcount, mount_group_release);
1136-
}
1137-
1138-
/**
1139-
* dfs_cache_put_refsrv_sessions - put all referral server sessions
1140-
*
1141-
* Put all SMB sessions from the given mount group id.
1142-
*
1143-
* @mount_id: mount group uuid to lookup.
1144-
*/
1145-
void dfs_cache_put_refsrv_sessions(const uuid_t *mount_id)
1146-
{
1147-
struct mount_group *mg;
1148-
1149-
if (!mount_id || uuid_is_null(mount_id))
1150-
return;
1151-
1152-
mutex_lock(&mount_group_list_lock);
1153-
mg = find_mount_group_locked(mount_id);
1154-
if (IS_ERR(mg)) {
1155-
mutex_unlock(&mount_group_list_lock);
1156-
return;
1157-
}
1158-
mutex_unlock(&mount_group_list_lock);
1159-
kref_put(&mg->refcount, mount_group_release);
1160-
}
1161-
11621027
/* Extract share from DFS target and return a pointer to prefix path or NULL */
11631028
static const char *parse_target_share(const char *target, char **share)
11641029
{
@@ -1384,11 +1249,6 @@ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
13841249
cifs_dbg(FYI, "%s: not a dfs mount\n", __func__);
13851250
return 0;
13861251
}
1387-
1388-
if (uuid_is_null(&cifs_sb->dfs_mount_id)) {
1389-
cifs_dbg(FYI, "%s: no dfs mount group id\n", __func__);
1390-
return -EINVAL;
1391-
}
13921252
/*
13931253
* After reconnecting to a different server, unique ids won't match anymore, so we disable
13941254
* serverino. This prevents dentry revalidation to think the dentry are stale (ESTALE).

fs/cifs/dfs_cache.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@ int dfs_cache_get_tgt_referral(const char *path, const struct dfs_cache_tgt_iter
4040
struct dfs_info3_param *ref);
4141
int dfs_cache_get_tgt_share(char *path, const struct dfs_cache_tgt_iterator *it, char **share,
4242
char **prefix);
43-
void dfs_cache_put_refsrv_sessions(const uuid_t *mount_id);
44-
void dfs_cache_add_refsrv_session(const uuid_t *mount_id, struct cifs_ses *ses);
4543
char *dfs_cache_canonical_path(const char *path, const struct nls_table *cp, int remap);
4644
int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb);
4745

0 commit comments

Comments
 (0)