On Sat, Jun 27, 2026 at 01:42:28PM +0000, [email protected] wrote: > Thank you for your contribution! Sashiko AI review found 7 potential issue(s) > to consider: > > New issues: > - [Critical] Heap buffer overflow in `qcom_pas_tee_get_rsc_table` when > copying the input resource table. > - [High] Out-of-bounds read and info leak in `qcom_pas_tee_get_rsc_table` > when duplicating the returned resource table. > - [High] The first TEE invocation fails to pass the input resource table data > to the TA, resulting in an invalid parameter error. > - [High] Unsynchronized mutation of the global operations structure and > unconditional unregistration during remove. > - [Medium] The `output_rt_size` pointer is left uninitialized if the TEE > returns a size of 0. > - [Medium] The driver incorrectly treats the expected > `TEEC_ERROR_SHORT_BUFFER` response as a fatal error.
I have responded in v6 as well, Shashiko is not able to get understand the TEE ABI with the TA here. Please go and look into the OP-TEE code-base here: https://github.com/OP-TEE/optee_os/blob/master/core/pta/qcom/pas/pta_qcom_pas.c -Sumit > > Pre-existing issues: > - [High] Missing protection for `ops_ptr` reads causes a TOCTOU NULL pointer > dereference. > -- > > commit f2659330f38d92cd91647244dc269a73194079c0 > Author: Sumit Garg <[email protected]> > > firmware: qcom: Add a PAS TEE service > > This patch adds support for a Peripheral Authentication Service (PAS) > driver based on the TEE bus, using OP-TEE as the backend implementation. > > > diff --git a/drivers/firmware/qcom/qcom_pas_tee.c > > b/drivers/firmware/qcom/qcom_pas_tee.c > > new file mode 100644 > > index 0000000000000..a2ba3af05a502 > > --- /dev/null > > +++ b/drivers/firmware/qcom/qcom_pas_tee.c > [ ... ] > > +static void *qcom_pas_tee_get_rsc_table(struct device *dev, > > + struct qcom_pas_context *ctx, > > + void *input_rt, size_t input_rt_size, > > + size_t *output_rt_size) > > +{ > > + struct qcom_pas_tee_private *data = dev_get_drvdata(dev); > > + struct tee_ioctl_invoke_arg inv_arg = { > > + .func = TA_QCOM_PAS_GET_RESOURCE_TABLE, > > + .session = data->session_id, > > + .num_params = TEE_NUM_PARAMS > > + }; > > + struct tee_param param[4] = { > > + [0] = { > > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT, > > + .u.value.a = ctx->pas_id, > > + }, > > + [1] = { > > + .attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT, > > + .u.memref.size = input_rt_size, > > + } > > + }; > > [Severity: High] > Does this first tee_client_invoke_func() invocation pass an invalid memory > reference to the TA? > > In qcom_pas_tee_get_rsc_table(), param[1] is initialized with > u.memref.size = input_rt_size, but u.memref.shm is left uninitialized (NULL). > If the firmware has an embedded input resource table (input_rt_size > 0), will > the TEE subsystem reject this call with an invalid parameter error before the > TA is even invoked? > > > + void *rt_buf = NULL; > > + int ret; > > + > > + ret = tee_client_invoke_func(data->ctx, &inv_arg, param); > > + if (ret < 0 || inv_arg.ret != 0) { > > [Severity: Medium] > Will this error check abort the loading process if the TA follows the standard > GlobalPlatform TEE Client API specification? > > When queried for a buffer size, a standard TA is expected to return > TEEC_ERROR_SHORT_BUFFER (a non-zero error code) since the initial buffer is > NULL or too small. Since this check treats any non-zero inv_arg.ret as a > failure, does this prevent the code from proceeding to the second pass to > allocate the correctly sized buffer? > > > + dev_err(dev, "PAS get RT failed, pas_id: %d, ret: %d, err: > > 0x%x\n", > > + ctx->pas_id, ret, inv_arg.ret); > > + return ret ? ERR_PTR(ret) : ERR_PTR(-EINVAL); > > + } > > + > > + if (param[1].u.memref.size) { > > [Severity: Medium] > Is it possible for output_rt_size to remain uninitialized here? > > If the TA legitimately returns param[1].u.memref.size as 0 during the first > invocation (indicating no resources), this block is skipped and the function > returns NULL. Since output_rt_size is never assigned a value, callers like > those in qcom_q6v5_pas.c might read garbage stack data for the table size. > > > + struct tee_shm *rt_shm __free(shm_free) = > > + tee_shm_alloc_kernel_buf(data->ctx, > > + param[1].u.memref.size); > > + void *rt_shm_va; > > + > > + if (IS_ERR_OR_NULL(rt_shm)) { > > + dev_err(dev, "rt_shm allocation failed\n"); > > + rt_shm = NULL; > > + return ERR_PTR(-ENOMEM); > > + } > > + > > + rt_shm_va = tee_shm_get_va(rt_shm, 0); > > + if (IS_ERR(rt_shm_va)) { > > + dev_err(dev, "rt_shm get VA failed\n"); > > + return ERR_CAST(rt_shm_va); > > + } > > + memcpy(rt_shm_va, input_rt, input_rt_size); > > [Severity: Critical] > Can this memcpy overflow the allocated rt_shm buffer? > > The rt_shm buffer is allocated using the size requested by the TA from the > first invocation (param[1].u.memref.size). However, this memcpy > unconditionally copies input_rt_size bytes. If the TA returns a size smaller > than input_rt_size, wouldn't this write past the bounds of the allocated > kernel heap buffer? > > > + > > + param[1].u.memref.shm = rt_shm; > > + ret = tee_client_invoke_func(data->ctx, &inv_arg, param); > > + if (ret < 0 || inv_arg.ret != 0) { > > + dev_err(dev, "PAS get RT failed, pas_id: %d, ret: %d, > > err: 0x%x\n", > > + ctx->pas_id, ret, inv_arg.ret); > > + return ret ? ERR_PTR(ret) : ERR_PTR(-EINVAL); > > + } > > + > > + if (param[1].u.memref.size) { > > + *output_rt_size = param[1].u.memref.size; > > + rt_buf = kmemdup(rt_shm_va, *output_rt_size, > > GFP_KERNEL); > > [Severity: High] > Could this kmemdup read out of bounds of the initially allocated rt_shm > buffer? > > During the second tee_client_invoke_func() call, the TA can update > param[1].u.memref.size to a value larger than the initially allocated rt_shm > capacity. If output_rt_size is updated to this larger value, kmemdup will read > past the end of the rt_shm_va buffer, potentially leaking adjacent sensitive > kernel heap data to the caller. > > > + if (!rt_buf) > > + return ERR_PTR(-ENOMEM); > > + } > > + } > > + > > + return rt_buf; > > +} > [ ... ] > > +static int qcom_pas_tee_probe(struct tee_client_device *pas_dev) > > +{ > [ ... ] > > + data->session_id = sess_arg.session; > > + dev_set_drvdata(dev, data); > > + qcom_pas_ops_tee.dev = dev; > > + qcom_pas_ops_register(&qcom_pas_ops_tee); > > [Severity: High] > Is it safe to mutate the static qcom_pas_ops_tee.dev singleton without > synchronization? > > If qcom_pas_ops_register() is called when another backend (like SCM) is > already registered, it fails silently but the probe still succeeds. > Could this lead to conflicting states if multiple backends probe concurrently? > > [Severity: High] > This isn't a bug introduced by this patch, but is there a TOCTOU race > condition when reading ops_ptr in qcom_pas.c? > > In functions like qcom_pas_init_image(), ops_ptr is checked for NULL and > subsequently dereferenced: > > if (!ops_ptr) > return -ENODEV; > > return ops_ptr->init_image(ops_ptr->dev, pas_id, metadata, size, ctx); > > Since ops_ptr is not protected by RCU, a lock, or READ_ONCE(), a concurrent > call to qcom_pas_ops_unregister() (e.g., via module unload) can set ops_ptr to > NULL between the check and the dereference, causing a NULL pointer > dereference. > > > + > > + return ret; > > +} > > + > > +static void qcom_pas_tee_remove(struct tee_client_device *pas_dev) > > +{ > > + struct device *dev = &pas_dev->dev; > > + struct qcom_pas_tee_private *data = dev_get_drvdata(dev); > > + > > + qcom_pas_ops_unregister(); > > [Severity: High] > Does this unconditional unregister break the SCM backend? > > If the TEE module is unloaded, qcom_pas_tee_remove() forcefully clears the > global ops_ptr. If the SCM backend was the active registered backend instead > of TEE, wouldn't this destroy the active SCM registration and break PAS > functionality globally? > > > + tee_client_close_session(data->ctx, data->session_id); > > + tee_client_close_context(data->ctx); > > +} > > -- > Sashiko AI review ยท > https://sashiko.dev/#/patchset/[email protected]?part=3
