Fedora Account System
Red Hat Associate
Red Hat Customer
AI_ONLY_REPORT package: libkcapi-1.5.0-3.el10 ------ Summary: Memory Corruption via Uncanceled AIO Requests on Error: libkcapi's one-shot AIO path can return an error before all submitted IOCBs are drained, allowing later kernel writes into caller-owned output buffers. Requirements to exploit: A consumer must use the one-shot AIO interfaces with `KCAPI_INIT_AIO` so the real AIO backend is active, hit an error after one or more IOCBs have been submitted or completed, and then free or reuse the referenced `outiov` buffers before all kernel completions finish. Component affected: `libkcapi-1.5.0-3.el10`, `lib/kcapi-kernel-if.c`, `_kcapi_aio_read_all()`, `_kcapi_aio_read_iov()`, `_kcapi_cipher_crypt_aio()` in the one-shot AIO path Version affected: `libkcapi-1.5.0-3.el10` when the handle is initialized with `KCAPI_INIT_AIO` and libkcapi enables its real kernel AIO backend Patch available: no released package fix established; proposed patch included below Version fixed: unknown Upstream coordination: Unknown. CVSS: CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:H - 6.8 (MEDIUM) AV:L - exploitation is local to the process using libkcapi. AC:L - once the real AIO path is active, the unsafe behavior is reached directly through documented error paths after request submission. PR:N - no elevated privileges are required beyond the ability to exercise the affected API in the local process. UI:N - no separate user interaction is required. S:U - the impact stays within the security scope of the calling process. C:L - late writes into reused buffers can expose limited process data. I:L - late kernel writes can alter caller memory after the library has reported failure. A:H - heap corruption or process termination is a realistic outcome. Impact: Moderate. This flaw can cause real process memory corruption, but it depends on an opt-in AIO configuration, kernel AIO availability, and an error path after submission rather than a default or easily reachable execution path. Under Red Hat's severity guidance, that is more consistent with Moderate impact than Important or Critical. Embargo: no Reason: the issue is local and configuration-dependent, requires explicit AIO use plus an error-path trigger, and does not present a straightforward remote compromise scenario. Acknowledgement: Aisle Research Vulnerability Details: In the affected one-shot AIO path, libkcapi can return failure before all submitted kernel requests are drained. `_kcapi_aio_read_all()` returns immediately on the first negative completion result, and `_kcapi_aio_read_iov()` returns immediately on a short `io_submit()` result even if some IOCBs were already submitted. `_kcapi_cipher_crypt_aio()` then propagates that error directly to the caller. If the caller treats the operation as failed and promptly frees or reuses `outiov[*].iov_base`, later kernel completions may still write into those buffers. ```c if (events[i].res < 0) { handle->aio.iocb_ret[events[i].data] = events[i].res; return (int)events[i].res; } ... ret = io_submit(handle->aio.aio_ctx, (long)iovlen, handle->aio.ciopp); if ((uint32_t)ret != iovlen) { if (ret < 0) { ret = -errno; kcapi_dolog(KCAPI_LOG_ERR, "io_read Error: %d\n", ret); return ret; } else { kcapi_dolog(KCAPI_LOG_ERR, "Could not sumbit AIO read\n"); return -EIO; } } ``` ```c ret = _kcapi_aio_read_iov(handle, outiov, process); if (ret < 0) return ret; ``` The issue is not established for the default synchronous path. Reachability depends on the caller enabling `KCAPI_INIT_AIO` so libkcapi uses the real kernel AIO backend. Steps to reproduce: 1. Build an ASAN-enabled harness that calls `kcapi_cipher_encrypt_aio()` or `kcapi_aead_decrypt_aio()` with multiple output `iovec` entries. 2. Initialize the handle with `KCAPI_INIT_AIO` so the real AIO path is active. 3. Force a deterministic short-submit path with an `LD_PRELOAD` wrapper around `syscall(__NR_io_submit, ...)` that returns `0 < ret < iovlen` after forwarding only the first `N` IOCBs. 4. Optionally fault-inject `io_getevents()` so one returned event has `res < 0` before all completions are consumed. 5. When the API returns an error, immediately free or reallocate the `outiov[*].iov_base` buffers. 6. Observe delayed writes into freed or reused memory, for example as an ASAN use-after-free, heap corruption, or a canary mismatch. Mitigation: Until a fix is available, avoid initializing affected handles with `KCAPI_INIT_AIO` and prefer the synchronous interfaces. If the real AIO path must remain enabled, applications should not immediately free or reuse `outiov` buffers after an error return. Proposed Fix: Drain all submitted requests before returning an error. A minimal approach is to record the first completion error, continue draining outstanding completions, and drain any successfully submitted subset after a short `io_submit()` result. ```diff diff --git a/lib/kcapi-kernel-if.c b/lib/kcapi-kernel-if.c — a/lib/kcapi-kernel-if.c +++ b/lib/kcapi-kernel-if.c @@ -420,6 +420,7 @@ int _kcapi_aio_read_all(struct kcapi_handle *handle, size_t toread, struct timespec *timeout) { + int first_err = 0; if (toread > KCAPI_AIO_CONCURRENT) return -EINVAL; @@ -440,8 +441,10 @@ int _kcapi_aio_read_all(struct kcapi_handle *handle, size_t toread, if (events[i].res < 0) { handle->aio.iocb_ret[events[i].data] = events[i].res; - return (int)events[i].res; + if (!first_err) + first_err = (int)events[i].res; + continue; } @@ -467,7 +470,7 @@ int _kcapi_aio_read_all(struct kcapi_handle *handle, size_t toread, toread -= (uint32_t)rc; } - return 0; + return first_err; } @@ -534,15 +537,20 @@ int _kcapi_aio_read_iov(struct kcapi_handle *handle, ret = io_submit(handle->aio.aio_ctx, (long)iovlen, handle->aio.ciopp); if ((uint32_t)ret != iovlen) { + size_t submitted = (ret > 0) ? (size_t)ret : 0; + int submit_err = (ret < 0) ? -errno : -EIO; if (ret < 0) { - ret = -errno; kcapi_dolog(KCAPI_LOG_ERR, "io_read Error: %d\n", ret); - return ret; } else { kcapi_dolog(KCAPI_LOG_ERR, "Could not sumbit AIO read\n"); - return -EIO; } + if (submitted) { + int drain = _kcapi_aio_read_all(handle, submitted, NULL); + if (submit_err == 0 && drain < 0) + submit_err = drain; + } + return submit_err; } ``` (Equivalent fix using `io_cancel` + confirmed drain is also acceptable.) ------ This report was generated using AI technology. Always review AI-generated content prior to use