so_splice: Synchronize so_unsplice() with so_splice()

so_unsplice() assumed that if SB_SPLICED is set in the receive buffer of
the first socket, then the splice is fully initialized.  However, that's
not true, and it's possible for so_unsplice() to race ahead of
so_splice().

Modify so_unsplice() to simply bail if the splice state is embryonic.

Reported by:	syzkaller
Reviewed by:	gallatin
Fixes:		a1da7dc1cd ("socket: Implement SO_SPLICE")
MFC after:	2 weeks
Differential Revision:	https://reviews.freebsd.org/D49814

(cherry picked from commit 992b18a9ec)
This commit is contained in:
Mark Johnston committed 2025-04-29 00:44:04 +00:00
1 parent dfbda7c53d
commit c0c5d01e53
1 file changed
+14 -1
+14 -1
View File
@@ -1721,6 +1721,8 @@ so_splice(struct socket *so, struct socket *so2, struct splice *splice)
/*
* Transfer any data already present in the socket buffer.
*/
KASSERT(sp->state == SPLICE_INIT,
("so_splice: splice %p state %d", sp, sp->state));
sp->state = SPLICE_QUEUED;
so_splice_xfer(sp);
return (0);
@@ -1749,8 +1751,19 @@ so_unsplice(struct socket *so, bool timeout)
SOCK_UNLOCK(so);
return (ENOTCONN);
}
so->so_rcv.sb_flags &= ~SB_SPLICED;
sp = so->so_splice;
mtx_lock(&sp->mtx);
if (sp->state == SPLICE_INIT) {
/*
* A splice is in the middle of being set up.
*/
mtx_unlock(&sp->mtx);
SOCK_RECVBUF_UNLOCK(so);
SOCK_UNLOCK(so);
return (ENOTCONN);
}
mtx_unlock(&sp->mtx);
so->so_rcv.sb_flags &= ~SB_SPLICED;
so->so_splice = NULL;
SOCK_RECVBUF_UNLOCK(so);
SOCK_UNLOCK(so);