Skip to content

Commit 062a7f0

Browse files
Paulo AlcantaraSteve French
authored andcommitted
smb: client: guarantee refcounted children from parent session
Avoid potential use-after-free bugs when walking DFS referrals, mounting and performing DFS failover by ensuring that all children from parent @tcon->ses are also refcounted. They're all needed across the entire DFS mount. Get rid of @tcon->dfs_ses_list while we're at it, too. Cc: [email protected] # 6.4+ Reported-by: kernel test robot <[email protected]> Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/ Signed-off-by: Paulo Alcantara (Red Hat) <[email protected]> Signed-off-by: Steve French <[email protected]>
1 parent e9e6224 commit 062a7f0

File tree

7 files changed

+76
-72
lines changed

7 files changed

+76
-72
lines changed

fs/smb/client/cifsglob.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,7 +1281,6 @@ struct cifs_tcon {
12811281
struct cached_fids *cfids;
12821282
/* BB add field for back pointer to sb struct(s)? */
12831283
#ifdef CONFIG_CIFS_DFS_UPCALL
1284-
struct list_head dfs_ses_list;
12851284
struct delayed_work dfs_cache_work;
12861285
#endif
12871286
struct delayed_work query_interfaces; /* query interfaces workqueue job */
@@ -1804,7 +1803,6 @@ struct cifs_mount_ctx {
18041803
struct TCP_Server_Info *server;
18051804
struct cifs_ses *ses;
18061805
struct cifs_tcon *tcon;
1807-
struct list_head dfs_ses_list;
18081806
};
18091807

18101808
static inline void __free_dfs_info_param(struct dfs_info3_param *param)

fs/smb/client/cifsproto.h

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -725,31 +725,31 @@ struct super_block *cifs_get_tcon_super(struct cifs_tcon *tcon);
725725
void cifs_put_tcon_super(struct super_block *sb);
726726
int cifs_wait_for_server_reconnect(struct TCP_Server_Info *server, bool retry);
727727

728-
/* Put references of @ses and @ses->dfs_root_ses */
728+
/* Put references of @ses and its children */
729729
static inline void cifs_put_smb_ses(struct cifs_ses *ses)
730730
{
731-
struct cifs_ses *rses = ses->dfs_root_ses;
731+
struct cifs_ses *next;
732732

733-
__cifs_put_smb_ses(ses);
734-
if (rses)
735-
__cifs_put_smb_ses(rses);
733+
do {
734+
next = ses->dfs_root_ses;
735+
__cifs_put_smb_ses(ses);
736+
} while ((ses = next));
736737
}
737738

738-
/* Get an active reference of @ses and @ses->dfs_root_ses.
739+
/* Get an active reference of @ses and its children.
739740
*
740741
* NOTE: make sure to call this function when incrementing reference count of
741742
* @ses to ensure that any DFS root session attached to it (@ses->dfs_root_ses)
742743
* will also get its reference count incremented.
743744
*
744-
* cifs_put_smb_ses() will put both references, so call it when you're done.
745+
* cifs_put_smb_ses() will put all references, so call it when you're done.
745746
*/
746747
static inline void cifs_smb_ses_inc_refcount(struct cifs_ses *ses)
747748
{
748749
lockdep_assert_held(&cifs_tcp_ses_lock);
749750

750-
ses->ses_count++;
751-
if (ses->dfs_root_ses)
752-
ses->dfs_root_ses->ses_count++;
751+
for (; ses; ses = ses->dfs_root_ses)
752+
ses->ses_count++;
753753
}
754754

755755
static inline bool dfs_src_pathname_equal(const char *s1, const char *s2)

fs/smb/client/connect.c

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1866,6 +1866,9 @@ static int match_session(struct cifs_ses *ses, struct smb3_fs_context *ctx)
18661866
ctx->sectype != ses->sectype)
18671867
return 0;
18681868

1869+
if (ctx->dfs_root_ses != ses->dfs_root_ses)
1870+
return 0;
1871+
18691872
/*
18701873
* If an existing session is limited to less channels than
18711874
* requested, it should not be reused
@@ -2358,9 +2361,9 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
23582361
* need to lock before changing something in the session.
23592362
*/
23602363
spin_lock(&cifs_tcp_ses_lock);
2364+
if (ctx->dfs_root_ses)
2365+
cifs_smb_ses_inc_refcount(ctx->dfs_root_ses);
23612366
ses->dfs_root_ses = ctx->dfs_root_ses;
2362-
if (ses->dfs_root_ses)
2363-
ses->dfs_root_ses->ses_count++;
23642367
list_add(&ses->smb_ses_list, &server->smb_ses_list);
23652368
spin_unlock(&cifs_tcp_ses_lock);
23662369

@@ -3311,6 +3314,9 @@ void cifs_mount_put_conns(struct cifs_mount_ctx *mnt_ctx)
33113314
cifs_put_smb_ses(mnt_ctx->ses);
33123315
else if (mnt_ctx->server)
33133316
cifs_put_tcp_session(mnt_ctx->server, 0);
3317+
mnt_ctx->ses = NULL;
3318+
mnt_ctx->tcon = NULL;
3319+
mnt_ctx->server = NULL;
33143320
mnt_ctx->cifs_sb->mnt_cifs_flags &= ~CIFS_MOUNT_POSIX_PATHS;
33153321
free_xid(mnt_ctx->xid);
33163322
}
@@ -3589,8 +3595,6 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
35893595
bool isdfs;
35903596
int rc;
35913597

3592-
INIT_LIST_HEAD(&mnt_ctx.dfs_ses_list);
3593-
35943598
rc = dfs_mount_share(&mnt_ctx, &isdfs);
35953599
if (rc)
35963600
goto error;
@@ -3621,7 +3625,6 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
36213625
return rc;
36223626

36233627
error:
3624-
dfs_put_root_smb_sessions(&mnt_ctx.dfs_ses_list);
36253628
cifs_mount_put_conns(&mnt_ctx);
36263629
return rc;
36273630
}
@@ -3636,6 +3639,18 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
36363639
goto error;
36373640

36383641
rc = cifs_mount_get_tcon(&mnt_ctx);
3642+
if (!rc) {
3643+
/*
3644+
* Prevent superblock from being created with any missing
3645+
* connections.
3646+
*/
3647+
if (WARN_ON(!mnt_ctx.server))
3648+
rc = -EHOSTDOWN;
3649+
else if (WARN_ON(!mnt_ctx.ses))
3650+
rc = -EACCES;
3651+
else if (WARN_ON(!mnt_ctx.tcon))
3652+
rc = -ENOENT;
3653+
}
36393654
if (rc)
36403655
goto error;
36413656

fs/smb/client/dfs.c

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -66,33 +66,20 @@ static int get_session(struct cifs_mount_ctx *mnt_ctx, const char *full_path)
6666
}
6767

6868
/*
69-
* Track individual DFS referral servers used by new DFS mount.
70-
*
71-
* On success, their lifetime will be shared by final tcon (dfs_ses_list).
72-
* Otherwise, they will be put by dfs_put_root_smb_sessions() in cifs_mount().
69+
* Get an active reference of @ses so that next call to cifs_put_tcon() won't
70+
* release it as any new DFS referrals must go through its IPC tcon.
7371
*/
74-
static int add_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
72+
static void add_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
7573
{
7674
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
77-
struct dfs_root_ses *root_ses;
7875
struct cifs_ses *ses = mnt_ctx->ses;
7976

8077
if (ses) {
81-
root_ses = kmalloc(sizeof(*root_ses), GFP_KERNEL);
82-
if (!root_ses)
83-
return -ENOMEM;
84-
85-
INIT_LIST_HEAD(&root_ses->list);
86-
8778
spin_lock(&cifs_tcp_ses_lock);
8879
cifs_smb_ses_inc_refcount(ses);
8980
spin_unlock(&cifs_tcp_ses_lock);
90-
root_ses->ses = ses;
91-
list_add_tail(&root_ses->list, &mnt_ctx->dfs_ses_list);
9281
}
93-
/* Select new DFS referral server so that new referrals go through it */
9482
ctx->dfs_root_ses = ses;
95-
return 0;
9683
}
9784

9885
static inline int parse_dfs_target(struct smb3_fs_context *ctx,
@@ -185,11 +172,8 @@ static int __dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx,
185172
continue;
186173
}
187174

188-
if (is_refsrv) {
189-
rc = add_root_smb_session(mnt_ctx);
190-
if (rc)
191-
goto out;
192-
}
175+
if (is_refsrv)
176+
add_root_smb_session(mnt_ctx);
193177

194178
rc = ref_walk_advance(rw);
195179
if (!rc) {
@@ -232,13 +216,26 @@ static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
232216
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
233217
struct cifs_tcon *tcon;
234218
char *origin_fullpath;
219+
bool new_tcon = true;
235220
int rc;
236221

237222
origin_fullpath = dfs_get_path(cifs_sb, ctx->source);
238223
if (IS_ERR(origin_fullpath))
239224
return PTR_ERR(origin_fullpath);
240225

241226
rc = dfs_referral_walk(mnt_ctx);
227+
if (!rc) {
228+
/*
229+
* Prevent superblock from being created with any missing
230+
* connections.
231+
*/
232+
if (WARN_ON(!mnt_ctx->server))
233+
rc = -EHOSTDOWN;
234+
else if (WARN_ON(!mnt_ctx->ses))
235+
rc = -EACCES;
236+
else if (WARN_ON(!mnt_ctx->tcon))
237+
rc = -ENOENT;
238+
}
242239
if (rc)
243240
goto out;
244241

@@ -247,15 +244,14 @@ static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
247244
if (!tcon->origin_fullpath) {
248245
tcon->origin_fullpath = origin_fullpath;
249246
origin_fullpath = NULL;
247+
} else {
248+
new_tcon = false;
250249
}
251250
spin_unlock(&tcon->tc_lock);
252251

253-
if (list_empty(&tcon->dfs_ses_list)) {
254-
list_replace_init(&mnt_ctx->dfs_ses_list, &tcon->dfs_ses_list);
252+
if (new_tcon) {
255253
queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
256254
dfs_cache_get_ttl() * HZ);
257-
} else {
258-
dfs_put_root_smb_sessions(&mnt_ctx->dfs_ses_list);
259255
}
260256

261257
out:
@@ -298,7 +294,6 @@ int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
298294
if (rc)
299295
return rc;
300296

301-
ctx->dfs_root_ses = mnt_ctx->ses;
302297
/*
303298
* If called with 'nodfs' mount option, then skip DFS resolving. Otherwise unconditionally
304299
* try to get an DFS referral (even cached) to determine whether it is an DFS mount.
@@ -324,7 +319,9 @@ int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
324319

325320
*isdfs = true;
326321
add_root_smb_session(mnt_ctx);
327-
return __dfs_mount_share(mnt_ctx);
322+
rc = __dfs_mount_share(mnt_ctx);
323+
dfs_put_root_smb_sessions(mnt_ctx);
324+
return rc;
328325
}
329326

330327
/* Update dfs referral path of superblock */

fs/smb/client/dfs.h

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
#define _CIFS_DFS_H
88

99
#include "cifsglob.h"
10+
#include "cifsproto.h"
1011
#include "fs_context.h"
12+
#include "dfs_cache.h"
1113
#include "cifs_unicode.h"
1214
#include <linux/namei.h>
1315

@@ -114,11 +116,6 @@ static inline void ref_walk_set_tgt_hint(struct dfs_ref_walk *rw)
114116
ref_walk_tit(rw));
115117
}
116118

117-
struct dfs_root_ses {
118-
struct list_head list;
119-
struct cifs_ses *ses;
120-
};
121-
122119
int dfs_parse_target_referral(const char *full_path, const struct dfs_info3_param *ref,
123120
struct smb3_fs_context *ctx);
124121
int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs);
@@ -133,20 +130,32 @@ static inline int dfs_get_referral(struct cifs_mount_ctx *mnt_ctx, const char *p
133130
{
134131
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
135132
struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
133+
struct cifs_ses *rses = ctx->dfs_root_ses ?: mnt_ctx->ses;
136134

137-
return dfs_cache_find(mnt_ctx->xid, ctx->dfs_root_ses, cifs_sb->local_nls,
135+
return dfs_cache_find(mnt_ctx->xid, rses, cifs_sb->local_nls,
138136
cifs_remap(cifs_sb), path, ref, tl);
139137
}
140138

141-
static inline void dfs_put_root_smb_sessions(struct list_head *head)
139+
/*
140+
* cifs_get_smb_ses() already guarantees an active reference of
141+
* @ses->dfs_root_ses when a new session is created, so we need to put extra
142+
* references of all DFS root sessions that were used across the mount process
143+
* in dfs_mount_share().
144+
*/
145+
static inline void dfs_put_root_smb_sessions(struct cifs_mount_ctx *mnt_ctx)
142146
{
143-
struct dfs_root_ses *root, *tmp;
147+
const struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
148+
struct cifs_ses *ses = ctx->dfs_root_ses;
149+
struct cifs_ses *cur;
150+
151+
if (!ses)
152+
return;
144153

145-
list_for_each_entry_safe(root, tmp, head, list) {
146-
list_del_init(&root->list);
147-
cifs_put_smb_ses(root->ses);
148-
kfree(root);
154+
for (cur = ses; cur; cur = cur->dfs_root_ses) {
155+
if (cur->dfs_root_ses)
156+
cifs_put_smb_ses(cur->dfs_root_ses);
149157
}
158+
cifs_put_smb_ses(ses);
150159
}
151160

152161
#endif /* _CIFS_DFS_H */

fs/smb/client/dfs_cache.c

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1278,21 +1278,12 @@ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
12781278
void dfs_cache_refresh(struct work_struct *work)
12791279
{
12801280
struct TCP_Server_Info *server;
1281-
struct dfs_root_ses *rses;
12821281
struct cifs_tcon *tcon;
12831282
struct cifs_ses *ses;
12841283

12851284
tcon = container_of(work, struct cifs_tcon, dfs_cache_work.work);
1286-
ses = tcon->ses;
1287-
server = ses->server;
12881285

1289-
mutex_lock(&server->refpath_lock);
1290-
if (server->leaf_fullpath)
1291-
__refresh_tcon(server->leaf_fullpath + 1, ses, false);
1292-
mutex_unlock(&server->refpath_lock);
1293-
1294-
list_for_each_entry(rses, &tcon->dfs_ses_list, list) {
1295-
ses = rses->ses;
1286+
for (ses = tcon->ses; ses; ses = ses->dfs_root_ses) {
12961287
server = ses->server;
12971288
mutex_lock(&server->refpath_lock);
12981289
if (server->leaf_fullpath)

fs/smb/client/misc.c

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,6 @@ tcon_info_alloc(bool dir_leases_enabled)
138138
atomic_set(&ret_buf->num_local_opens, 0);
139139
atomic_set(&ret_buf->num_remote_opens, 0);
140140
ret_buf->stats_from_time = ktime_get_real_seconds();
141-
#ifdef CONFIG_CIFS_DFS_UPCALL
142-
INIT_LIST_HEAD(&ret_buf->dfs_ses_list);
143-
#endif
144141

145142
return ret_buf;
146143
}
@@ -156,9 +153,6 @@ tconInfoFree(struct cifs_tcon *tcon)
156153
atomic_dec(&tconInfoAllocCount);
157154
kfree(tcon->nativeFileSystem);
158155
kfree_sensitive(tcon->password);
159-
#ifdef CONFIG_CIFS_DFS_UPCALL
160-
dfs_put_root_smb_sessions(&tcon->dfs_ses_list);
161-
#endif
162156
kfree(tcon->origin_fullpath);
163157
kfree(tcon);
164158
}

0 commit comments

Comments
 (0)