]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/blob - queue-6.8/smb-client-guarantee-refcounted-children-from-parent-session.patch
6.8-stable patches
[thirdparty/kernel/stable-queue.git] / queue-6.8 / smb-client-guarantee-refcounted-children-from-parent-session.patch
1 From 062a7f0ff46eb57aff526897bd2bebfdb1d3046a Mon Sep 17 00:00:00 2001
2 From: Paulo Alcantara <pc@manguebit.com>
3 Date: Mon, 1 Apr 2024 22:37:42 -0500
4 Subject: smb: client: guarantee refcounted children from parent session
5
6 From: Paulo Alcantara <pc@manguebit.com>
7
8 commit 062a7f0ff46eb57aff526897bd2bebfdb1d3046a upstream.
9
10 Avoid potential use-after-free bugs when walking DFS referrals,
11 mounting and performing DFS failover by ensuring that all children
12 from parent @tcon->ses are also refcounted. They're all needed across
13 the entire DFS mount. Get rid of @tcon->dfs_ses_list while we're at
14 it, too.
15
16 Cc: stable@vger.kernel.org # 6.4+
17 Reported-by: kernel test robot <lkp@intel.com>
18 Closes: https://lore.kernel.org/oe-kbuild-all/202404021527.ZlRkIxgv-lkp@intel.com/
19 Signed-off-by: Paulo Alcantara (Red Hat) <pc@manguebit.com>
20 Signed-off-by: Steve French <stfrench@microsoft.com>
21 Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
22 ---
23 fs/smb/client/cifsglob.h | 2 -
24 fs/smb/client/cifsproto.h | 20 +++++++++---------
25 fs/smb/client/connect.c | 25 ++++++++++++++++++----
26 fs/smb/client/dfs.c | 51 +++++++++++++++++++++-------------------------
27 fs/smb/client/dfs.h | 37 ++++++++++++++++++++-------------
28 fs/smb/client/dfs_cache.c | 11 ---------
29 fs/smb/client/misc.c | 6 -----
30 7 files changed, 78 insertions(+), 74 deletions(-)
31
32 --- a/fs/smb/client/cifsglob.h
33 +++ b/fs/smb/client/cifsglob.h
34 @@ -1267,7 +1267,6 @@ struct cifs_tcon {
35 struct cached_fids *cfids;
36 /* BB add field for back pointer to sb struct(s)? */
37 #ifdef CONFIG_CIFS_DFS_UPCALL
38 - struct list_head dfs_ses_list;
39 struct delayed_work dfs_cache_work;
40 #endif
41 struct delayed_work query_interfaces; /* query interfaces workqueue job */
42 @@ -1788,7 +1787,6 @@ struct cifs_mount_ctx {
43 struct TCP_Server_Info *server;
44 struct cifs_ses *ses;
45 struct cifs_tcon *tcon;
46 - struct list_head dfs_ses_list;
47 };
48
49 static inline void __free_dfs_info_param(struct dfs_info3_param *param)
50 --- a/fs/smb/client/cifsproto.h
51 +++ b/fs/smb/client/cifsproto.h
52 @@ -723,31 +723,31 @@ struct super_block *cifs_get_tcon_super(
53 void cifs_put_tcon_super(struct super_block *sb);
54 int cifs_wait_for_server_reconnect(struct TCP_Server_Info *server, bool retry);
55
56 -/* Put references of @ses and @ses->dfs_root_ses */
57 +/* Put references of @ses and its children */
58 static inline void cifs_put_smb_ses(struct cifs_ses *ses)
59 {
60 - struct cifs_ses *rses = ses->dfs_root_ses;
61 + struct cifs_ses *next;
62
63 - __cifs_put_smb_ses(ses);
64 - if (rses)
65 - __cifs_put_smb_ses(rses);
66 + do {
67 + next = ses->dfs_root_ses;
68 + __cifs_put_smb_ses(ses);
69 + } while ((ses = next));
70 }
71
72 -/* Get an active reference of @ses and @ses->dfs_root_ses.
73 +/* Get an active reference of @ses and its children.
74 *
75 * NOTE: make sure to call this function when incrementing reference count of
76 * @ses to ensure that any DFS root session attached to it (@ses->dfs_root_ses)
77 * will also get its reference count incremented.
78 *
79 - * cifs_put_smb_ses() will put both references, so call it when you're done.
80 + * cifs_put_smb_ses() will put all references, so call it when you're done.
81 */
82 static inline void cifs_smb_ses_inc_refcount(struct cifs_ses *ses)
83 {
84 lockdep_assert_held(&cifs_tcp_ses_lock);
85
86 - ses->ses_count++;
87 - if (ses->dfs_root_ses)
88 - ses->dfs_root_ses->ses_count++;
89 + for (; ses; ses = ses->dfs_root_ses)
90 + ses->ses_count++;
91 }
92
93 static inline bool dfs_src_pathname_equal(const char *s1, const char *s2)
94 --- a/fs/smb/client/connect.c
95 +++ b/fs/smb/client/connect.c
96 @@ -1869,6 +1869,9 @@ static int match_session(struct cifs_ses
97 ctx->sectype != ses->sectype)
98 return 0;
99
100 + if (ctx->dfs_root_ses != ses->dfs_root_ses)
101 + return 0;
102 +
103 /*
104 * If an existing session is limited to less channels than
105 * requested, it should not be reused
106 @@ -2361,9 +2364,9 @@ cifs_get_smb_ses(struct TCP_Server_Info
107 * need to lock before changing something in the session.
108 */
109 spin_lock(&cifs_tcp_ses_lock);
110 + if (ctx->dfs_root_ses)
111 + cifs_smb_ses_inc_refcount(ctx->dfs_root_ses);
112 ses->dfs_root_ses = ctx->dfs_root_ses;
113 - if (ses->dfs_root_ses)
114 - ses->dfs_root_ses->ses_count++;
115 list_add(&ses->smb_ses_list, &server->smb_ses_list);
116 spin_unlock(&cifs_tcp_ses_lock);
117
118 @@ -3312,6 +3315,9 @@ void cifs_mount_put_conns(struct cifs_mo
119 cifs_put_smb_ses(mnt_ctx->ses);
120 else if (mnt_ctx->server)
121 cifs_put_tcp_session(mnt_ctx->server, 0);
122 + mnt_ctx->ses = NULL;
123 + mnt_ctx->tcon = NULL;
124 + mnt_ctx->server = NULL;
125 mnt_ctx->cifs_sb->mnt_cifs_flags &= ~CIFS_MOUNT_POSIX_PATHS;
126 free_xid(mnt_ctx->xid);
127 }
128 @@ -3590,8 +3596,6 @@ int cifs_mount(struct cifs_sb_info *cifs
129 bool isdfs;
130 int rc;
131
132 - INIT_LIST_HEAD(&mnt_ctx.dfs_ses_list);
133 -
134 rc = dfs_mount_share(&mnt_ctx, &isdfs);
135 if (rc)
136 goto error;
137 @@ -3622,7 +3626,6 @@ out:
138 return rc;
139
140 error:
141 - dfs_put_root_smb_sessions(&mnt_ctx.dfs_ses_list);
142 cifs_mount_put_conns(&mnt_ctx);
143 return rc;
144 }
145 @@ -3637,6 +3640,18 @@ int cifs_mount(struct cifs_sb_info *cifs
146 goto error;
147
148 rc = cifs_mount_get_tcon(&mnt_ctx);
149 + if (!rc) {
150 + /*
151 + * Prevent superblock from being created with any missing
152 + * connections.
153 + */
154 + if (WARN_ON(!mnt_ctx.server))
155 + rc = -EHOSTDOWN;
156 + else if (WARN_ON(!mnt_ctx.ses))
157 + rc = -EACCES;
158 + else if (WARN_ON(!mnt_ctx.tcon))
159 + rc = -ENOENT;
160 + }
161 if (rc)
162 goto error;
163
164 --- a/fs/smb/client/dfs.c
165 +++ b/fs/smb/client/dfs.c
166 @@ -66,33 +66,20 @@ static int get_session(struct cifs_mount
167 }
168
169 /*
170 - * Track individual DFS referral servers used by new DFS mount.
171 - *
172 - * On success, their lifetime will be shared by final tcon (dfs_ses_list).
173 - * Otherwise, they will be put by dfs_put_root_smb_sessions() in cifs_mount().
174 + * Get an active reference of @ses so that next call to cifs_put_tcon() won't
175 + * release it as any new DFS referrals must go through its IPC tcon.
176 */
177 -static int add_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
178 +static void add_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
179 {
180 struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
181 - struct dfs_root_ses *root_ses;
182 struct cifs_ses *ses = mnt_ctx->ses;
183
184 if (ses) {
185 - root_ses = kmalloc(sizeof(*root_ses), GFP_KERNEL);
186 - if (!root_ses)
187 - return -ENOMEM;
188 -
189 - INIT_LIST_HEAD(&root_ses->list);
190 -
191 spin_lock(&cifs_tcp_ses_lock);
192 cifs_smb_ses_inc_refcount(ses);
193 spin_unlock(&cifs_tcp_ses_lock);
194 - root_ses->ses = ses;
195 - list_add_tail(&root_ses->list, &mnt_ctx->dfs_ses_list);
196 }
197 - /* Select new DFS referral server so that new referrals go through it */
198 ctx->dfs_root_ses = ses;
199 - return 0;
200 }
201
202 static inline int parse_dfs_target(struct smb3_fs_context *ctx,
203 @@ -185,11 +172,8 @@ again:
204 continue;
205 }
206
207 - if (is_refsrv) {
208 - rc = add_root_smb_session(mnt_ctx);
209 - if (rc)
210 - goto out;
211 - }
212 + if (is_refsrv)
213 + add_root_smb_session(mnt_ctx);
214
215 rc = ref_walk_advance(rw);
216 if (!rc) {
217 @@ -232,6 +216,7 @@ static int __dfs_mount_share(struct cifs
218 struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
219 struct cifs_tcon *tcon;
220 char *origin_fullpath;
221 + bool new_tcon = true;
222 int rc;
223
224 origin_fullpath = dfs_get_path(cifs_sb, ctx->source);
225 @@ -239,6 +224,18 @@ static int __dfs_mount_share(struct cifs
226 return PTR_ERR(origin_fullpath);
227
228 rc = dfs_referral_walk(mnt_ctx);
229 + if (!rc) {
230 + /*
231 + * Prevent superblock from being created with any missing
232 + * connections.
233 + */
234 + if (WARN_ON(!mnt_ctx->server))
235 + rc = -EHOSTDOWN;
236 + else if (WARN_ON(!mnt_ctx->ses))
237 + rc = -EACCES;
238 + else if (WARN_ON(!mnt_ctx->tcon))
239 + rc = -ENOENT;
240 + }
241 if (rc)
242 goto out;
243
244 @@ -247,15 +244,14 @@ static int __dfs_mount_share(struct cifs
245 if (!tcon->origin_fullpath) {
246 tcon->origin_fullpath = origin_fullpath;
247 origin_fullpath = NULL;
248 + } else {
249 + new_tcon = false;
250 }
251 spin_unlock(&tcon->tc_lock);
252
253 - if (list_empty(&tcon->dfs_ses_list)) {
254 - list_replace_init(&mnt_ctx->dfs_ses_list, &tcon->dfs_ses_list);
255 + if (new_tcon) {
256 queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
257 dfs_cache_get_ttl() * HZ);
258 - } else {
259 - dfs_put_root_smb_sessions(&mnt_ctx->dfs_ses_list);
260 }
261
262 out:
263 @@ -298,7 +294,6 @@ int dfs_mount_share(struct cifs_mount_ct
264 if (rc)
265 return rc;
266
267 - ctx->dfs_root_ses = mnt_ctx->ses;
268 /*
269 * If called with 'nodfs' mount option, then skip DFS resolving. Otherwise unconditionally
270 * try to get an DFS referral (even cached) to determine whether it is an DFS mount.
271 @@ -324,7 +319,9 @@ int dfs_mount_share(struct cifs_mount_ct
272
273 *isdfs = true;
274 add_root_smb_session(mnt_ctx);
275 - return __dfs_mount_share(mnt_ctx);
276 + rc = __dfs_mount_share(mnt_ctx);
277 + dfs_put_root_smb_sessions(mnt_ctx);
278 + return rc;
279 }
280
281 /* Update dfs referral path of superblock */
282 --- a/fs/smb/client/dfs.h
283 +++ b/fs/smb/client/dfs.h
284 @@ -7,7 +7,9 @@
285 #define _CIFS_DFS_H
286
287 #include "cifsglob.h"
288 +#include "cifsproto.h"
289 #include "fs_context.h"
290 +#include "dfs_cache.h"
291 #include "cifs_unicode.h"
292 #include <linux/namei.h>
293
294 @@ -114,11 +116,6 @@ static inline void ref_walk_set_tgt_hint
295 ref_walk_tit(rw));
296 }
297
298 -struct dfs_root_ses {
299 - struct list_head list;
300 - struct cifs_ses *ses;
301 -};
302 -
303 int dfs_parse_target_referral(const char *full_path, const struct dfs_info3_param *ref,
304 struct smb3_fs_context *ctx);
305 int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs);
306 @@ -133,20 +130,32 @@ static inline int dfs_get_referral(struc
307 {
308 struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
309 struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
310 + struct cifs_ses *rses = ctx->dfs_root_ses ?: mnt_ctx->ses;
311
312 - return dfs_cache_find(mnt_ctx->xid, ctx->dfs_root_ses, cifs_sb->local_nls,
313 + return dfs_cache_find(mnt_ctx->xid, rses, cifs_sb->local_nls,
314 cifs_remap(cifs_sb), path, ref, tl);
315 }
316
317 -static inline void dfs_put_root_smb_sessions(struct list_head *head)
318 -{
319 - struct dfs_root_ses *root, *tmp;
320 -
321 - list_for_each_entry_safe(root, tmp, head, list) {
322 - list_del_init(&root->list);
323 - cifs_put_smb_ses(root->ses);
324 - kfree(root);
325 +/*
326 + * cifs_get_smb_ses() already guarantees an active reference of
327 + * @ses->dfs_root_ses when a new session is created, so we need to put extra
328 + * references of all DFS root sessions that were used across the mount process
329 + * in dfs_mount_share().
330 + */
331 +static inline void dfs_put_root_smb_sessions(struct cifs_mount_ctx *mnt_ctx)
332 +{
333 + const struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
334 + struct cifs_ses *ses = ctx->dfs_root_ses;
335 + struct cifs_ses *cur;
336 +
337 + if (!ses)
338 + return;
339 +
340 + for (cur = ses; cur; cur = cur->dfs_root_ses) {
341 + if (cur->dfs_root_ses)
342 + cifs_put_smb_ses(cur->dfs_root_ses);
343 }
344 + cifs_put_smb_ses(ses);
345 }
346
347 #endif /* _CIFS_DFS_H */
348 --- a/fs/smb/client/dfs_cache.c
349 +++ b/fs/smb/client/dfs_cache.c
350 @@ -1278,21 +1278,12 @@ int dfs_cache_remount_fs(struct cifs_sb_
351 void dfs_cache_refresh(struct work_struct *work)
352 {
353 struct TCP_Server_Info *server;
354 - struct dfs_root_ses *rses;
355 struct cifs_tcon *tcon;
356 struct cifs_ses *ses;
357
358 tcon = container_of(work, struct cifs_tcon, dfs_cache_work.work);
359 - ses = tcon->ses;
360 - server = ses->server;
361
362 - mutex_lock(&server->refpath_lock);
363 - if (server->leaf_fullpath)
364 - __refresh_tcon(server->leaf_fullpath + 1, ses, false);
365 - mutex_unlock(&server->refpath_lock);
366 -
367 - list_for_each_entry(rses, &tcon->dfs_ses_list, list) {
368 - ses = rses->ses;
369 + for (ses = tcon->ses; ses; ses = ses->dfs_root_ses) {
370 server = ses->server;
371 mutex_lock(&server->refpath_lock);
372 if (server->leaf_fullpath)
373 --- a/fs/smb/client/misc.c
374 +++ b/fs/smb/client/misc.c
375 @@ -141,9 +141,6 @@ tcon_info_alloc(bool dir_leases_enabled)
376 atomic_set(&ret_buf->num_local_opens, 0);
377 atomic_set(&ret_buf->num_remote_opens, 0);
378 ret_buf->stats_from_time = ktime_get_real_seconds();
379 -#ifdef CONFIG_CIFS_DFS_UPCALL
380 - INIT_LIST_HEAD(&ret_buf->dfs_ses_list);
381 -#endif
382
383 return ret_buf;
384 }
385 @@ -159,9 +156,6 @@ tconInfoFree(struct cifs_tcon *tcon)
386 atomic_dec(&tconInfoAllocCount);
387 kfree(tcon->nativeFileSystem);
388 kfree_sensitive(tcon->password);
389 -#ifdef CONFIG_CIFS_DFS_UPCALL
390 - dfs_put_root_smb_sessions(&tcon->dfs_ses_list);
391 -#endif
392 kfree(tcon->origin_fullpath);
393 kfree(tcon);
394 }