The main problem is with the handling of errors from unp_externalize().
It turns out that this was quite broken, and unfortunately it's easy to
trigger such errors (e.g., by setting a low per-process fd limit with
setrlimit()).
In non-peek mode, uipc_soreceive_stream_or_seqpacket() cuts a bunch of
mbufs from the head of the socket buffer, to be consumed by userspace.
When unp_externalize() returns an error, we splice the removed mbuf
chain back onto the head of the socket buffer. This is expensive, but
that's ok since such errors are rare.
The problem is that this cutting is not correctly implemented: it does
not clear the "next" pointer for the last mbuf in the chain, so it
still points to the first mbuf still resident in the socket buffer.
This means that mc_init_m() creates a chain that still includes the rest
of the socket buffer, so splicing the chain back into the socket buffer
does not work properly.
Fix this: fully detach the control chain from the socket buffer so that
we can safely use mc_init_m(). Then, incrementally add data mbufs,
taking care to handle "part".
Fix some related bugs while here:
- Don't swallow the error if unp_externalize() fails and there's nothing left in the socket buffer (i.e., control->m_next == NULL).
- Roll back changes to the partially read mbuf.