Bug 2569239
| Summary: | CVE-2026-112314 krb5: MIT krb5 GSS-RPC client memory leak on XID mismatch retries [fedora-all] | ||
|---|---|---|---|
| Product: | [Fedora] Fedora | Reporter: | Samuele Negrini <snegrini> |
| Component: | krb5 | Assignee: | Julien Rische <jrische> |
| Status: | NEW --- | QA Contact: | Fedora Extras Quality Assurance <extras-qa> |
| Severity: | medium | Docs Contact: | |
| Priority: | medium | ||
| Version: | rawhide | CC: | abokovoy, antorres, ftrivino, jrische, j, sbose, ssorce |
| Target Milestone: | --- | Keywords: | Security, SecurityTracking |
| Target Release: | --- | ||
| Hardware: | Unspecified | ||
| OS: | Unspecified | ||
| Whiteboard: | {"flaws": ["29d41794-6ec8-44ab-a1ad-6623791a78d9"]} | ||
| Fixed In Version: | Doc Type: | --- | |
| Doc Text: | Story Points: | --- | |
| Clone Of: | Environment: | ||
| Last Closed: | Type: | --- | |
| Regression: | --- | Mount Type: | --- |
| Documentation: | --- | CRM: | |
| Verified Versions: | Category: | --- | |
| oVirt Team: | --- | RHEL 7.3 requirements from Atomic Host: | |
| Cloudforms Team: | --- | Target Upstream Version: | |
| Embargoed: | |||
| Bug Depends On: | |||
| Bug Blocks: | 2478446 | ||
Disclaimer: Community trackers are created by Red Hat Product Security team on a best effort basis. Package maintainers are required to ascertain if the flaw indeed affects their package, before starting the update process. AI_ONLY_REPORT package: krb5-1.21.3-8.el10_0 ------ Summary: Unbounded Memory Leak in `clnttcp_call` on XID Mismatch (plus retry leak on `AUTH_REFRESH`): a malicious RPC peer or on-path attacker can cause unbounded client-side memory growth and denial of service by sending accepted TCP RPC replies with mismatched transaction IDs and non-empty verifiers. Requirements to exploit: An application using the TCP RPC client path in `krb5-1.21.3-8.el10_0` must issue a call through `clnttcp_call`, and the attacker must be able to act as the RPC server or inject syntactically valid reply records on the connection before a matching reply is processed. Component affected: `krb5-1.21.3-8.el10_0`, `src/lib/rpc/clnt_tcp.c`, `clnttcp_call` (with verifier allocation reached through `xdr_opaque_auth()` and `xdr_bytes()`) Version affected: `krb5-1.21.3-8.el10_0` when the TCP RPC client path in `clnttcp_call` is used Patch available: no released package fix established; proposed patch included below Version fixed: unknown Upstream coordination: Not notified. CVSS: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - 7.5 (HIGH) AV:N - A malicious RPC peer, or an attacker able to inject replies on the network path, can trigger the flaw remotely. AC:L - The attack requires only syntactically valid `REPLY` messages with a mismatched `rm_xid` and a non-empty verifier. PR:N - No privileges on the client are required. UI:N - No additional user interaction is required once the vulnerable client performs the RPC call. S:U - The impact is confined to the client process using the affected library path. C:N - No confidentiality impact is established by the available evidence. I:N - No integrity impact is established by the available evidence. A:H - Each mismatched accepted reply can leak up to `MAX_AUTH_BYTES` (`400`) and the mismatch loop is unbounded for the duration of the call, allowing sustained memory growth and possible process termination. Impact: Moderate. Red Hat classifies remote denial-of-service issues as Important when they are easy and broadly exploitable, but this case is narrower: it affects a specific client-side RPC path, requires an attacker-controlled or intercepted peer, and the demonstrated consequence is denial of service of the consuming client process only. The available evidence does not establish confidentiality, integrity, or broader system compromise. Embargo: no Reason: This is an availability-only, client-side issue that requires a malicious RPC server or an on-path attacker while the affected TCP RPC client path is in use; the current evidence does not show broad compromise or wormable behavior. Acknowledgement: Aisle Research Vulnerability Details: In `clnttcp_call`, reply headers are decoded in a loop until `reply_msg.rm_xid == x_id`. For each iteration, `reply_msg.acpted_rply.ar_verf` is reset to `gssrpc__null_auth` before `xdr_replymsg()` decodes the reply. When a syntactically valid accepted reply carries a non-matching XID, the loop continues without freeing the verifier buffer that may have been allocated during decode. ```c while (TRUE) { reply_msg.acpted_rply.ar_verf = gssrpc__null_auth; reply_msg.acpted_rply.ar_results.where = NULL; reply_msg.acpted_rply.ar_results.proc = xdr_void; if (! xdrrec_skiprecord(xdrs)) return (ct->ct_error.re_status); /* now decode and validate the response header */ if (! xdr_replymsg(xdrs, &reply_msg)) { /* Free some stuff allocated by xdr_replymsg() * to avoid leaks, since it may allocate * memory from partially successful decodes. */ enum xdr_op op = xdrs->x_op; xdrs->x_op = XDR_FREE; xdr_replymsg(xdrs, &reply_msg); xdrs->x_op = op; if (ct->ct_error.re_status == RPC_SUCCESS) continue; return (ct->ct_error.re_status); } if (reply_msg.rm_xid == x_id) break; } ``` The final verifier cleanup runs only after the loop exits, and the retry path can also bypass that cleanup: ```c else { /* maybe our credentials need to be refreshed ... */ if (refreshes-- && AUTH_REFRESH(h->cl_auth, &reply_msg)) goto call_again; } /* free verifier ... */ if ((reply_msg.rm_reply.rp_stat == MSG_ACCEPTED) && (reply_msg.acpted_rply.ar_verf.oa_base != NULL)) { xdrs->x_op = XDR_FREE; (void)xdr_opaque_auth(xdrs, &(reply_msg.acpted_rply.ar_verf)); } ``` `xdr_opaque_auth()` decodes `ar_verf` through `xdr_bytes()`, which allocates `oa_base` on decode and releases it only under `XDR_FREE`. On an XID mismatch, the next loop iteration overwrites `ar_verf` before that free occurs, so the previous pointer is lost. A second leak exists when `AUTH_REFRESH()` succeeds and jumps to `call_again` before verifier cleanup. The retry-path leak is bounded by the refresh count and is secondary; the main issue is the unbounded mismatch loop. The available evidence supports availability impact only. Steps to reproduce: 1. Build with ASan, or run under Valgrind, and exercise any client path that uses `clnttcp_call`. 2. Connect the client to a controlled TCP RPC server, or position an on-path interceptor, that repeatedly sends syntactically valid `REPLY` records with `rm_direction = REPLY`, `rp_stat = MSG_ACCEPTED`, `ar_verf.oa_length > 0` (up to `MAX_AUTH_BYTES`), and `rm_xid != client x_id`. 3. Keep sending mismatched replies while the client waits for the legitimate response. 4. Observe monotonic memory growth in the client process through RSS, ASan, or Valgrind. 5. Optionally, return replies that trigger an auth error followed by a successful `AUTH_REFRESH()` to reproduce the retry-path leak. Mitigation: No complete runtime mitigation is established in the package. Exposure can be reduced by limiting this TCP RPC client path to trusted servers and protected networks, and by using conservative RPC timeouts so a waiting call cannot be held open indefinitely. These measures reduce exposure but do not remove the underlying leak. Proposed Fix: Free the verifier for non-matching accepted replies before the next decode, and free it before retrying `call_again` on successful `AUTH_REFRESH()`. ```diff diff --git a/src/lib/rpc/clnt_tcp.c b/src/lib/rpc/clnt_tcp.c — a/src/lib/rpc/clnt_tcp.c +++ b/src/lib/rpc/clnt_tcp.c @@ -320,6 +320,15 @@ clnttcp_call(CLIENT *h, rpcproc_t proc, xdrproc_t xdr_args, void *args_ptr, if (reply_msg.rm_xid == x_id) break; + /* Free verifier from non-matching replies before next decode. */ + if ((reply_msg.rm_reply.rp_stat == MSG_ACCEPTED) && + (reply_msg.acpted_rply.ar_verf.oa_base != NULL)) { + enum xdr_op op = xdrs->x_op; + xdrs->x_op = XDR_FREE; + (void)xdr_opaque_auth(xdrs, &(reply_msg.acpted_rply.ar_verf)); + xdrs->x_op = op; + } } @@ -340,8 +349,18 @@ clnttcp_call(CLIENT *h, rpcproc_t proc, xdrproc_t xdr_args, void *args_ptr, else { /* maybe our credentials need to be refreshed ... */ if (refreshes- && AUTH_REFRESH(h->cl_auth, &reply_msg)) + if (refreshes-- && AUTH_REFRESH(h->cl_auth, &reply_msg)) { + /* Free verifier before retrying call_again. */ + if ((reply_msg.rm_reply.rp_stat == MSG_ACCEPTED) && + (reply_msg.acpted_rply.ar_verf.oa_base != NULL)) { + enum xdr_op op = xdrs->x_op; + xdrs->x_op = XDR_FREE; + (void)xdr_opaque_auth(xdrs, + &(reply_msg.acpted_rply.ar_verf)); + xdrs->x_op = op; + } goto call_again; + } } ``` ------ This report was generated using AI technology. Always review AI-generated content prior to use