[PATCH hmm] drm/amdkfd: fix a use after free race with mmu_notififer unregister
Kuehling, Felix
Felix.Kuehling at amd.com
Sun Aug 4 21:28:36 UTC 2019
On 2019-08-02 16:07, Jason Gunthorpe wrote:
> When using mmu_notififer_unregister_no_release() the caller must ensure
> there is a SRCU synchronize before the mn memory is freed, otherwise use
> after free races are possible, for instance:
>
> CPU0 CPU1
> invalidate_range_start
> hlist_for_each_entry_rcu(..)
> mmu_notifier_unregister_no_release(&p->mn)
> kfree(mn)
> if (mn->ops->invalidate_range_end)
>
> The error unwind in amdkfd misses the SRCU synchronization.
>
> amdkfd keeps the kfd_process around until the mm is released, so split the
> flow to fully initialize the kfd_process and register it for find_process,
> and with the notifier. Past this point the kfd_process does not need to be
> cleaned up as it is fully ready.
>
> The final failable step does a vm_mmap() and does not seem to impact the
> kfd_process global state. Since it also cannot be undone (and already has
> problems with undo if it internally fails), it has to be last.
>
> This way we don't have to try to unwind the mmu_notifier_register() and
> avoid the problem with the SRCU.
>
> Along the way this also fixes various other error unwind bugs in the flow.
>
> Fixes: 45102048f77e ("amdkfd: Add process queue manager module")
> Signed-off-by: Jason Gunthorpe <jgg at mellanox.com>
> ---
> drivers/gpu/drm/amd/amdkfd/kfd_process.c | 74 +++++++++++-------------
> 1 file changed, 35 insertions(+), 39 deletions(-)
>
> amdkfd folks, this little bug is blocking some rework I have for the
> mmu notifiers (ie mm/mmu_notifiers: remove unregister_no_release)
>
> Can I get your help to review and if needed polish this change? I'd
> like to send this patch through the hmm tree along with the rework,
> thanks
Thanks. That's a nice cleanup of the error handling during KFD process
creation. One nit-pick inline, otherwise this looks good to me.
>
> You can see the larger series here:
>
> https://github.com/jgunthorpe/linux/commits/mmu_notifier
>
> Jason
>
> diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_process.c b/drivers/gpu/drm/amd/amdkfd/kfd_process.c
> index 8f1076c0c88a25..81e3ee3f1813bf 100644
> --- a/drivers/gpu/drm/amd/amdkfd/kfd_process.c
> +++ b/drivers/gpu/drm/amd/amdkfd/kfd_process.c
> @@ -62,8 +62,8 @@ static struct workqueue_struct *kfd_restore_wq;
>
> static struct kfd_process *find_process(const struct task_struct *thread);
> static void kfd_process_ref_release(struct kref *ref);
> -static struct kfd_process *create_process(const struct task_struct *thread,
> - struct file *filep);
> +static struct kfd_process *create_process(const struct task_struct *thread);
> +static int kfd_process_init_cwsr_apu(struct kfd_process *p, struct file *filep);
>
> static void evict_process_worker(struct work_struct *work);
> static void restore_process_worker(struct work_struct *work);
> @@ -289,7 +289,15 @@ struct kfd_process *kfd_create_process(struct file *filep)
> if (process) {
> pr_debug("Process already found\n");
> } else {
> - process = create_process(thread, filep);
> + process = create_process(thread);
> + if (IS_ERR(process))
> + goto out;
> +
> + ret = kfd_process_init_cwsr_apu(process, filep);
> + if (ret) {
> + process = ERR_PTR(ret);
> + goto out;
> + }
>
> if (!procfs.kobj)
> goto out;
> @@ -609,64 +617,56 @@ static int kfd_process_device_init_cwsr_dgpu(struct kfd_process_device *pdd)
> return 0;
> }
>
> -static struct kfd_process *create_process(const struct task_struct *thread,
> - struct file *filep)
> +/*
> + * On return the kfd_process is fully operational and will be freed when the
> + * mm is released
> + */
> +static struct kfd_process *create_process(const struct task_struct *thread)
> {
> struct kfd_process *process;
> int err = -ENOMEM;
>
> process = kzalloc(sizeof(*process), GFP_KERNEL);
> -
> if (!process)
> goto err_alloc_process;
>
> - process->pasid = kfd_pasid_alloc();
> - if (process->pasid == 0)
> - goto err_alloc_pasid;
> -
> - if (kfd_alloc_process_doorbells(process) < 0)
> - goto err_alloc_doorbells;
> -
> kref_init(&process->ref);
> -
> mutex_init(&process->mutex);
> -
> process->mm = thread->mm;
> -
> - /* register notifier */
> - process->mmu_notifier.ops = &kfd_process_mmu_notifier_ops;
> - err = mmu_notifier_register(&process->mmu_notifier, process->mm);
> - if (err)
> - goto err_mmu_notifier;
> -
> - hash_add_rcu(kfd_processes_table, &process->kfd_processes,
> - (uintptr_t)process->mm);
> -
> process->lead_thread = thread->group_leader;
> - get_task_struct(process->lead_thread);
> -
> INIT_LIST_HEAD(&process->per_device_data);
> -
> + INIT_DELAYED_WORK(&process->eviction_work, evict_process_worker);
> + INIT_DELAYED_WORK(&process->restore_work, restore_process_worker);
> + process->last_restore_timestamp = get_jiffies_64();
> kfd_event_init_process(process);
> + process->is_32bit_user_mode = in_compat_syscall();
> +
> + process->pasid = kfd_pasid_alloc();
> + if (process->pasid == 0)
> + goto err_alloc_pasid;
> +
> + if (kfd_alloc_process_doorbells(process) < 0)
> + goto err_alloc_doorbells;
>
> err = pqm_init(&process->pqm, process);
> if (err != 0)
> goto err_process_pqm_init;
>
> /* init process apertures*/
> - process->is_32bit_user_mode = in_compat_syscall();
> err = kfd_init_apertures(process);
> if (err != 0)
> goto err_init_apertures;
>
> - INIT_DELAYED_WORK(&process->eviction_work, evict_process_worker);
> - INIT_DELAYED_WORK(&process->restore_work, restore_process_worker);
> - process->last_restore_timestamp = get_jiffies_64();
> -
> - err = kfd_process_init_cwsr_apu(process, filep);
> + /* Must be last, have to use release destruction after this */
> + process->mmu_notifier.ops = &kfd_process_mmu_notifier_ops;
> + err = mmu_notifier_register(&process->mmu_notifier, process->mm);
> if (err)
> goto err_init_cwsr;
This label should be renamed to something like err_mmu_notifier. With
that fixed this patch is
Reviewed-by: Felix Kuehling <Felix.Kuehling at amd.com>
>
> + get_task_struct(process->lead_thread);
> + hash_add_rcu(kfd_processes_table, &process->kfd_processes,
> + (uintptr_t)process->mm);
> +
> return process;
>
> err_init_cwsr:
> @@ -675,15 +675,11 @@ static struct kfd_process *create_process(const struct task_struct *thread,
> err_init_apertures:
> pqm_uninit(&process->pqm);
> err_process_pqm_init:
> - hash_del_rcu(&process->kfd_processes);
> - synchronize_rcu();
> - mmu_notifier_unregister_no_release(&process->mmu_notifier, process->mm);
> -err_mmu_notifier:
> - mutex_destroy(&process->mutex);
> kfd_free_process_doorbells(process);
> err_alloc_doorbells:
> kfd_pasid_free(process->pasid);
> err_alloc_pasid:
> + mutex_destroy(&process->mutex);
> kfree(process);
> err_alloc_process:
> return ERR_PTR(err);
More information about the dri-devel
mailing list