unix stream: Fix use-after-free crashes
Subsystems (3)
PATCH 1/1 unix stream: Fix use-after-free crashes
Commit 0856a30409 (Scm: Remove unnecessary pid & credential references in Unix socket's send and receive path) introduced a use-after-free bug. It happens that if skb is consumed and destructed by the receive side before unix_stream_sendmsg finishes its job.
Signed-off-by Zheng Yan <[email protected]>
Reported-by Jiri Slaby <[email protected]>
Tested-by Sedat Dilek <[email protected]>
---
net/unix/af_unix.c +20 -9
@@ -1577,6 +1577,7 @@ static int unix_stream_sendmsg(struct kiocb *kiocb, struct socket *sock,int sent =0 ;struct scm_cookie tmp_scm;bool fds_sent = false; +bool scm_ref = true;int max_level;if (NULL == siocb->scm)
@@ -1637,12 +1638,21 @@ static int unix_stream_sendmsg(struct kiocb *kiocb, struct socket *sock,*/ size =min_t (int, size,skb_tailroom (skb)); +/* +* If a single skb is large enough to hold all data, pass the +* scm reference to the skb. Otherwise we should hold a scm +* reference because the skb can be consumed at any time after +* we queue it into sk_receive_queue. +*/ +if (!fds_sent && sent + size >= len) + scm_ref = false; -/* Only send the fds and no ref to pid in the first buffer */ - err =unix_scm_to_skb (siocb->scm, skb, !fds_sent, fds_sent); +/* Only send the fds in the first buffer */ + err =unix_scm_to_skb (siocb->scm, skb, !fds_sent, + fds_sent || scm_ref);if (err <0 ) {kfree_skb (skb); - goto out; + goto out_err; } max_level = err +1 ; fds_sent = true;
@@ -1650,7 +1660,7 @@ static int unix_stream_sendmsg(struct kiocb *kiocb, struct socket *sock, err =memcpy_fromiovec (skb_put (skb, size), msg->msg_iov, size);if (err) {kfree_skb (skb); - goto out; + goto out_err; }unix_state_lock (other);
@@ -1667,10 +1677,10 @@ static int unix_stream_sendmsg(struct kiocb *kiocb, struct socket *sock, sent += size; } -if (skb) -scm_release (siocb->scm); -else +if (scm_ref)scm_destroy (siocb->scm); +else +scm_release (siocb->scm); siocb->scm =NULL ;return sent;
@@ -1683,9 +1693,10 @@ pipe_err:send_sig (SIGPIPE , current,0 ); err = -EPIPE ; out_err: -if (skb ==NULL ) +if (scm_ref)scm_destroy (siocb->scm); -out: +else +scm_release (siocb->scm); siocb->scm =NULL ;return sent ? : err; }