Commit 396935de authored by Paulo Alcantara's avatar Paulo Alcantara Committed by Steve French
Browse files

cifs: fix use-after-free bug in refresh_cache_worker()



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: default avatarPaulo Alcantara (SUSE) <pc@manguebit.com>
Cc: stable@vger.kernel.org # 6.2
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
parent b56bce50
Loading
Loading
Loading
Loading
+0 −2
Original line number Diff line number Diff line
@@ -61,8 +61,6 @@ struct cifs_sb_info {
	/* only used when CIFS_MOUNT_USE_PREFIX_PATH is set */
	char *prepath;

	/* randomly generated 128-bit number for indexing dfs mount groups in referral cache */
	uuid_t dfs_mount_id;
	/*
	 * Indicate whether serverino option was turned off later
	 * (cifs_autodisable_serverino) in order to match new mounts.
+2 −1
Original line number Diff line number Diff line
@@ -1233,6 +1233,7 @@ struct cifs_tcon {
	/* BB add field for back pointer to sb struct(s)? */
#ifdef CONFIG_CIFS_DFS_UPCALL
	struct list_head ulist; /* cache update list */
	struct list_head dfs_ses_list;
#endif
	struct delayed_work	query_interfaces; /* query interfaces workqueue job */
};
@@ -1749,8 +1750,8 @@ struct cifs_mount_ctx {
	struct TCP_Server_Info *server;
	struct cifs_ses *ses;
	struct cifs_tcon *tcon;
	uuid_t mount_id;
	char *origin_fullpath, *leaf_fullpath;
	struct list_head dfs_ses_list;
};

static inline void free_dfs_info_param(struct dfs_info3_param *param)
+3 −6
Original line number Diff line number Diff line
@@ -3408,7 +3408,8 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
	bool isdfs;
	int rc;

	uuid_gen(&mnt_ctx.mount_id);
	INIT_LIST_HEAD(&mnt_ctx.dfs_ses_list);

	rc = dfs_mount_share(&mnt_ctx, &isdfs);
	if (rc)
		goto error;
@@ -3428,7 +3429,6 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
	kfree(cifs_sb->prepath);
	cifs_sb->prepath = ctx->prepath;
	ctx->prepath = NULL;
	uuid_copy(&cifs_sb->dfs_mount_id, &mnt_ctx.mount_id);

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

error:
	dfs_cache_put_refsrv_sessions(&mnt_ctx.mount_id);
	dfs_put_root_smb_sessions(&mnt_ctx.dfs_ses_list);
	kfree(mnt_ctx.origin_fullpath);
	kfree(mnt_ctx.leaf_fullpath);
	cifs_mount_put_conns(&mnt_ctx);
@@ -3638,9 +3638,6 @@ cifs_umount(struct cifs_sb_info *cifs_sb)
	spin_unlock(&cifs_sb->tlink_tree_lock);

	kfree(cifs_sb->prepath);
#ifdef CONFIG_CIFS_DFS_UPCALL
	dfs_cache_put_refsrv_sessions(&cifs_sb->dfs_mount_id);
#endif
	call_rcu(&cifs_sb->rcu, delayed_free);
}

+39 −13
Original line number Diff line number Diff line
@@ -99,18 +99,27 @@ static int get_session(struct cifs_mount_ctx *mnt_ctx, const char *full_path)
	return rc;
}

static void set_root_ses(struct cifs_mount_ctx *mnt_ctx)
static int get_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
{
	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
	struct dfs_root_ses *root_ses;
	struct cifs_ses *ses = mnt_ctx->ses;

	if (ses) {
		root_ses = kmalloc(sizeof(*root_ses), GFP_KERNEL);
		if (!root_ses)
			return -ENOMEM;

		INIT_LIST_HEAD(&root_ses->list);

		spin_lock(&cifs_tcp_ses_lock);
		ses->ses_count++;
		spin_unlock(&cifs_tcp_ses_lock);
		dfs_cache_add_refsrv_session(&mnt_ctx->mount_id, ses);
		root_ses->ses = ses;
		list_add_tail(&root_ses->list, &mnt_ctx->dfs_ses_list);
	}
	ctx->dfs_root_ses = mnt_ctx->ses;
	ctx->dfs_root_ses = ses;
	return 0;
}

static int get_dfs_conn(struct cifs_mount_ctx *mnt_ctx, const char *ref_path, const char *full_path,
@@ -118,7 +127,8 @@ static int get_dfs_conn(struct cifs_mount_ctx *mnt_ctx, const char *ref_path, co
{
	struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
	struct dfs_info3_param ref = {};
	int rc;
	bool is_refsrv = false;
	int rc, rc2;

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

	if (ref.flags & DFSREF_REFERRAL_SERVER)
		set_root_ses(mnt_ctx);
	is_refsrv = !!(ref.flags & DFSREF_REFERRAL_SERVER);

	rc = -EREMOTE;
	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
			goto out;

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

		rc = cifs_is_path_remote(mnt_ctx);
	}

	if (rc == -EREMOTE && is_refsrv) {
		rc2 = get_root_smb_session(mnt_ctx);
		if (rc2)
			rc = rc2;
	}

out:
	free_dfs_info_param(&ref);
	return rc;
@@ -162,6 +175,7 @@ static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
	char *ref_path = NULL, *full_path = NULL;
	struct dfs_cache_tgt_iterator *tit;
	struct TCP_Server_Info *server;
	struct cifs_tcon *tcon;
	char *origin_fullpath = NULL;
	int num_links = 0;
	int rc;
@@ -231,13 +245,23 @@ static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)

	if (!rc) {
		server = mnt_ctx->server;
		tcon = mnt_ctx->tcon;

		mutex_lock(&server->refpath_lock);
		if (!server->origin_fullpath) {
			server->origin_fullpath = origin_fullpath;
			server->current_fullpath = server->leaf_fullpath;
		mutex_unlock(&server->refpath_lock);
			origin_fullpath = NULL;
		}
		mutex_unlock(&server->refpath_lock);

		if (list_empty(&tcon->dfs_ses_list)) {
			list_replace_init(&mnt_ctx->dfs_ses_list,
					  &tcon->dfs_ses_list);
		} else {
			dfs_put_root_smb_sessions(&mnt_ctx->dfs_ses_list);
		}
	}

out:
	kfree(origin_fullpath);
@@ -277,7 +301,9 @@ int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
	}

	*isdfs = true;
	set_root_ses(mnt_ctx);
	rc = get_root_smb_session(mnt_ctx);
	if (rc)
		return rc;

	return __dfs_mount_share(mnt_ctx);
}
+16 −0
Original line number Diff line number Diff line
@@ -10,6 +10,11 @@
#include "fs_context.h"
#include "cifs_unicode.h"

struct dfs_root_ses {
	struct list_head list;
	struct cifs_ses *ses;
};

int dfs_parse_target_referral(const char *full_path, const struct dfs_info3_param *ref,
			      struct smb3_fs_context *ctx);
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)
							true);
}

static inline void dfs_put_root_smb_sessions(struct list_head *head)
{
	struct dfs_root_ses *root, *tmp;

	list_for_each_entry_safe(root, tmp, head, list) {
		list_del_init(&root->list);
		cifs_put_smb_ses(root->ses);
		kfree(root);
	}
}

#endif /* _CIFS_DFS_H */
Loading