Full Report
FreeBSD supports asynchronous I/O (AIO) with POSIX syscalls. Naturally, with asynchronous actions, reference counts are important to make sure objects aren't deleted too early. The code path used the AIO takes a reference to ucred. However, this is NOT released when the error path is taken. Although this looks like a simple memory leak, arbitrary increments of a 32 bit reference counter could lead to the invalid releasing of the object! To hit this error path, simply send a file that is not a regular file or directory. At this point, there is a theoretical issue with the reference count being overflowable. But, is it practical? The author lists 5 things to check: The reference count is NOT checked for an overflow. It is feasible to do this in a reasonable time. The overflow can cause a premature free to give a double free or use after vulnerability. Flexibility on the heap to allocate a useful object in this space. The object itself is useful with the allocation. The overflow is checked with KASSERT, which is NOT enabled on production builds. Additionally, this is a 32 bit integer, making it possible to perform in a reasonable time. Can we trigger the free? With normal use case, a job will clean up after itself, releasing the object too soon. After this, the author goes through the internals of the BSD heap allocator. There are specialized zones and general purpose heaps. The object being exploited belongs in the general purpose heap, allowing for many standard gadgets. For exploitation, this object is literally security related! Shouldn't be too bad to swap in a fake object to control the ucred for a different uid. The object has many pointers in it, so a simple swap doesn't work; since BSD doesn't have KASLR, this could be exploited fine though. Instead of going for the swap technique, the author had a different plan: keep the pointers the same and use a partial overwrite to cause havoc. Since FreeBSD doesn't zero on free, this is doable. The author didn't go this route though. Originally, using crcopysafe was the way to free the object. Instead of doing this in one go, they had a different idea. Let's use the free to give us a kernel info leak. Then, once we have the leak, we can recreate an identical version of this back in the whole of a legitimate ucred we want to use. Now, we have escalated privileges. They found that the cap_ioctls_limit can be used to write lots of custom data and cap_ioctls_get can be used to retrieve required data. The flow for allocation of the leak is as follows: Allocate the object and trigger the vulnerability to create the whole. Create the fake credential using cap_ioctls_limit over the UAF object. Collect the AIO job in order to free the buffer. Allocate another fresh set of credentials. This will go into the UAF file buffer, giving us an amazing memory leak. To put the ucred back in, we need to abuse this UAF. This can be done with the following steps: Free the file points to ucred. The ucred is now free. Create a new file with cap_ioctls_limit to swap in a fake ucred. The previous step is possible because we know most of the pointers from the info leak above. You are now root! Overall, an interesting vulnerability! The exploitation of this was straight forward, once the ref count was realized to be wrappable and data controllable within it. The control around the allocations makes this very nice. Great writeup!
Analysis Summary
# Vulnerability: FreeBSD AIO Reference Count Wrap Leading to Privilege Escalation
## CVE Details
- CVE ID: CVE-2022-23090
- CVSS Score: Not explicitly provided, but described as leading to Local Privilege Escalation (LPE). Given the impact, it would likely be High (7.0-8.9) or Critical (9.0-10.0).
- CWE: CWE-401 (Missing Release of Resource to Caller/Memory Leak) leading to CWE-416 (Use After Free) or CWE-192 (Improper Use of Negative Value/Integer Underflow).
## Affected Systems
- Products: FreeBSD Kernel
- Versions: FreeBSD 11.0 through FreeBSD 13.0.
- Configurations: Any system running the vulnerable kernel version supporting Asynchronous I/O (AIO) POSIX syscalls.
## Vulnerability Description
The vulnerability resides in the kernel function `aio_aqueue`, which handles asynchronous I/O job submission. When processing certain I/O operations, if an error path is taken after resolving file descriptors but before the job is successfully queued, a reference count (`refcount`) on the calling process's credential structure (`ucred`) is incorrectly incremented via `crhold(td->td_ucred)` but never subsequently decremented upon failing out (`aquueue_fail` path).
Since this is a 32-bit reference counter and no overflow check exists (especially in production builds where `KASSERT` is disabled), this count can be arbitrarily incremented until it wraps around to zero or a small positive value. Wrapping the counter effectively triggers a premature free (Use-After-Free) on the `ucred` object back into the general-purpose heap, allowing an attacker with local privileges to control the allocation that reclaims this memory space, leading to privilege escalation.
## Exploitation
- Status: Proof-of-Concept (PoC) exploit available (demonstrated Local Privilege Escalation).
- Complexity: Medium (Requires advanced kernel heap manipulation techniques, including info leaks via UAF and subsequent controlled allocation/swapping using Capsicum features like `cap_ioctls_limit`/`cap_ioctls_get`).
- Attack Vector: Local (An authenticated, non-privileged user is required).
### Impact Details
The exploit successfully achieves full control over the `ucred` structure, allowing the attacker to replace their current credential with a root credential structure, resulting in UID 0 access.
- Confidentiality: High (Kernel memory disclosure likely occurs during heap spray/leak phase to locate targets).
- Integrity: High (Full system integrity compromise resulting in root execution).
- Availability: Low (The primary impact is compromise, not denial of service).
## Remediation
### Patches
- The fix was merged into supported branches by late July 2022. Systems should be updated to versions released after this date.
- FreeBSD 13.1-RELEASE and newer.
- Patched versions of `stable/12` (backported around June 27, 2022).
- **Note:** FreeBSD 11.x will not receive an official backport due to end-of-life policy.
### Workarounds
- Disabling AIO support explicitly (if possible, though this is difficult without recompiling the kernel or disabling AIO capabilities for users).
- Restricting local user access to prevent exploitation attempts.
## Detection
- **Indicators of Compromise:** Unexpected privilege escalation to root without authentication, unusual system calls related to AIO operations preceding privilege changes, or kernel panics if exploitation attempts are flawed.
- **Detection Methods and Tools:** Monitoring kernel memory allocations and heap activity (if advanced kernel tracing is available). Specific analysis should focus on atypical use of `cap_ioctls_limit`/`cap_ioctls_get` operations following an error in AIO syscall queuing.
## References
- https://www.freebsd.org/security/advisories/FreeBSD-SA-22:10.aio.asc
- accessvector.net/2022/freebsd-aio-lpe.html (Defanged due to article context: `accessvector.net/2022/freebsd-aio-lpe.html`)