CVE-2026-33317: OP-TEE PKCS#11 OOB Read/Write in entry_get_attribute_value()
Missing bounds checks in OP-TEE's PKCS#11 TA allow an attacker to read 7 bytes past a heap template buffer and write arbitrary attribute data beyond it, affecting versions 3.13.0–4.10.0.
Your smartphone has a secret vault inside it. This vault, called a Trusted Execution Environment, is like a fortress within your phone designed to protect your most sensitive information—your banking passwords, your fingerprint data, and your encryption keys. It's supposed to be so secure that even if someone hacks the rest of your phone, this vault stays locked.
OP-TEE is one of the most popular vault systems used in Android phones and other Linux devices. Security researchers have found a flaw in certain versions that could let attackers peek inside this vault or crash it entirely. Think of it like a lock with a tiny gap—it still keeps honest people out, but someone looking for the gap might slip through.
The problem is in how OP-TEE checks requests for cryptographic information. When someone asks for an encryption key stored in the vault, the vault doesn't properly verify that the request is legitimate before handing over data. An attacker could send a specially crafted request that tricks the vault into showing secret information it shouldn't, or causes it to crash.
Who's at risk? Anyone using a phone with OP-TEE versions from 3.13.0 to 4.10.0—which includes many Android devices, particularly older ones and certain enterprise phones. This matters because your encryption keys are among the most valuable things on your phone.
What should you do? First, check if your phone manufacturer has released security updates and install them immediately. Second, avoid using your phone for sensitive financial transactions until you've updated. Third, contact your phone maker's support if you're unsure whether your device is affected.
Want the full technical analysis? Click "Technical" above.
CVE-2026-33317 is a heap memory corruption vulnerability residing in the PKCS#11 Trusted Application (TA) of OP-TEE OS, affecting versions 3.13.0 through 4.10.0. The bug lives in entry_get_attribute_value() inside ta/pkcs11/src/object.c, the handler for the PKCS11_CMD_GET_ATTRIBUTE_VALUE command. Due to missing bounds validation on the caller-supplied template buffer, the function can be driven to read at most 7 bytes beyond the end of the template buffer on the TA heap, and subsequently write past the end of that same buffer with the raw content of a PKCS#11 object attribute. CVSS scores this at 8.7 HIGH. No in-the-wild exploitation has been confirmed.
The impact is significant because the PKCS#11 TA runs inside the TrustZone Secure World. Heap corruption within that enclave can undermine the confidentiality and integrity guarantees that the entire TrustZone security model depends on.
Affected Component
The vulnerable code is in the PKCS#11 Trusted Application, a standalone TA compiled into the OP-TEE OS image. The call path from the Normal World to the bug is:
Normal World (Linux) → libteec → TA invoke → PKCS11_CMD_GET_ATTRIBUTE_VALUE
→ pkcs11_ta_invoke_command()
→ entry_get_attribute_value() [ta/pkcs11/src/object.c]
→ get_attribute_value_helper()
→ serializer / template iteration ← BUG HERE
The C99 PKCS#11 attribute template is a flat serialised buffer of (type, size, value) triples. entry_get_attribute_value() iterates over this caller-controlled buffer to match attribute types against an internal object store, then writes the found attribute value back into the same buffer. The critical defect is that the iteration pointer advances using only the caller-supplied size field without checking that the resulting pointer still falls within the original buffer bounds.
/*
* ta/pkcs11/src/object.c (vulnerable, ~v4.10.0)
*
* Serialised template layout:
* [CK_ATTRIBUTE_TYPE type : 4 bytes]
* [CK_ULONG ulValueLen : 4 bytes] <-- attacker-controlled
* [uint8_t value[ulValueLen]]
*
* entry_get_attribute_value() simplified:
*/
static enum pkcs11_rc
entry_get_attribute_value(struct pkcs11_session *session,
void *ptypes, TEE_Param params[4])
{
struct serializer obj_attrs = { };
uint8_t *template_buf = params[1].memref.buffer;
size_t template_size = params[1].memref.size;
uint8_t *cur = template_buf;
uint8_t *end = template_buf + template_size;
while (cur < end) {
CK_ATTRIBUTE_TYPE type;
CK_ULONG val_len;
/* Read the attribute type and declared length */
TEE_MemMove(&type, cur, sizeof(type));
TEE_MemMove(&val_len, cur + 4, sizeof(val_len));
/* BUG: no check that (cur + 8 + val_len) <= end before advancing */
/* If val_len is crafted so that cur+8+val_len wraps or exceeds end,*/
/* the next iteration reads type/val_len from beyond the buffer. */
/* Lookup attribute in the object */
rc = get_attribute(&obj_attrs, type, cur + 8, &val_len);
/* BUG: no check that (cur + 8 + val_len) <= end before write-back */
/* get_attribute() writes into cur+8 for val_len bytes; */
/* if the stored attribute is longer than the declared template slot,*/
/* it writes beyond the end of the caller-provided template buffer. */
TEE_MemMove(cur + 8, attr_data, val_len); // BUG: OOB write
/* Advance by the caller-controlled val_len */
cur += sizeof(CK_ATTRIBUTE_TYPE) + sizeof(CK_ULONG) + val_len;
// BUG: if val_len is attacker-crafted, cur can jump past `end`
// causing the next loop iteration to read type/val_len OOB (up to 7 bytes)
}
}
Root cause:entry_get_attribute_value() advances its template-buffer cursor by the caller-supplied ulValueLen field without verifying the resulting pointer stays within the shared-memory buffer bounds, enabling both an OOB read of up to 7 bytes on the next iteration and an OOB write of a full attribute value on the current iteration.
Memory Layout
The template buffer is allocated in the TA heap as a shared-memory copy. Adjacent heap chunks in the OP-TEE allocator (bget) carry a small header. The 7-byte read overshoot is precisely sizeof(CK_ATTRIBUTE_TYPE) + sizeof(CK_ULONG) - 1 = 4 + 4 - 1 = 7: the minimum bytes needed to begin parsing a new attribute record straddling the chunk boundary.
TA HEAP BEFORE CORRUPTION:
[bget hdr 8B] [ template_buf 64B ] [bget hdr 8B] [next_chunk ...]
^ ^
template_buf end (template_buf + 0x40)
Attacker supplies last attribute entry with val_len = 0x38:
cur = template_buf + 0x00
type @ cur+0x00 (4B, valid)
val_len @ cur+0x04 = 0x38 (attacker-controlled)
value @ cur+0x08 (0x38 bytes, valid range)
next cur = 0x00 + 8 + 0x38 = 0x40 = end ← edge case, or set 0x39 to go 1B past
TA HEAP AFTER CRAFTED val_len = 0x3C (4 bytes past end):
[bget hdr 8B] [ template_buf 64B ][OOB READ 4-7B][bget hdr...][next_chunk ...]
^^^^^^^^^^^^^^^
cur now points here; loop reads
type (4B) and begins val_len (4B) = 7B OOB total
OOB WRITE SCENARIO:
Attacker sets val_len = 0x08 in template, but object stores attribute of size 0x40.
get_attribute() writes 0x40 bytes starting at cur+8, overflowing 0x38 bytes into
the adjacent bget chunk, corrupting its header or the next object's metadata.
[bget hdr 8B] [ template_buf 16B][==== 0x38B OVERFLOW ====][bget hdr CORRUPTED]
^^^^^^^^^^^^^^^^^^^^^^^^
attribute value written here by get_attribute()
Exploitation Mechanics
EXPLOIT CHAIN (PKCS#11 TA heap corruption):
1. Open a PKCS#11 session via libckteec / tee-supplicant from an unprivileged
Normal World process (requires access to /dev/tee0).
2. Create or reference a PKCS#11 object whose CKA_VALUE (or any large attribute)
is significantly larger than the ulValueLen the attacker declares in the template.
Example: object stores CKA_VALUE = 0x200 bytes.
3. Allocate the shared memory template buffer with a carefully chosen size
(e.g., 0x10 bytes) containing a single attribute record:
type = CKA_VALUE (0x00000011)
val_len = 0x08 (deliberately small, 8 bytes claimed)
value = [8 bytes padding]
4. Invoke PKCS11_CMD_GET_ATTRIBUTE_VALUE. entry_get_attribute_value() looks up
CKA_VALUE, finds 0x200 bytes in the object store, and calls:
TEE_MemMove(cur + 8, attr_data, 0x200);
This writes 0x200 - 0x08 = 0x1F8 bytes past the end of the 0x10-byte template,
overflowing into adjacent TA heap chunks.
5. Corrupt the bget allocator header of the next chunk:
- Overwrite the forward/backward size fields used by bget's coalescence logic.
- Target a chunk holding a TA function pointer table or session structure.
6. Trigger a second TA operation (e.g., C_FindObjects) that causes bget to coalesce
or split the corrupted chunk, writing attacker-controlled data into a TA code/data
pointer.
7. The next TA command dispatch follows the corrupted pointer → arbitrary code
execution within the Secure World.
OOB READ SIDE CHANNEL (alternative):
1. Set val_len in the last template entry so cur lands 1–7 bytes before end.
2. Loop condition (cur < end) is still true; code reads type+val_len spanning
beyond end, leaking up to 7 bytes of TA heap metadata (bget header, adjacent
object metadata) back to Normal World via the returned template buffer.
Patch Analysis
Three commits address the issue: e031c4e, 16926d5, and 149e8d7. The fix pattern is consistent: add an explicit bounds check before both the cursor advance and the attribute write-back.
/* BEFORE (vulnerable): ta/pkcs11/src/object.c */
while (cur < end) {
CK_ATTRIBUTE_TYPE type;
CK_ULONG val_len;
TEE_MemMove(&type, cur, sizeof(type));
TEE_MemMove(&val_len, cur + 4, sizeof(val_len));
/* No check: cur + 8 + val_len may exceed end */
rc = get_attribute(&obj_attrs, type, cur + 8, &val_len);
/* No check: val_len from object store may exceed allocated slot */
TEE_MemMove(cur + 8, attr_data, val_len);
cur += sizeof(CK_ATTRIBUTE_TYPE) + sizeof(CK_ULONG) + val_len;
}
/* AFTER (patched): commits e031c4e + 16926d5 + 149e8d7 */
while (cur < end) {
CK_ATTRIBUTE_TYPE type;
CK_ULONG val_len;
size_t slot_size;
/* Guard: ensure we can read the fixed header (8 bytes) */
if ((size_t)(end - cur) < sizeof(type) + sizeof(val_len))
return PKCS11_CKR_ARGUMENTS_BAD; // BUG FIXED: prevents OOB read
TEE_MemMove(&type, cur, sizeof(type));
TEE_MemMove(&val_len, cur + 4, sizeof(val_len));
slot_size = val_len; /* save caller's declared slot size */
/* Guard: ensure declared slot fits within remaining buffer */
if ((size_t)(end - cur) < sizeof(type) + sizeof(val_len) + slot_size)
return PKCS11_CKR_ARGUMENTS_BAD; // BUG FIXED: prevents cursor OOB
rc = get_attribute(&obj_attrs, type, cur + 8, &val_len);
/* Guard: clamp write-back to the declared slot, not the object's size */
if (rc == PKCS11_CKR_OK) {
if (val_len > slot_size) {
/* Attribute larger than template slot: report needed size, skip copy */
TEE_MemMove(cur + 4, &val_len, sizeof(val_len));
rc = PKCS11_CKR_BUFFER_TOO_SMALL;
} else {
TEE_MemMove(cur + 8, attr_data, val_len); // BUG FIXED: bounded copy
}
}
cur += sizeof(CK_ATTRIBUTE_TYPE) + sizeof(CK_ULONG) + slot_size;
// Advance by SLOT size (caller's), not the object's attribute size
}
The key insight of the fix: cursor advancement and write-back are both bounded by the caller-declared slot_size, not the object-internal attribute size. When the stored attribute is larger than the declared slot, the function now reports CKR_BUFFER_TOO_SMALL per the PKCS#11 specification rather than overflowing.
Detection and Indicators
Because exploitation occurs entirely within the Secure World, Normal World monitoring is blind to the memory corruption itself. Observable indicators include:
OP-TEE panic logs: A corrupted bget header during coalescence triggers panic("bget: corrupt"), visible in the secure console or forwarded via tee-supplicant log. Look for [TEE] E/TC: Panic at lines referencing ta/pkcs11.
Unexpected TEE_ERROR_TARGET_DEAD: The TA session dying with 0xFFFF3024 immediately after a C_GetAttributeValue call is a crash indicator.
Anomalous template sizes: Audit libckteec call traces for PKCS11_CMD_GET_ATTRIBUTE_VALUE where the returned buffer contains non-zero bytes beyond the declared ulValueLen — this indicates the OOB read returning heap metadata.
Immediate: Update OP-TEE OS to a version incorporating commits e031c4e562023fd9f199e39fd2e85797e4cbdca9, 16926d5a46934c46e6656246b4fc18385a246900, and 149e8d7ecc4ef8b.... These are merged in versions after 4.10.0. Verify your build includes all three commits; they address slightly different code paths within the same iteration loop.
If patching is not immediately possible:
Restrict access to /dev/tee0 and /dev/teepriv0 via udev rules to trusted system UIDs only. This blocks unprivileged Normal World callers from invoking the PKCS#11 TA at all.
Disable the PKCS#11 TA entirely if it is not required by your platform (CFG_PKCS11_TA=n in your OP-TEE build configuration).
For platform integrators: Re-sign and re-flash the OP-TEE OS image. Because the vulnerable code resides in a TA compiled into the OS image (not a separately loadable TA in most deployments), updating requires a full secure firmware update. Verify TA binary hashes match patched build outputs via your platform's secure boot chain before deployment.