## Summary `bcm2835_audio_release()` calls `vchi_service_close()` and then unconditionally calls `vchi_service_release()` with the same service handle. In the VCHI shim implementation, a successful `vchi_service_close()` calls `service_free(service)`. The subsequent `vchi_service_release()` therefore dereferences a freed `SHIM_SERVICE_T` object when it reads `service->handle`. This is a real kernel heap-use-after-free during `bcm2835_audio` device detach. It can result in a kernel panic or other undefined kernel behavior. I have not identified a remote network trigger. I am reporting this privately because it is a kernel use-after-free and I would appreciate the Security Team's classification. ## Bug Details ### Vulnerable caller At `sys/arm/broadcom/bcm2835/bcm2835_audio.c:358`, `bcm2835_audio_release()` checks the service handle. At lines 363-367 it closes the service and then releases the same handle: ```c if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) { success = vchi_service_close(sc->vchi_handle); if (success != 0) BCM2835_LOG_ERROR(sc, "vchi_service_close failed: %d\\n", success); vchi_service_release(sc->vchi_handle); sc->vchi_handle = VCHIQ_SERVICE_HANDLE_INVALID; } ``` ### Free in `vchi_service_close()` At `sys/contrib/vchiq/interface/vchiq_arm/vchiq_shim.c:694-708`, a successful close frees the service object: ```c int32_t vchi_service_close(const VCHI_SERVICE_HANDLE_T handle) { int32_t ret = -1; SHIM_SERVICE_T *service = (SHIM_SERVICE_T *)handle; if (service) { VCHIQ_STATUS_T status = vchiq_close_service(service->handle); if (status == VCHIQ_SUCCESS) { service_free(service); service = NULL; } ret = vchiq_status_to_vchi(status); } return ret; } ``` `service_free()` at approximately `sys/contrib/vchiq/interface/vchiq_arm/vchiq_shim.c:625` deletes the queue and frees the `SHIM_SERVICE_T` object. ### Use after free in `vchi_service_release()` At `sys/contrib/vchiq/interface/vchiq_arm/vchiq_shim.c:851-860`, `vchi_service_release()` casts the handle and reads `service->handle`: ```c int32_t vchi_service_release(const VCHI_SERVICE_HANDLE_T handle) { int32_t ret = -1; SHIM_SERVICE_T *service = (SHIM_SERVICE_T *)handle; if (service) ret = vchiq_status_to_vchi( vchiq_release_service(service->handle)); return ret; } ``` When `vchi_service_close()` returns success, `service` points to freed memory before `vchi_service_release()` is called. The read of `service->handle` is consequently a use-after-free. The vulnerable call chain is: ```text bcm2835_audio_detach() -> bcm2835_audio_release() [bcm2835_audio.c:358] -> vchi_service_close(sc->vchi_handle) [bcm2835_audio.c:363] -> vchiq_close_service(service->handle) [vchiq_shim.c:699] -> service_free(service) [vchiq_shim.c:701] -> vchi_service_release(sc->vchi_handle) [bcm2835_audio.c:367] -> vchiq_release_service(service->handle) [vchiq_shim.c:856] ``` ## Impact and Severity The direct impact is a kernel heap-use-after-free during audio device detach. Depending on allocator state and the contents of the freed object, this may cause a kernel panic, failed driver unload, or other undefined kernel behavior. Preliminary assessment: - **Confidentiality:** No impact observed. - **Integrity:** No impact observed. - **Availability:** Possible local kernel panic or denial of service. - **Privilege requirement:** Device detach or driver unload likely requires root or equivalent administrative privilege. - **Remote exploitability:** Not demonstrated and unlikely through network traffic. - **Security classification:** Likely a local kernel reliability issue rather than a remotely exploitable DoS under the stated FreeBSD policy. It may be suitable for a normal bug report or an Errata Notice rather than a Security Advisory. The use-after-free is nevertheless in kernel code and could warrant security handling during initial triage. ## Proposed Fix The minimal fix is to avoid calling `vchi_service_release()` after a successful `vchi_service_close()`, because the close operation has already freed the shim service object: ```diff --- a/sys/arm/broadcom/bcm2835/bcm2835_audio.c +++ b/sys/arm/broadcom/bcm2835/bcm2835_audio.c @@ -360,12 +360,12 @@ bcm2835_audio_release(struct bcm2835_audio_info *sc) if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) { success = vchi_service_close(sc->vchi_handle); if (success != 0) BCM2835_LOG_ERROR(sc, "vchi_service_close failed: %d\\n", success); - vchi_service_release(sc->vchi_handle); sc->vchi_handle = VCHIQ_SERVICE_HANDLE_INVALID; } ``` The final fix should confirm the intended VCHI lifecycle semantics for a failed `vchi_service_close()`. If `vchi_service_release()` is required only after a failed close, the implementation should call it conditionally and only while the handle remains valid. It should never be called after a successful close has freed the shim service. Reporter: Xuqing Yang
^Triage: note that this Problem Report contains an inline patch. (These days, we prefer that patches be submitted as Attachments.)
Created attachment 273354 [details] patch for the uaf bug
Created attachment 273481 [details] patch for the uaf bug ^Triage: convert to git diff format (now preferred).
Created attachment 273678 [details] improved patch I improved the patch. The handling in uaudio_attach() is actually useless because audio_rev is fetched in uaudio_mixer_fill_info() only. I just swapped the udev argument for uaa. Can you test this on the "main" branch? Otherwise you'll need to modify it a bit.
Created attachment 273680 [details] uaf patch Oops. My last comment and patch was meant for 294803...
Mark, the patch and the analysis looks good to me. vchi_service_close() casts "handle" to "service": SHIM_SERVICE_T *service = (SHIM_SERVICE_T *)handle; And then if vchiq_close_service() succeeds, it frees "service", i.e., handle. The fix seems correct even though I don't have the HW to test it.
(In reply to Christos Margiolis from comment #6) The patch fixes the UAF, yes, but that vchi_release_service() releases a reference which might block vchi_service_close() from completing successfully. So I'd maybe also add a /* XXX vchi_service_release()? */ comment above the vchi_service_close() call.