diff --git a/arch/x86/kernel/cpu/sgx/encl.c b/arch/x86/kernel/cpu/sgx/encl.c
index d6a19bdd1921..b9a7c56f7c25 100644
--- a/arch/x86/kernel/cpu/sgx/encl.c
+++ b/arch/x86/kernel/cpu/sgx/encl.c
@@ -196,6 +196,12 @@ int sgx_encl_mm_add(struct sgx_encl *encl, struct mm_struct *mm)
 	struct sgx_encl_mm *encl_mm;
 	int ret;
 
+	/*
+	 * This flow relies on mmap_sem to provide mutual exclusivity (for a
+	 * given mm) to prevent duplicate instances of an encl_mm on the list.
+	 */
+	lockdep_assert_held_write(&mm->mmap_sem);
+
 	if (atomic_read(&encl->flags) & SGX_ENCL_DEAD)
 		return -EINVAL;
 
@@ -223,10 +229,22 @@ int sgx_encl_mm_add(struct sgx_encl *encl, struct mm_struct *mm)
 
 	spin_lock(&encl->mm_lock);
 	list_add_rcu(&encl_mm->list, &encl->mm_list);
+	/*
+	 * Ensure the mm is added to the list before updating the version.
+	 * Pairs with the smp_rmb() in sgx_reclaimer_block().
+	 */
+	smp_wmb();
+	encl->mm_list_version++;
 	spin_unlock(&encl->mm_lock);
 
-	synchronize_srcu(&encl->srcu);
-
+	/*
+	 * DO NOT call synchronize_srcu()!  When this is called via dup_mmap(),
+	 * mmap_sem is held for write in both the old mm and new mm, and the
+	 * reclaimer may be holding srcu for read while waiting on down_read()
+	 * for the old mm's mmap_sem, i.e. synchronizing will deadlock.
+	 * Incrementing the list version ensures readers that must not race
+	 * with a mm being added will see the updated list.
+	 */
 	return 0;
 }
 
diff --git a/arch/x86/kernel/cpu/sgx/encl.h b/arch/x86/kernel/cpu/sgx/encl.h
index 44b353aa8866..f0f72e591244 100644
--- a/arch/x86/kernel/cpu/sgx/encl.h
+++ b/arch/x86/kernel/cpu/sgx/encl.h
@@ -74,6 +74,7 @@ struct sgx_encl {
 	struct mutex lock;
 	struct list_head mm_list;
 	spinlock_t mm_lock;
+	unsigned long mm_list_version;
 	struct file *backing;
 	struct kref refcount;
 	struct srcu_struct srcu;
diff --git a/arch/x86/kernel/cpu/sgx/reclaim.c b/arch/x86/kernel/cpu/sgx/reclaim.c
index 39f0ddefbb79..3b4b849c5b2e 100644
--- a/arch/x86/kernel/cpu/sgx/reclaim.c
+++ b/arch/x86/kernel/cpu/sgx/reclaim.c
@@ -155,6 +155,11 @@ static bool sgx_reclaimer_age(struct sgx_epc_page *epc_page)
 	bool ret = true;
 	int idx;
 
+	/*
+	 * Note, this can race with sgx_encl_mm_add(), but worst case scenario
+	 * a page will be reclaimed immediately after it's accessed in the new
+	 * process/mm.
+	 */
 	idx = srcu_read_lock(&encl->srcu);
 
 	list_for_each_entry_rcu(encl_mm, &encl->mm_list, list) {
@@ -184,10 +189,20 @@ static void sgx_reclaimer_block(struct sgx_epc_page *epc_page)
 	struct sgx_encl_page *page = epc_page->owner;
 	unsigned long addr = SGX_ENCL_PAGE_ADDR(page);
 	struct sgx_encl *encl = page->encl;
+	unsigned long mm_list_version;
 	struct sgx_encl_mm *encl_mm;
 	struct vm_area_struct *vma;
 	int idx, ret;
 
+retry:
+	mm_list_version = encl->mm_list_version;
+	/*
+	 * Ensure the list version is read before walking the list to prevent
+	 * beginning the walk with the old list using the new version.  Pairs
+	 * with the smp_wmb() in sgx_encl_mm_add().
+	 */
+	smp_rmb();
+
 	idx = srcu_read_lock(&encl->srcu);
 
 	list_for_each_entry_rcu(encl_mm, &encl->mm_list, list) {
@@ -207,6 +222,19 @@ static void sgx_reclaimer_block(struct sgx_epc_page *epc_page)
 
 	srcu_read_unlock(&encl->srcu, idx);
 
+	/*
+	 * Redo the zapping if a mm was added to the list while zapping was in
+	 * progress.  dup_mmap() copies the PTEs for VM_PFNMAP VMAs, i.e. the
+	 * new mm won't take a page fault and so won't see that the page is
+	 * tagged RECLAIMED.  Note, vm_ops->open()/sgx_encl_mm_add() is called
+	 * _after_ PTEs are copied, and dup_mmap() holds the old mm's mmap_sem
+	 * for write, so the version check is only needed to protect against
+	 * dup_mmap() running after the list walk started but before the old
+	 * mm's PTEs were zapped.
+	 */
+	if (unlikely(encl->mm_list_version != mm_list_version))
+		goto retry;
+
 	mutex_lock(&encl->lock);
 
 	if (!(atomic_read(&encl->flags) & SGX_ENCL_DEAD)) {
@@ -250,6 +278,11 @@ static const cpumask_t *sgx_encl_ewb_cpumask(struct sgx_encl *encl)
 	struct sgx_encl_mm *encl_mm;
 	int idx;
 
+	/*
+	 * Note, this can race with sgx_encl_mm_add(), but ETRACK has already
+	 * been executed, so CPUs running in the new mm will enter the enclave
+	 * in a different epoch.
+	 */
 	cpumask_clear(cpumask);
 
 	idx = srcu_read_lock(&encl->srcu);
