Commit 0d012343 authored by Yu Kuai's avatar Yu Kuai Committed by openeuler-sync-bot
Browse files

md: protect md_thread with rcu

mainline inclusion
from mainline-v6.5-rc1
commit 44693154
category: bugfix
bugzilla: https://gitee.com/openeuler/kernel/issues/I8OPEK
CVE: NA

Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4469315439827290923fce4f3f672599cabeb366



--------------------------------

Currently, there are many places that md_thread can be accessed without
protection, following are known scenarios that can cause
null-ptr-dereference or uaf:

1) sync_thread that is allocated and started from md_start_sync()
2) mddev->thread can be accessed directly from timeout_store() and
   md_bitmap_daemon_work()
3) md_unregister_thread() from action_store().

Currently, a global spinlock 'pers_lock' is borrowed to protect
'mddev->thread' in some places, this problem can be fixed likewise,
however, use a global lock for all the cases is not good.

Fix this problem by protecting all md_thread with rcu.

Signed-off-by: default avatarYu Kuai <yukuai3@huawei.com>
Signed-off-by: default avatarSong Liu <song@kernel.org>
Link: https://lore.kernel.org/r/20230523021017.3048783-6-yukuai1@huaweicloud.com



Conflicts:
  1) drivers/md/md.c
  commit 3ce94ce5 ("md: fix duplicate filename for rdev") adds
  declaration of export_rdev();
  commit 3ce94ce5 ("md: fix duplicate filename for rdev") adds
  md_free_rdev();
  commit 72adae23 ("md: Change active_io to percpu") remove
  synchronize_rcu() in mddev_suspend();
  2) drivers/md/raid5-cache.c
  commit ad831a16 ("md/raid5: use bdev_write_cache instead of
  open coding it") remove the use of request_queue;
  commit 913cce5a ("md: remove most calls to bdevname") remove
  the array of bdevname and change the formart of display.
Signed-off-by: default avatarLi Lingfeng <lilingfeng3@huawei.com>
(cherry picked from commit 448fbeaf)
parent ccb8f958
Loading
Loading
Loading
Loading
+8 −2
Original line number Diff line number Diff line
@@ -1222,13 +1222,19 @@ static bitmap_counter_t *md_bitmap_get_counter(struct bitmap_counts *bitmap,
static void mddev_set_timeout(struct mddev *mddev, unsigned long timeout,
			      bool force)
{
	struct md_thread *thread = mddev->thread;
	struct md_thread *thread;

	rcu_read_lock();
	thread = rcu_dereference(mddev->thread);

	if (!thread)
		return;
		goto out;

	if (force || thread->timeout < MAX_SCHEDULE_TIMEOUT)
		thread->timeout = timeout;

out:
	rcu_read_unlock();
}

/*
+11 −6
Original line number Diff line number Diff line
@@ -75,14 +75,14 @@ struct md_cluster_info {
	sector_t suspend_hi;
	int suspend_from; /* the slot which broadcast suspend_lo/hi */

	struct md_thread *recovery_thread;
	struct md_thread __rcu *recovery_thread;
	unsigned long recovery_map;
	/* communication loc resources */
	struct dlm_lock_resource *ack_lockres;
	struct dlm_lock_resource *message_lockres;
	struct dlm_lock_resource *token_lockres;
	struct dlm_lock_resource *no_new_dev_lockres;
	struct md_thread *recv_thread;
	struct md_thread __rcu *recv_thread;
	struct completion newdisk_completion;
	wait_queue_head_t wait;
	unsigned long state;
@@ -362,8 +362,8 @@ static void __recover_slot(struct mddev *mddev, int slot)

	set_bit(slot, &cinfo->recovery_map);
	if (!cinfo->recovery_thread) {
		cinfo->recovery_thread = md_register_thread(recover_bitmaps,
				mddev, "recover");
		rcu_assign_pointer(cinfo->recovery_thread,
			md_register_thread(recover_bitmaps, mddev, "recover"));
		if (!cinfo->recovery_thread) {
			pr_warn("md-cluster: Could not create recovery thread\n");
			return;
@@ -526,11 +526,15 @@ static void process_add_new_disk(struct mddev *mddev, struct cluster_msg *cmsg)
static void process_metadata_update(struct mddev *mddev, struct cluster_msg *msg)
{
	int got_lock = 0;
	struct md_thread *thread;
	struct md_cluster_info *cinfo = mddev->cluster_info;
	mddev->good_device_nr = le32_to_cpu(msg->raid_slot);

	dlm_lock_sync(cinfo->no_new_dev_lockres, DLM_LOCK_CR);
	wait_event(mddev->thread->wqueue,

	/* daemaon thread must exist */
	thread = rcu_dereference_protected(mddev->thread, true);
	wait_event(thread->wqueue,
		   (got_lock = mddev_trylock(mddev)) ||
		    test_bit(MD_CLUSTER_HOLDING_MUTEX_FOR_RECVD, &cinfo->state));
	md_reload_sb(mddev, mddev->good_device_nr);
@@ -890,7 +894,8 @@ static int join(struct mddev *mddev, int nodes)
	}
	/* Initiate the communication resources */
	ret = -ENOMEM;
	cinfo->recv_thread = md_register_thread(recv_daemon, mddev, "cluster_recv");
	rcu_assign_pointer(cinfo->recv_thread,
			md_register_thread(recv_daemon, mddev, "cluster_recv"));
	if (!cinfo->recv_thread) {
		pr_err("md-cluster: cannot allocate memory for recv_thread!\n");
		goto err;
+2 −2
Original line number Diff line number Diff line
@@ -407,8 +407,8 @@ static int multipath_run (struct mddev *mddev)
	if (ret)
		goto out_free_conf;

	mddev->thread = md_register_thread(multipathd, mddev,
					   "multipath");
	rcu_assign_pointer(mddev->thread,
			   md_register_thread(multipathd, mddev, "multipath"));
	if (!mddev->thread)
		goto out_free_conf;

+32 −37
Original line number Diff line number Diff line
@@ -68,11 +68,7 @@
#include "md-bitmap.h"
#include "md-cluster.h"

/* pers_list is a list of registered personalities protected
 * by pers_lock.
 * pers_lock does extra service to protect accesses to
 * mddev->thread when the mutex cannot be held.
 */
/* pers_list is a list of registered personalities protected by pers_lock. */
static LIST_HEAD(pers_list);
static DEFINE_SPINLOCK(pers_lock);

@@ -90,7 +86,7 @@ static struct workqueue_struct *md_rdev_misc_wq;
static int remove_and_add_spares(struct mddev *mddev,
				 struct md_rdev *this);
static void mddev_detach(struct mddev *mddev);
static void md_wakeup_thread_directly(struct md_thread *thread);
static void md_wakeup_thread_directly(struct md_thread __rcu *thread);

/*
 * Default number of read corrections we'll attempt on an rdev
@@ -500,8 +496,10 @@ static blk_qc_t md_submit_bio(struct bio *bio)
 */
void mddev_suspend(struct mddev *mddev)
{
	WARN_ON_ONCE(mddev->thread && current == mddev->thread->tsk);
	lockdep_assert_held(&mddev->reconfig_mutex);
	struct md_thread *thread = rcu_dereference_protected(mddev->thread,
			lockdep_is_held(&mddev->reconfig_mutex));

	WARN_ON_ONCE(thread && current == thread->tsk);
	if (mddev->suspended++)
		return;
	synchronize_rcu();
@@ -852,13 +850,8 @@ void mddev_unlock(struct mddev *mddev)
	} else
		mutex_unlock(&mddev->reconfig_mutex);

	/* As we've dropped the mutex we need a spinlock to
	 * make sure the thread doesn't disappear
	 */
	spin_lock(&pers_lock);
	md_wakeup_thread(mddev->thread);
	wake_up(&mddev->sb_wait);
	spin_unlock(&pers_lock);
}
EXPORT_SYMBOL_GPL(mddev_unlock);

@@ -8015,19 +8008,29 @@ static int md_thread(void *arg)
	return 0;
}

static void md_wakeup_thread_directly(struct md_thread *thread)
static void md_wakeup_thread_directly(struct md_thread __rcu *thread)
{
	if (thread)
		wake_up_process(thread->tsk);
	struct md_thread *t;

	rcu_read_lock();
	t = rcu_dereference(thread);
	if (t)
		wake_up_process(t->tsk);
	rcu_read_unlock();
}

void md_wakeup_thread(struct md_thread *thread)
void md_wakeup_thread(struct md_thread __rcu *thread)
{
	if (thread) {
		pr_debug("md: waking up MD thread %s.\n", thread->tsk->comm);
		set_bit(THREAD_WAKEUP, &thread->flags);
		wake_up(&thread->wqueue);
	struct md_thread *t;

	rcu_read_lock();
	t = rcu_dereference(thread);
	if (t) {
		pr_debug("md: waking up MD thread %s.\n", t->tsk->comm);
		set_bit(THREAD_WAKEUP, &t->flags);
		wake_up(&t->wqueue);
	}
	rcu_read_unlock();
}
EXPORT_SYMBOL(md_wakeup_thread);

@@ -8057,22 +8060,15 @@ struct md_thread *md_register_thread(void (*run) (struct md_thread *),
}
EXPORT_SYMBOL(md_register_thread);

void md_unregister_thread(struct md_thread **threadp)
void md_unregister_thread(struct md_thread __rcu **threadp)
{
	struct md_thread *thread;
	struct md_thread *thread = rcu_dereference_protected(*threadp, true);

	/*
	 * Locking ensures that mddev_unlock does not wake_up a
	 * non-existent thread
	 */
	spin_lock(&pers_lock);
	thread = *threadp;
	if (!thread) {
		spin_unlock(&pers_lock);
	if (!thread)
		return;
	}
	*threadp = NULL;
	spin_unlock(&pers_lock);

	rcu_assign_pointer(*threadp, NULL);
	synchronize_rcu();

	pr_debug("interrupting MD-thread pid %d\n", task_pid_nr(thread->tsk));
	kthread_stop(thread->tsk);
@@ -9273,9 +9269,8 @@ static void md_start_sync(struct work_struct *ws)
{
	struct mddev *mddev = container_of(ws, struct mddev, del_work);

	mddev->sync_thread = md_register_thread(md_do_sync,
						mddev,
						"resync");
	rcu_assign_pointer(mddev->sync_thread,
			   md_register_thread(md_do_sync, mddev, "resync"));
	if (!mddev->sync_thread) {
		pr_warn("%s: could not start resync thread...\n",
			mdname(mddev));
+4 −4
Original line number Diff line number Diff line
@@ -361,8 +361,8 @@ struct mddev {
	int				new_chunk_sectors;
	int				reshape_backwards;

	struct md_thread		*thread;	/* management thread */
	struct md_thread		*sync_thread;	/* doing resync or reconstruct */
	struct md_thread __rcu		*thread;	/* management thread */
	struct md_thread __rcu		*sync_thread;	/* doing resync or reconstruct */

	/* 'last_sync_action' is initialized to "none".  It is set when a
	 * sync operation (i.e "data-check", "requested-resync", "resync",
@@ -732,8 +732,8 @@ extern struct md_thread *md_register_thread(
	void (*run)(struct md_thread *thread),
	struct mddev *mddev,
	const char *name);
extern void md_unregister_thread(struct md_thread **threadp);
extern void md_wakeup_thread(struct md_thread *thread);
extern void md_unregister_thread(struct md_thread __rcu **threadp);
extern void md_wakeup_thread(struct md_thread __rcu *thread);
extern void md_check_recovery(struct mddev *mddev);
extern void md_reap_sync_thread(struct mddev *mddev);
extern int mddev_init_writes_pending(struct mddev *mddev);
Loading