{"schema":"threatline.record/2","record":{"id":"GHSA-jhv5-m83m-hxhq","aliases":["GHSA-jhv5-m83m-hxhq"],"summary":"PKCS#11 key enumeration writes past the fixed 64-entry `keys[]` array in `collect_keys()`","details":"This issue was found by Anthropic using Claude to study the security of open-source software, validated manually by\r\nme from Ada Logics\r\n\r\n### Summary\r\n\r\n`P11EnumKeysState` embeds a fixed array `NCryptKeyEnum keys[MAX_KEYS]` with `MAX_KEYS = 64`, and\r\nthe whole structure is allocated as a single `calloc` in `NCryptP11EnumKeys()`\r\n(`ncrypt_pkcs11.c:819`, 29720 bytes on x86-64).\r\n\r\n`collect_keys()` fills that array with two independent loops: an outer loop over up to `MAX_SLOTS`\r\n(64) slots and, per slot, an inner loop over up to `MAX_KEYS_PER_SLOT` (64) objects returned by\r\n`C_FindObjects`. Each discovered object is written through\r\n\r\n```c\r\nNCryptKeyEnum* key = &state->keys[state->nKeys];   /* ncrypt_pkcs11.c:525 */\r\n```\r\n\r\nand `state->nKeys++` runs at `ncrypt_pkcs11.c:568`. There is no comparison of `state->nKeys` against\r\n`MAX_KEYS` anywhere in the function, so the product of the two loop bounds (4096) can be written\r\ninto a 64-entry array. The four `ulValueLen` checks after `object_load_attributes()` bound the\r\nlength of each attribute inside one entry; they do not bound the index of the entry.\r\n\r\nThe out-of-range `key` pointer is passed straight to the PKCS#11 module as the destination of\r\n`CKA_ID` and `CKA_LABEL` in `C_GetAttributeValue`, so the first excess object is written by the\r\nmodule's own `memcpy` at 392 bytes into an entry that already starts at the end of the allocation.\r\n`state->keyIndex` — the cursor `NCryptP11EnumKeys()` later uses to walk `keys[]` at\r\n`ncrypt_pkcs11.c:855` — immediately follows the array and is the first field overwritten.\r\n\r\nThe path is the ordinary smartcard-logon enumeration:\r\n`freerdp_smartcard_list()` (`client/common/smartcard_cli.c:30`) →\r\n`smartcard_enumerateCerts()` (`libfreerdp/core/smartcardlogon.c:896`) →\r\n`list_provider_keys()` (`:412`) → `NCryptEnumKeys()` → `collect_keys()`.\r\n`WITH_PKCS11` is ON by default on Linux.\r\n\r\n### Primitive\r\n\r\nLinear forward heap write, 456 bytes per excess entry, up to 4032 excess entries (~1.8 MB) past a\r\n29720-byte allocation. Per entry the attacker-supplied bytes are `CKA_ID` (up to 64, arbitrary),\r\n`CKA_LABEL` (up to 256, arbitrary), `CKA_KEY_TYPE` (8, constrained), `CK_SLOT_INFO` (112 bytes of\r\nslot description/manufacturer, token/reader-supplied) and the module-assigned slot ID (8). The\r\nstarting offset is deterministic (`keys[64]`, i.e. the end of the allocation) and the attacker\r\nchooses the distance by choosing how many key objects the token set presents. The corruption\r\nhappens early in client startup, so the set of neighbouring allocations is small and fairly\r\ndeterministic. No function pointer or vtable is written.\r\n\r\n### Attacker position and scope caveats\r\n\r\nThe triggering data is token data, not FreeRDP configuration: the number of `CKO_PUBLIC_KEY`\r\nobjects with `CKA_VERIFY = TRUE` visible across the enumerated slots, and their `CKA_ID` /\r\n`CKA_LABEL` contents. Enumeration opens a serial session and never calls `C_Login`, so no PIN is\r\nneeded. The realistic actor is therefore someone who controls the contents of a token set the\r\nvictim's client enumerates — a supplied or issued smartcard, a multi-slot token, or a p11-kit\r\nconfiguration aggregating several tokens/modules. That is a physical/local vector, not a malicious\r\nRDP server, gateway or `.rdp` file. The user must be using smartcard logon or listing smartcard\r\ncertificates. The PKCS#11 module itself is unmodified and legitimate in the reproduction; a\r\nmalicious module is not required.\r\n\r\nNote that a single fully populated card in one reader yields exactly 64 objects, which fills the\r\narray without overflowing; two tokens (or one token exposing two slots) is the minimum.\r\n\r\n### Reproduction\r\n\r\n[ANT-2026-GP8MQ9VN-e2e.zip](https://github.com/user-attachments/files/32197155/ANT-2026-GP8MQ9VN-e2e.zip)\r\n\r\n\r\nUbuntu 24.04, clang-20 + AddressSanitizer, FreeRDP built from source at the commit above with the\r\ndefault `-DWITH_PKCS11=ON` and the X11 client. The token set is the distro SoftHSM2 PKCS#11 module,\r\nunmodified, initialised with two tokens of 64 EC key pairs each (128 public-key objects across two\r\nslots). FreeRDP sources are unmodified and no FreeRDP code is stubbed or patched.\r\n\r\n```\r\nunzip ANT-2026-GP8MQ9VN-e2e.zip && cd ANT-2026-GP8MQ9VN\r\ndocker build -t ant-2026-gp8mq9vn docker-e2e/\r\ndocker run --rm ant-2026-gp8mq9vn\r\n```\r\n\r\nNo special `docker run` flags are needed. The container runs the shipped client twice: once as\r\n`xfreerdp /list:smartcard` with no module argument (the module is discovered through\r\np11-kit-proxy, i.e. the stock Linux configuration) and once with\r\n`/list:smartcard:pkcs11-module:<path>`. Both abort.\r\n\r\nReproducer attached as `ANT-2026-GP8MQ9VN-e2e.zip`.\r\n\r\n### Observed output\r\n\r\nFrom the first run (`xfreerdp /list:smartcard`, default configuration):\r\n\r\n```\r\n==13==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7ae5b0be7798 at pc 0x5b28505ab484\r\nWRITE of size 1 at 0x7ae5b0be7798 thread T0\r\n    #0 in memcpy\r\n    #4 0x7c25b1131ce3 in C_GetAttributeValue (/usr/lib/x86_64-linux-gnu/softhsm/libsofthsm2.so+0x26ce3)\r\n    #5 0x7825b0a74338  (/lib/x86_64-linux-gnu/p11-kit-proxy.so+0x3a338)\r\n    #6 0x7c25b4b8b696 in object_load_attributes winpr/libwinpr/ncrypt/ncrypt_pkcs11.c:284:13\r\n    #7 0x7c25b4b8b696 in collect_keys winpr/libwinpr/ncrypt/ncrypt_pkcs11.c:534:10\r\n    #8 0x7c25b4b882df in NCryptP11EnumKeys winpr/libwinpr/ncrypt/ncrypt_pkcs11.c:845:9\r\n    #9 0x7c25b5127048 in list_provider_keys libfreerdp/core/smartcardlogon.c:412:9\r\n   #10 0x7c25b5125b01 in smartcard_hw_enumerateCerts libfreerdp/core/smartcardlogon.c:705:9\r\n   #11 0x7c25b5125b01 in smartcard_enumerateCerts libfreerdp/core/smartcardlogon.c:896:6\r\n   #12 0x7c25b568a51f in freerdp_smartcard_list client/common/smartcard_cli.c:30:7\r\n   #13 0x7c25b5668f80 in evaluate_result client/common/cmdline.c:1885:4\r\n   #15 0x5b28505f0da7 in main client/X11/cli/xfreerdp.c:81:8\r\n\r\n0x7ae5b0be7798 is located 384 bytes after 29720-byte region [0x7ae5b0be0200,0x7ae5b0be7618)\r\nallocated by thread T0 here:\r\n    #1 0x7c25b4b88171 in NCryptP11EnumKeys winpr/libwinpr/ncrypt/ncrypt_pkcs11.c:819:30\r\n```\r\n\r\n`29712 + 392 = 29720 + 384`, i.e. the write is the `CKA_ID` field of `keys[64]`.\r\n\r\n### Credit\r\n\r\nFound by Anthropic using Claude to study the security of open-source software, validated manually by\r\nAda Logics. Disclosed under Anthropic's coordinated vulnerability disclosure policy\r\n(https://www.anthropic.com/coordinated-vulnerability-disclosure, 90 days). Please credit Claude and\r\nAda Logics in any advisory.","categories":["Unclassified"],"product":"FreeRDP","vendor":"","ecosystem":null,"affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/FreeRDP/FreeRDP/security/advisories/GHSA-jhv5-m83m-hxhq","source_modified_at":"2026-09-28T08:51:31Z","fetched_at":"2026-10-23T21:47:51.402276Z","package":{"ecosystem":"c","name":"FreeRDP"},"range":"<= 3.31.1"}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/FreeRDP/FreeRDP/security/advisories/GHSA-jhv5-m83m-hxhq","source_modified_at":"2026-09-28T08:51:31Z","fetched_at":"2026-10-23T21:47:51.402276Z","package":{"ecosystem":"c","name":"FreeRDP"},"version":"3.32.0"}],"cvss":null,"severity":"Medium","kev":{"listed":false},"epss":{"probability":null,"percentile":null,"score_date":null},"published_at":"2026-09-28T08:51:31Z","modified_at":"2026-09-28T08:51:31Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/FreeRDP/FreeRDP/security/advisories/GHSA-jhv5-m83m-hxhq","fetched_at":"2026-10-23T21:47:51.402276Z","source_modified_at":"2026-09-28T08:51:31Z"}],"sources":["GitHub Advisory Database"],"references":[],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":null,"confidence":"unknown","reviewed":false},"change_timestamp":"2026-09-28T08:51:31Z","content_hash":"39227fdebd4b6ba5c63338d597cae788b6ee158195a34fcd851fafcae0951982"}}
