In flow_divert_pcb_init_internal
, a flow divert PCB is created and added to the desired socket below:
fd_cb = flow_divert_pcb_create(so); // 1
if (fd_cb != NULL) {
so->so_fd_pcb = fd_cb;
so->so_flags |= SOF_FLOW_DIVERT;
// ...
error = flow_divert_pcb_insert(fd_cb, group_unit); // 2
if (error) {
so->so_fd_pcb = NULL;
so->so_flags &= ~SOF_FLOW_DIVERT;
FDRELEASE(fd_cb); // 3
} else {
flow_divert_pcb_create
(1) creates a flow divert PCB and initializes it with a refcount of 1 to represent the socket's ownership. flow_divert_pcb_init_internal
has a reference to the PCB on the stack with variable fd_cb
that is otherwise unaccounted for with the assumption that fd_cb
should be alive for the duration of the entire function thus the incref/decref can be elided. But flow_divert_pcb_insert
(2) drops the socket lock, so another thread can call disconnectx
on the socket, deleting the PCB from the socket after dropping its only reference. This leaves the fd_cb
pointer dangling pointing to freed memory. If flow_divert_pcb_insert
fails, as in this testcase (no groups available), the FD_RELEASE
(3) call will (among other possible outcomes) modify a freed buffer.
The syscalls involved are available inside the app sandbox on iOS 15.4.
This bug was patched in iOS 15.5 (All credit goes to @NedWilliamson for this amazing bug.)