mirror of
https://github.com/torvalds/linux.git
synced 2026-05-30 10:04:04 +02:00
iommufd for 6.5 rc
Two user triggerable problems:
- Syzkaller found a way to trigger a WARN_ON and leak memory by racing
destroy with other actions
- There is still a bug in the "batch carry" stuff that gets invoked for
complex cases with accesses and unmapping of huge pages. The test suite
found this (triggers rarely)
-----BEGIN PGP SIGNATURE-----
iHUEABYIAB0WIQRRRCHOFoQz/8F5bUaFwuHvBreFYQUCZMO+5wAKCRCFwuHvBreF
YR+YAP446OXo05z243Y6CUIL1qacwS08QfXnfzKjDHIDXqzEuQEAsQf9hbb2BhP/
+4/rrMgMVHrmeUDfzmCL2poOP+Kvtgc=
=a/mr
-----END PGP SIGNATURE-----
Merge tag 'for-linus-iommufd' of git://git.kernel.org/pub/scm/linux/kernel/git/jgg/iommufd
Pull iommufd fixes from Jason Gunthorpe:
"Two user triggerable problems:
- Syzkaller found a way to trigger a WARN_ON and leak memory by
racing destroy with other actions
- There is still a bug in the "batch carry" stuff that gets invoked
for complex cases with accesses and unmapping of huge pages. The
test suite found this (triggers rarely)"
* tag 'for-linus-iommufd' of git://git.kernel.org/pub/scm/linux/kernel/git/jgg/iommufd:
iommufd: Set end correctly when doing batch carry
iommufd: IOMMUFD_DESTROY should not increase the refcount
This commit is contained in:
commit
0299a13af0
|
|
@ -109,10 +109,7 @@ EXPORT_SYMBOL_NS_GPL(iommufd_device_bind, IOMMUFD);
|
|||
*/
|
||||
void iommufd_device_unbind(struct iommufd_device *idev)
|
||||
{
|
||||
bool was_destroyed;
|
||||
|
||||
was_destroyed = iommufd_object_destroy_user(idev->ictx, &idev->obj);
|
||||
WARN_ON(!was_destroyed);
|
||||
iommufd_object_destroy_user(idev->ictx, &idev->obj);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(iommufd_device_unbind, IOMMUFD);
|
||||
|
||||
|
|
@ -382,7 +379,7 @@ void iommufd_device_detach(struct iommufd_device *idev)
|
|||
mutex_unlock(&hwpt->devices_lock);
|
||||
|
||||
if (hwpt->auto_domain)
|
||||
iommufd_object_destroy_user(idev->ictx, &hwpt->obj);
|
||||
iommufd_object_deref_user(idev->ictx, &hwpt->obj);
|
||||
else
|
||||
refcount_dec(&hwpt->obj.users);
|
||||
|
||||
|
|
@ -456,10 +453,7 @@ EXPORT_SYMBOL_NS_GPL(iommufd_access_create, IOMMUFD);
|
|||
*/
|
||||
void iommufd_access_destroy(struct iommufd_access *access)
|
||||
{
|
||||
bool was_destroyed;
|
||||
|
||||
was_destroyed = iommufd_object_destroy_user(access->ictx, &access->obj);
|
||||
WARN_ON(!was_destroyed);
|
||||
iommufd_object_destroy_user(access->ictx, &access->obj);
|
||||
}
|
||||
EXPORT_SYMBOL_NS_GPL(iommufd_access_destroy, IOMMUFD);
|
||||
|
||||
|
|
|
|||
|
|
@ -176,8 +176,19 @@ void iommufd_object_abort_and_destroy(struct iommufd_ctx *ictx,
|
|||
struct iommufd_object *obj);
|
||||
void iommufd_object_finalize(struct iommufd_ctx *ictx,
|
||||
struct iommufd_object *obj);
|
||||
bool iommufd_object_destroy_user(struct iommufd_ctx *ictx,
|
||||
struct iommufd_object *obj);
|
||||
void __iommufd_object_destroy_user(struct iommufd_ctx *ictx,
|
||||
struct iommufd_object *obj, bool allow_fail);
|
||||
static inline void iommufd_object_destroy_user(struct iommufd_ctx *ictx,
|
||||
struct iommufd_object *obj)
|
||||
{
|
||||
__iommufd_object_destroy_user(ictx, obj, false);
|
||||
}
|
||||
static inline void iommufd_object_deref_user(struct iommufd_ctx *ictx,
|
||||
struct iommufd_object *obj)
|
||||
{
|
||||
__iommufd_object_destroy_user(ictx, obj, true);
|
||||
}
|
||||
|
||||
struct iommufd_object *_iommufd_object_alloc(struct iommufd_ctx *ictx,
|
||||
size_t size,
|
||||
enum iommufd_object_type type);
|
||||
|
|
|
|||
|
|
@ -116,14 +116,56 @@ struct iommufd_object *iommufd_get_object(struct iommufd_ctx *ictx, u32 id,
|
|||
return obj;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove the given object id from the xarray if the only reference to the
|
||||
* object is held by the xarray. The caller must call ops destroy().
|
||||
*/
|
||||
static struct iommufd_object *iommufd_object_remove(struct iommufd_ctx *ictx,
|
||||
u32 id, bool extra_put)
|
||||
{
|
||||
struct iommufd_object *obj;
|
||||
XA_STATE(xas, &ictx->objects, id);
|
||||
|
||||
xa_lock(&ictx->objects);
|
||||
obj = xas_load(&xas);
|
||||
if (xa_is_zero(obj) || !obj) {
|
||||
obj = ERR_PTR(-ENOENT);
|
||||
goto out_xa;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the caller is holding a ref on obj we put it here under the
|
||||
* spinlock.
|
||||
*/
|
||||
if (extra_put)
|
||||
refcount_dec(&obj->users);
|
||||
|
||||
if (!refcount_dec_if_one(&obj->users)) {
|
||||
obj = ERR_PTR(-EBUSY);
|
||||
goto out_xa;
|
||||
}
|
||||
|
||||
xas_store(&xas, NULL);
|
||||
if (ictx->vfio_ioas == container_of(obj, struct iommufd_ioas, obj))
|
||||
ictx->vfio_ioas = NULL;
|
||||
|
||||
out_xa:
|
||||
xa_unlock(&ictx->objects);
|
||||
|
||||
/* The returned object reference count is zero */
|
||||
return obj;
|
||||
}
|
||||
|
||||
/*
|
||||
* The caller holds a users refcount and wants to destroy the object. Returns
|
||||
* true if the object was destroyed. In all cases the caller no longer has a
|
||||
* reference on obj.
|
||||
*/
|
||||
bool iommufd_object_destroy_user(struct iommufd_ctx *ictx,
|
||||
struct iommufd_object *obj)
|
||||
void __iommufd_object_destroy_user(struct iommufd_ctx *ictx,
|
||||
struct iommufd_object *obj, bool allow_fail)
|
||||
{
|
||||
struct iommufd_object *ret;
|
||||
|
||||
/*
|
||||
* The purpose of the destroy_rwsem is to ensure deterministic
|
||||
* destruction of objects used by external drivers and destroyed by this
|
||||
|
|
@ -131,22 +173,22 @@ bool iommufd_object_destroy_user(struct iommufd_ctx *ictx,
|
|||
* side of this, such as during ioctl execution.
|
||||
*/
|
||||
down_write(&obj->destroy_rwsem);
|
||||
xa_lock(&ictx->objects);
|
||||
refcount_dec(&obj->users);
|
||||
if (!refcount_dec_if_one(&obj->users)) {
|
||||
xa_unlock(&ictx->objects);
|
||||
up_write(&obj->destroy_rwsem);
|
||||
return false;
|
||||
}
|
||||
__xa_erase(&ictx->objects, obj->id);
|
||||
if (ictx->vfio_ioas && &ictx->vfio_ioas->obj == obj)
|
||||
ictx->vfio_ioas = NULL;
|
||||
xa_unlock(&ictx->objects);
|
||||
ret = iommufd_object_remove(ictx, obj->id, true);
|
||||
up_write(&obj->destroy_rwsem);
|
||||
|
||||
if (allow_fail && IS_ERR(ret))
|
||||
return;
|
||||
|
||||
/*
|
||||
* If there is a bug and we couldn't destroy the object then we did put
|
||||
* back the caller's refcount and will eventually try to free it again
|
||||
* during close.
|
||||
*/
|
||||
if (WARN_ON(IS_ERR(ret)))
|
||||
return;
|
||||
|
||||
iommufd_object_ops[obj->type].destroy(obj);
|
||||
kfree(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int iommufd_destroy(struct iommufd_ucmd *ucmd)
|
||||
|
|
@ -154,13 +196,11 @@ static int iommufd_destroy(struct iommufd_ucmd *ucmd)
|
|||
struct iommu_destroy *cmd = ucmd->cmd;
|
||||
struct iommufd_object *obj;
|
||||
|
||||
obj = iommufd_get_object(ucmd->ictx, cmd->id, IOMMUFD_OBJ_ANY);
|
||||
obj = iommufd_object_remove(ucmd->ictx, cmd->id, false);
|
||||
if (IS_ERR(obj))
|
||||
return PTR_ERR(obj);
|
||||
iommufd_ref_to_users(obj);
|
||||
/* See iommufd_ref_to_users() */
|
||||
if (!iommufd_object_destroy_user(ucmd->ictx, obj))
|
||||
return -EBUSY;
|
||||
iommufd_object_ops[obj->type].destroy(obj);
|
||||
kfree(obj);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ static void batch_clear_carry(struct pfn_batch *batch, unsigned int keep_pfns)
|
|||
batch->pfns[0] = batch->pfns[batch->end - 1] +
|
||||
(batch->npfns[batch->end - 1] - keep_pfns);
|
||||
batch->npfns[0] = keep_pfns;
|
||||
batch->end = 0;
|
||||
batch->end = 1;
|
||||
}
|
||||
|
||||
static void batch_skip_carry(struct pfn_batch *batch, unsigned int skip_pfns)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user