RDMA/core: Add rdma_restrack_begin/abort/commit_del() operations

Add rdma_restrack_abort_del(), rdma_restrack_begin_del() and
rdma_restrack_commit_del() functions to allow deleting a resource from
the xarray to effectively prevent future access to it and wait for all
current users to finish while preserving its index in the xarray to
allow to re-insert it if needed with guaranteed success.

This is a preparatory change for subsequent patches in the series
which will use these functions to fix the cleanup flow.

Signed-off-by: Patrisious Haddad <phaddad@nvidia.com>
Reviewed-by: Michael Guralnik <michaelgur@nvidia.com>
Signed-off-by: Edward Srouji <edwards@nvidia.com>
Link: https://patch.msgid.link/20260713-restrack-uaf-fix-resub-v2-1-bbe8bb270d51@nvidia.com
Signed-off-by: Leon Romanovsky <leon@kernel.org>
This commit is contained in:
Patrisious Haddad 2026-07-13 18:38:00 +03:00 committed by Leon Romanovsky
parent 97f7c2262c
commit 8d18621067
2 changed files with 135 additions and 33 deletions

View File

@ -129,6 +129,46 @@ static void rdma_restrack_attach_task(struct rdma_restrack_entry *res,
res->user = true;
}
static struct rdma_restrack_root *res_to_rt(struct rdma_restrack_entry *res)
{
struct ib_device *dev = res_to_dev(res);
if (WARN_ON(!dev))
return NULL;
return &dev->res[res->type];
}
static void restrack_drain_res(struct rdma_restrack_root *rt,
struct rdma_restrack_entry *res)
{
if (rt) {
struct rdma_restrack_entry *old;
old = xa_cmpxchg(&rt->xa, res->id, res, XA_ZERO_ENTRY,
GFP_KERNEL);
WARN_ON(old != res);
}
rdma_restrack_put(res);
wait_for_completion(&res->comp);
}
static void restrack_restore_res(struct rdma_restrack_root *rt,
struct rdma_restrack_entry *res)
{
reinit_completion(&res->comp);
kref_init(&res->kref);
if (rt) {
struct rdma_restrack_entry *old;
old = xa_cmpxchg(&rt->xa, res->id, XA_ZERO_ENTRY, res,
GFP_KERNEL);
WARN_ON(old);
}
}
/**
* rdma_restrack_set_name() - set the task for this resource
* @res: resource entry
@ -177,22 +217,23 @@ void rdma_restrack_new(struct rdma_restrack_entry *res,
EXPORT_SYMBOL(rdma_restrack_new);
/**
* rdma_restrack_add() - add object to the resource tracking database
* rdma_restrack_add() - add object to the resource tracking database.
* If this resource reuses an ID of a resource that was already destroyed
* after calling rdma_restrack_begin() but didn't yet call
* rdma_restrack_commit_del() it can result in an untracked QP.
* @res: resource entry
*/
void rdma_restrack_add(struct rdma_restrack_entry *res)
{
struct ib_device *dev = res_to_dev(res);
struct rdma_restrack_root *rt;
int ret = 0;
if (!dev)
return;
if (res->no_track)
goto out;
rt = &dev->res[res->type];
rt = res_to_rt(res);
if (!rt)
return;
if (res->type == RDMA_RESTRACK_QP) {
/* Special case to ensure that LQPN points to right QP */
@ -229,6 +270,28 @@ void rdma_restrack_add(struct rdma_restrack_entry *res)
}
EXPORT_SYMBOL(rdma_restrack_add);
/**
* rdma_restrack_abort_del() - re-add object to the resource tracking database
* it can only be used after rdma_restrack_begin_del().
* @res: resource entry
*/
void rdma_restrack_abort_del(struct rdma_restrack_entry *res)
{
struct rdma_restrack_root *rt = NULL;
if (!res->valid)
return;
if (!res->no_track) {
rt = res_to_rt(res);
if (!rt)
return;
}
restrack_restore_res(rt, res);
}
EXPORT_SYMBOL(rdma_restrack_abort_del);
int __must_check rdma_restrack_get(struct rdma_restrack_entry *res)
{
return kref_get_unless_zero(&res->kref);
@ -265,7 +328,7 @@ static void restrack_release(struct kref *kref)
struct rdma_restrack_entry *res;
res = container_of(kref, struct rdma_restrack_entry, kref);
if (res->task) {
if (res->task && !res->valid) {
put_task_struct(res->task);
res->task = NULL;
}
@ -291,37 +354,20 @@ EXPORT_SYMBOL(rdma_restrack_put);
*/
void rdma_restrack_sync(struct rdma_restrack_entry *res)
{
struct rdma_restrack_entry *old;
struct rdma_restrack_root *rt;
struct task_struct *task;
struct ib_device *dev;
if (!res->valid || res->no_track)
return;
dev = res_to_dev(res);
if (WARN_ON(!dev))
rt = res_to_rt(res);
if (!rt)
return;
rt = &dev->res[res->type];
if (WARN_ON(xa_get_mark(&rt->xa, res->id, RESTRACK_DD)))
return;
old = xa_cmpxchg(&rt->xa, res->id, res, XA_ZERO_ENTRY, GFP_KERNEL);
if (WARN_ON(old != res))
return;
task = res->task;
if (task)
get_task_struct(task);
rdma_restrack_put(res);
wait_for_completion(&res->comp);
reinit_completion(&res->comp);
if (task)
res->task = task;
kref_init(&res->kref);
xa_cmpxchg(&rt->xa, res->id, XA_ZERO_ENTRY, res, GFP_KERNEL);
restrack_drain_res(rt, res);
restrack_restore_res(rt, res);
}
EXPORT_SYMBOL(rdma_restrack_sync);
@ -333,7 +379,6 @@ void rdma_restrack_del(struct rdma_restrack_entry *res)
{
struct rdma_restrack_entry *old;
struct rdma_restrack_root *rt;
struct ib_device *dev;
if (!res->valid) {
if (res->task) {
@ -346,12 +391,10 @@ void rdma_restrack_del(struct rdma_restrack_entry *res)
if (res->no_track)
goto out;
dev = res_to_dev(res);
if (WARN_ON(!dev))
rt = res_to_rt(res);
if (!rt)
return;
rt = &dev->res[res->type];
old = xa_erase(&rt->xa, res->id);
WARN_ON(old != res);
@ -359,5 +402,61 @@ void rdma_restrack_del(struct rdma_restrack_entry *res)
res->valid = false;
rdma_restrack_put(res);
wait_for_completion(&res->comp);
if (res->task) {
put_task_struct(res->task);
res->task = NULL;
}
}
EXPORT_SYMBOL(rdma_restrack_del);
/**
* rdma_restrack_begin_del() - invalidate the object from the resource tracking
* database but preserve its index in the array.
* Since this preserves the index in the array until rdma_restrack_commit_del()
* is called, if rdma_restrack_add() is called in between with an old QP ID it
* can result in an untracked QP.
* @res: resource entry
*/
void rdma_restrack_begin_del(struct rdma_restrack_entry *res)
{
struct rdma_restrack_root *rt = NULL;
if (!res->valid)
return;
if (!res->no_track) {
rt = res_to_rt(res);
if (!rt)
return;
}
restrack_drain_res(rt, res);
}
EXPORT_SYMBOL(rdma_restrack_begin_del);
/**
* rdma_restrack_commit_del() - delete object from the resource tracking
* database and free the task.
* @res: resource entry
*/
void rdma_restrack_commit_del(struct rdma_restrack_entry *res)
{
struct rdma_restrack_root *rt;
if (!res->valid || res->no_track)
goto out;
rt = res_to_rt(res);
if (!rt)
return;
xa_erase(&rt->xa, res->id);
out:
res->valid = false;
if (res->task) {
put_task_struct(res->task);
res->task = NULL;
}
}
EXPORT_SYMBOL(rdma_restrack_commit_del);

View File

@ -26,8 +26,11 @@ struct rdma_restrack_root {
int rdma_restrack_init(struct ib_device *dev);
void rdma_restrack_clean(struct ib_device *dev);
void rdma_restrack_add(struct rdma_restrack_entry *res);
void rdma_restrack_abort_del(struct rdma_restrack_entry *res);
void rdma_restrack_del(struct rdma_restrack_entry *res);
void rdma_restrack_sync(struct rdma_restrack_entry *res);
void rdma_restrack_begin_del(struct rdma_restrack_entry *res);
void rdma_restrack_commit_del(struct rdma_restrack_entry *res);
void rdma_restrack_new(struct rdma_restrack_entry *res,
enum rdma_restrack_type type);
void rdma_restrack_set_name(struct rdma_restrack_entry *res,