Merge tag 'optee-fix-for-v7.1' of git://git.kernel.org/pub/scm/linux/kernel/git/jenswi/linux-tee into arm/fixes

OP-TEE fix for v7.1

Prevent possible use after free in supplicant communication.

* tag 'optee-fix-for-v7.1' of git://git.kernel.org/pub/scm/linux/kernel/git/jenswi/linux-tee:
  tee: optee: prevent use-after-free when the client exits before the supplicant

Signed-off-by: Arnd Bergmann <arnd@arndb.de>
This commit is contained in:
Arnd Bergmann
2026-05-28 15:28:19 +02:00
+74 -33
View File
@@ -10,7 +10,11 @@
struct optee_supp_req {
struct list_head link;
int id;
bool in_queue;
bool processed;
u32 func;
u32 ret;
size_t num_params;
@@ -19,6 +23,9 @@ struct optee_supp_req {
struct completion c;
};
/* It is temporary request used for revoked pending request in supp->idr. */
#define INVALID_REQ_PTR ((struct optee_supp_req *)ERR_PTR(-EBADF))
void optee_supp_init(struct optee_supp *supp)
{
memset(supp, 0, sizeof(*supp));
@@ -39,21 +46,23 @@ void optee_supp_release(struct optee_supp *supp)
{
int id;
struct optee_supp_req *req;
struct optee_supp_req *req_tmp;
mutex_lock(&supp->mutex);
/* Abort all request retrieved by supplicant */
/* Abort all request */
idr_for_each_entry(&supp->idr, req, id) {
idr_remove(&supp->idr, id);
req->ret = TEEC_ERROR_COMMUNICATION;
complete(&req->c);
}
/* Skip if request was already marked invalid */
if (IS_ERR(req))
continue;
/* Abort all queued requests */
list_for_each_entry_safe(req, req_tmp, &supp->reqs, link) {
list_del(&req->link);
req->in_queue = false;
/* For queued requests where supplicant has not seen it */
if (req->in_queue) {
list_del(&req->link);
req->in_queue = false;
}
req->processed = true;
req->ret = TEEC_ERROR_COMMUNICATION;
complete(&req->c);
}
@@ -100,8 +109,16 @@ u32 optee_supp_thrd_req(struct tee_context *ctx, u32 func, size_t num_params,
/* Insert the request in the request list */
mutex_lock(&supp->mutex);
req->id = idr_alloc(&supp->idr, req, 1, 0, GFP_KERNEL);
if (req->id < 0) {
mutex_unlock(&supp->mutex);
kfree(req);
return TEEC_ERROR_OUT_OF_MEMORY;
}
list_add_tail(&req->link, &supp->reqs);
req->in_queue = true;
req->processed = false;
mutex_unlock(&supp->mutex);
/* Tell an eventual waiter there's a new request */
@@ -117,21 +134,43 @@ u32 optee_supp_thrd_req(struct tee_context *ctx, u32 func, size_t num_params,
if (wait_for_completion_killable(&req->c)) {
mutex_lock(&supp->mutex);
if (req->in_queue) {
/* Supplicant has not seen this request yet. */
idr_remove(&supp->idr, req->id);
list_del(&req->link);
req->in_queue = false;
ret = TEEC_ERROR_COMMUNICATION;
} else if (req->processed) {
/*
* Supplicant has processed this request. Ignore the
* kill signal for now and submit the result. req is not
* in supp->reqs (removed by supp_pop_entry()) nor in
* supp->idr (removed by supp_pop_req()).
*/
ret = req->ret;
} else {
/*
* Supplicant is in the middle of processing this
* request. Replace req with INVALID_REQ_PTR so that
* the ID remains busy, causing optee_supp_send() to
* fail on the next call to supp_pop_req() with this ID.
*/
idr_replace(&supp->idr, INVALID_REQ_PTR, req->id);
ret = TEEC_ERROR_COMMUNICATION;
}
mutex_unlock(&supp->mutex);
req->ret = TEEC_ERROR_COMMUNICATION;
} else {
ret = req->ret;
}
ret = req->ret;
kfree(req);
return ret;
}
static struct optee_supp_req *supp_pop_entry(struct optee_supp *supp,
int num_params, int *id)
int num_params)
{
struct optee_supp_req *req;
@@ -153,10 +192,6 @@ static struct optee_supp_req *supp_pop_entry(struct optee_supp *supp,
return ERR_PTR(-EINVAL);
}
*id = idr_alloc(&supp->idr, req, 1, 0, GFP_KERNEL);
if (*id < 0)
return ERR_PTR(-ENOMEM);
list_del(&req->link);
req->in_queue = false;
@@ -214,7 +249,6 @@ int optee_supp_recv(struct tee_context *ctx, u32 *func, u32 *num_params,
struct optee *optee = tee_get_drvdata(teedev);
struct optee_supp *supp = &optee->supp;
struct optee_supp_req *req = NULL;
int id;
size_t num_meta;
int rc;
@@ -224,15 +258,11 @@ int optee_supp_recv(struct tee_context *ctx, u32 *func, u32 *num_params,
while (true) {
mutex_lock(&supp->mutex);
req = supp_pop_entry(supp, *num_params - num_meta, &id);
req = supp_pop_entry(supp, *num_params - num_meta);
if (req)
break; /* Keep mutex held. */
mutex_unlock(&supp->mutex);
if (req) {
if (IS_ERR(req))
return PTR_ERR(req);
break;
}
/*
* If we didn't get a request we'll block in
* wait_for_completion() to avoid needless spinning.
@@ -245,6 +275,13 @@ int optee_supp_recv(struct tee_context *ctx, u32 *func, u32 *num_params,
return -ERESTARTSYS;
}
/* supp->mutex held and req != NULL. */
if (IS_ERR(req)) {
mutex_unlock(&supp->mutex);
return PTR_ERR(req);
}
if (num_meta) {
/*
* tee-supplicant support meta parameters -> requsts can be
@@ -252,13 +289,11 @@ int optee_supp_recv(struct tee_context *ctx, u32 *func, u32 *num_params,
*/
param->attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT |
TEE_IOCTL_PARAM_ATTR_META;
param->u.value.a = id;
param->u.value.a = req->id;
param->u.value.b = 0;
param->u.value.c = 0;
} else {
mutex_lock(&supp->mutex);
supp->req_id = id;
mutex_unlock(&supp->mutex);
supp->req_id = req->id;
}
*func = req->func;
@@ -266,6 +301,7 @@ int optee_supp_recv(struct tee_context *ctx, u32 *func, u32 *num_params,
memcpy(param + num_meta, req->param,
sizeof(struct tee_param) * req->num_params);
mutex_unlock(&supp->mutex);
return 0;
}
@@ -297,12 +333,17 @@ static struct optee_supp_req *supp_pop_req(struct optee_supp *supp,
if (!req)
return ERR_PTR(-ENOENT);
/* optee_supp_thrd_req() already returned to optee. */
if (IS_ERR(req))
goto failed_req;
if ((num_params - nm) != req->num_params)
return ERR_PTR(-EINVAL);
*num_meta = nm;
failed_req:
idr_remove(&supp->idr, id);
supp->req_id = -1;
*num_meta = nm;
return req;
}
@@ -328,10 +369,9 @@ int optee_supp_send(struct tee_context *ctx, u32 ret, u32 num_params,
mutex_lock(&supp->mutex);
req = supp_pop_req(supp, num_params, param, &num_meta);
mutex_unlock(&supp->mutex);
if (IS_ERR(req)) {
/* Something is wrong, let supplicant restart. */
mutex_unlock(&supp->mutex);
/* Something is wrong, let supplicant handel it. */
return PTR_ERR(req);
}
@@ -355,9 +395,10 @@ int optee_supp_send(struct tee_context *ctx, u32 ret, u32 num_params,
}
}
req->ret = ret;
req->processed = true;
/* Let the requesting thread continue */
complete(&req->c);
mutex_unlock(&supp->mutex);
return 0;
}