diff --git a/poc-code/sctp-defrag-bug/Makefile.am b/poc-code/sctp-defrag-bug/Makefile.am index 72617ee3..0635e562 100644 --- a/poc-code/sctp-defrag-bug/Makefile.am +++ b/poc-code/sctp-defrag-bug/Makefile.am @@ -1,26 +1,26 @@ # # Copyright (C) 2017 Red Hat, Inc. All rights reserved. # # Author: Fabio M. Di Nitto # # This software licensed under GPL-2.0+, LGPL-2.0+ # MAINTAINERCLEANFILES = Makefile.in include $(top_srcdir)/build-aux/check.mk AM_CPPFLAGS = -I$(top_srcdir)/libknet # override global LIBS that pulls in lots of craft we don't need here -LIBS = +LIBS = -lz noinst_PROGRAMS = client server noinst_HEADERS = common.h server_SOURCES = server.c \ common.c client_SOURCES = client.c \ common.c diff --git a/poc-code/sctp-defrag-bug/client.c b/poc-code/sctp-defrag-bug/client.c index 919084e8..ecb4ced2 100644 --- a/poc-code/sctp-defrag-bug/client.c +++ b/poc-code/sctp-defrag-bug/client.c @@ -1,198 +1,215 @@ #include "config.h" #include #include #include #include #include #include #include #include #include #include +#include #ifdef HAVE_NETINET_SCTP_H #include #include "common.h" int main(int argc, char **argv) { int err = 0; int rv; + int enable_crc = 0; char defport[8] = "50000"; char *address = NULL, *port = NULL; struct sockaddr_storage ss; int sock; int rx_epoll; struct epoll_event ev; struct epoll_event events[32]; int i, nev; struct mmsghdr msg_in[256]; struct mmsghdr msg_out[256]; struct iovec iov_out[256]; int sent_msgs; - while ((rv = getopt(argc, argv, "a:p:")) != EOF) { + while ((rv = getopt(argc, argv, "a:p:c")) != EOF) { switch(rv) { case 'a': address = optarg; break; case 'p': port = optarg; break; + case 'c': + enable_crc = 1; + break; default: fprintf(stderr, "Unknown option\n"); return -1; break; } } /* * Setup RX buffers */ memset(&msg_in, 0, sizeof(struct mmsghdr)); if (setup_rx_buffers(msg_in) < 0) { return -1; } /* * setup TX buffers */ for (i = 0; i < 256; i++) { iov_out[i].iov_base = (void *)malloc(65536); if (!iov_out[i].iov_base) { fprintf(stderr, "Unable to malloc RX buffers(%d): %s\n", errno, strerror(errno)); return -1; } - memset(iov_out[i].iov_base, 0, 65536); + if (enable_crc) { + unsigned int *dataint = (unsigned int *)iov_out[i].iov_base; + unsigned int crc; + int j; + + for (j=1; j<65536/sizeof(int); j++) { + dataint[j] = rand(); + } + crc = crc32(0, NULL, 0); + dataint[0] = crc32(crc, (Bytef*)&dataint[1], 65536-sizeof(int)); + } else { + memset(iov_out[i].iov_base, 0, 65536); + } iov_out[i].iov_len = 65536; } rx_epoll = epoll_create(32); if (rx_epoll < 0) { fprintf(stderr, "Unable to create rx_epoll (%d): %s\n", errno, strerror(errno)); return -1; } /* * setup SCTP client socket */ if (!address) { fprintf(stderr, "No server address specified (use -a ).\n"); fprintf(stderr, "Scanning the internet for SCTP servers (this might take a while).\n"); while (1) { fprintf(stderr, "..."); sleep(1); } } if (!port) { port = defport; } if (strtoaddr(address, port, &ss, sizeof(struct sockaddr_storage)) < 0) { return -1; } sock = socket(ss.ss_family, SOCK_STREAM, IPPROTO_SCTP); if (sock < 0) { fprintf(stderr, "unable to create socket (%d): %s\n", errno, strerror(errno)); return -1; } if (setup_sctp_common_sock_opts(sock, &ss) < 0) { fprintf(stderr, "Unable to set socket options\n"); goto out; } if (connect(sock, (struct sockaddr *)&ss, sizeof(struct sockaddr_storage)) < 0) { if ((errno != EALREADY) && (errno != EINPROGRESS) && (errno != EISCONN)) { fprintf(stderr, "Unable to connect to server: (%d): %s\n", errno, strerror(errno)); return -1; } } /* * i am supposed to check SO_ERROR to see if it's connected, but this is a PoC * and I am lazy */ sleep(1); memset(&ev, 0, sizeof(struct epoll_event)); ev.events = EPOLLIN; ev.data.fd = sock; if (epoll_ctl(rx_epoll, EPOLL_CTL_ADD, sock, &ev) < 0) { fprintf(stderr, "Unable to add listen socket to epoll (%d): %s\n", errno, strerror(errno)); goto out; } /* * main loop */ while(1) { nev = epoll_wait(rx_epoll, events, 32, 0); if (nev < 0) { fprintf(stderr, "SCTP listen handler EPOLL ERROR (%d): %s\n", errno, strerror(errno)); } else { for (i = 0; i < nev; i++) { if (events[i].data.fd == sock) { - get_incoming_data(events[i].data.fd, msg_in); + get_incoming_data(events[i].data.fd, msg_in, enable_crc); } } } sleep(1); memset(&msg_out, 0, sizeof(msg_out)); for (i = 0; i < 256; i++) { memset(&msg_out[i].msg_hdr, 0, sizeof(struct msghdr)); msg_out[i].msg_hdr.msg_name = &ss; msg_out[i].msg_hdr.msg_namelen = sizeof(struct sockaddr_storage); msg_out[i].msg_hdr.msg_iov = &iov_out[i]; msg_out[i].msg_hdr.msg_iovlen = 1; } sent_msgs = sendmmsg(sock, msg_out, 256, MSG_DONTWAIT | MSG_NOSIGNAL); if (sent_msgs <= 0) { fprintf(stderr, "Error sending msgs (%d): %s\n", errno, strerror(errno)); } if (sent_msgs != 256) { fprintf(stderr, "Unable to send all 256 messages at once (sent: %d)\n", sent_msgs); } fprintf(stderr, "sent %d messages\n", sent_msgs); } out: if (sock >= 0) { close(sock); } if (rx_epoll >= 0) { close(rx_epoll); } return err; } #else int main(void) { printf("SCTP unsupported in this build\n"); errno = EINVAL; return -1; } #endif diff --git a/poc-code/sctp-defrag-bug/common.c b/poc-code/sctp-defrag-bug/common.c index 1d840f3c..432d53f0 100644 --- a/poc-code/sctp-defrag-bug/common.c +++ b/poc-code/sctp-defrag-bug/common.c @@ -1,328 +1,340 @@ #include "config.h" #include #include #include #include #include #include #include #include #include #include +#include #ifdef HAVE_NETINET_SCTP_H #include #include "common.h" int strtoaddr(const char *host, const char *port, struct sockaddr_storage *ss, socklen_t sslen) { int err; struct addrinfo hints; struct addrinfo *result = NULL; if (!host) { errno = EINVAL; return -1; } if (!port) { errno = EINVAL; return -1; } if (!ss) { errno = EINVAL; return -1; } if (!sslen) { errno = EINVAL; return -1; } memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; err = getaddrinfo(host, port, &hints, &result); if (!err) { memmove(ss, result->ai_addr, (sslen < result->ai_addrlen) ? sslen : result->ai_addrlen); freeaddrinfo(result); } return err; } int _fdset_cloexec(int fd) { int fdflags; fdflags = fcntl(fd, F_GETFD, 0); if (fdflags < 0) return -1; fdflags |= FD_CLOEXEC; if (fcntl(fd, F_SETFD, fdflags) < 0) return -1; return 0; } int _fdset_nonblock(int fd) { int fdflags; fdflags = fcntl(fd, F_GETFL, 0); if (fdflags < 0) return -1; fdflags |= O_NONBLOCK; if (fcntl(fd, F_SETFL, fdflags) < 0) return -1; return 0; } int setup_sctp_common_sock_opts(int sock, struct sockaddr_storage *ss) { struct sctp_event_subscribe events; int value; if (_fdset_cloexec(sock)) { fprintf(stderr, "unable to set CLOEXEC socket opts (%d): %s\n", errno, strerror(errno)); return -1; } if (_fdset_nonblock(sock)) { fprintf(stderr, "unable to set NONBLOCK socket opts (%d): %s\n", errno, strerror(errno)); return -1; } value = 8388608; if (setsockopt(sock, SOL_SOCKET, SO_RCVBUFFORCE, &value, sizeof(value)) < 0) { fprintf(stderr, "Unable to set receive buffer (%d): %s\n", errno, strerror(errno)); return -1; } value = 8388608; if (setsockopt(sock, SOL_SOCKET, SO_SNDBUFFORCE, &value, sizeof(value)) < 0) { fprintf(stderr, "Unable to set send buffer (%d): %s\n", errno, strerror(errno)); return -1; } if (ss->ss_family == AF_INET6) { value = IPV6_PMTUDISC_PROBE; if (setsockopt(sock, SOL_IPV6, IPV6_MTU_DISCOVER, &value, sizeof(value)) <0) { fprintf(stderr, "Unable to set PMTUDISC (%d): %s\n", errno, strerror(errno)); return -1; } } else { value = IP_PMTUDISC_PROBE; if (setsockopt(sock, SOL_IP, IP_MTU_DISCOVER, &value, sizeof(value)) <0) { fprintf(stderr, "Unable to set PMTUDISC (%d): %s\n", errno, strerror(errno)); return -1; } } value = 1; if (setsockopt(sock, SOL_SCTP, SCTP_NODELAY, &value, sizeof(value)) < 0) { fprintf(stderr, "Unable to set SCTP_NODELAY (%d): %s\n", errno, strerror(errno)); return -1; } #if 0 /* workaround */ value = 1; if (setsockopt(sock, SOL_SCTP, SCTP_DISABLE_FRAGMENTS, &value, sizeof(value)) < 0) { fprintf(stderr, "Unable to set SCTP_DISABLE_FRAGMENTS (%d): %s\n", errno, strerror(errno)); return -1; } #endif memset(&events, 0, sizeof (events)); events.sctp_data_io_event = 1; events.sctp_association_event = 1; events.sctp_send_failure_event = 1; events.sctp_address_event = 1; events.sctp_peer_error_event = 1; events.sctp_shutdown_event = 1; if (setsockopt(sock, IPPROTO_SCTP, SCTP_EVENTS, &events, sizeof (events)) < 0) { fprintf(stderr, "Unable to configure SCTP notifications (%d): %s\n", errno, strerror(errno)); return -1; } return 0; } int setup_sctp_server_sock_opts(int sock, struct sockaddr_storage *ss) { int value; if (setup_sctp_common_sock_opts(sock, ss) < 0) { return -1; } value = 1; if (setsockopt(sock, SOL_IP, IP_FREEBIND, &value, sizeof(value)) <0) { fprintf(stderr, "Unable to set FREEBIND (%d): %s\n", errno, strerror(errno)); return -1; } if (ss->ss_family == AF_INET6) { value = 1; if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &value, sizeof(value)) < 0) { fprintf(stderr, "Unable to set IPv6 only (%d): %s\n", errno, strerror(errno)); return -1; } } value = 1; if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)) < 0) { fprintf(stderr, "Unable to set REUSEADDR (%d): %s\n", errno, strerror(errno)); return -1; } return 0; } static int fragsize = 0; -static void parse_incoming_data(struct mmsghdr msg) +static void parse_incoming_data(struct mmsghdr msg, int check_crc) { int i; struct iovec *iov = msg.msg_hdr.msg_iov; size_t iovlen = msg.msg_hdr.msg_iovlen; + unsigned int crc; + unsigned int *data = msg.msg_hdr.msg_iov->iov_base; + size_t datalen = msg.msg_len; union sctp_notification *snp; if (!(msg.msg_hdr.msg_flags & MSG_NOTIFICATION)) { if (msg.msg_len == 0) { fprintf(stderr, "Received 0bytes packet\n"); exit(-1); } if (!(msg.msg_hdr.msg_flags & MSG_EOR)) { fprintf(stderr, "got a fragment size: %d\n", msg.msg_len); fragsize = fragsize + msg.msg_len; return; } if (fragsize) { fragsize = fragsize + msg.msg_len; fprintf(stderr, "Received all packets from frags: %d\n", fragsize); if (fragsize != 65536) { fprintf(stderr, "KABOOM: %d\n", msg.msg_len); exit(-1); } fragsize = 0; } else { /* check pckt len here */ if (msg.msg_len != 65536) { fprintf(stderr, "KABOOM: %d\n", msg.msg_len); exit(-1); } + if (check_crc) { + crc = crc32(0, NULL, 0); + crc = crc32(crc, (Bytef *)&data[1], datalen - sizeof(unsigned int)) & 0xFFFFFFFF; + if (crc != data[0]) { + fprintf(stderr, "KABOOM - CRCs do not match\n"); + exit(-1); + } + } } return; } if (!(msg.msg_hdr.msg_flags & MSG_EOR)) { fprintf(stderr, "[event] end of notifications\n"); return; } /* got a notification */ for (i=0; i< iovlen; i++) { snp = iov[i].iov_base; switch (snp->sn_header.sn_type) { case SCTP_ASSOC_CHANGE: fprintf(stderr, "[event] sctp assoc change\n"); break; case SCTP_SHUTDOWN_EVENT: fprintf(stderr, "[event] sctp shutdown event\n"); break; case SCTP_SEND_FAILED: fprintf(stderr, "[event] sctp send failed\n"); break; case SCTP_PEER_ADDR_CHANGE: fprintf(stderr, "[event] sctp peer addr change\n"); break; case SCTP_REMOTE_ERROR: fprintf(stderr, "[event] sctp remote error\n"); break; default: fprintf(stderr, "[event] unknown sctp event type: %hu\n", snp->sn_header.sn_type); exit(-1); break; } } return; } -void get_incoming_data(int sock, struct mmsghdr *msg) +void get_incoming_data(int sock, struct mmsghdr *msg, int check_crc) { int i, msg_recv; msg_recv = recvmmsg(sock, msg, 256, MSG_DONTWAIT | MSG_NOSIGNAL, NULL); if (msg_recv <= 0) { fprintf(stderr, "Error message received from recvmmsg (%d): %s\n", errno, strerror(errno)); exit(-1); } fprintf(stderr, "Received: %d messages\n", msg_recv); for (i = 0; i < msg_recv; i++) { - parse_incoming_data(msg[i]); + parse_incoming_data(msg[i], check_crc); } } int setup_rx_buffers(struct mmsghdr *msg) { int i; struct sockaddr_storage addr[256]; struct iovec iov_in[256]; if (!msg) { return -1; } /* * Setup buffers */ for (i = 0; i < 256; i++) { iov_in[i].iov_base = (void *)malloc(65536); if (!iov_in[i].iov_base) { fprintf(stderr, "Unable to malloc RX buffers(%d): %s\n", errno, strerror(errno)); return -1; } memset(iov_in[i].iov_base, 0, 65536); iov_in[i].iov_len = 65536; memset(&msg[i].msg_hdr, 0, sizeof(struct msghdr)); msg[i].msg_hdr.msg_name = &addr[i]; msg[i].msg_hdr.msg_namelen = sizeof(struct sockaddr_storage); msg[i].msg_hdr.msg_iov = &iov_in[i]; msg[i].msg_hdr.msg_iovlen = 1; } return 0; } #endif diff --git a/poc-code/sctp-defrag-bug/common.h b/poc-code/sctp-defrag-bug/common.h index fc82d8cf..a82299f4 100644 --- a/poc-code/sctp-defrag-bug/common.h +++ b/poc-code/sctp-defrag-bug/common.h @@ -1,20 +1,20 @@ /* * Copyright (C) 2017 Red Hat, Inc. All rights reserved. * * Authors: Fabio M. Di Nitto * * This software licensed under GPL-2.0+, LGPL-2.0+ */ #ifndef __COMMON_H__ #define __COMMON_H__ int strtoaddr(const char *host, const char *port, struct sockaddr_storage *ss, socklen_t sslen); int _fdset_cloexec(int fd); int _fdset_nonblock(int fd); int setup_sctp_common_sock_opts(int sock, struct sockaddr_storage *ss); int setup_sctp_server_sock_opts(int sock, struct sockaddr_storage *ss); -void get_incoming_data(int sock, struct mmsghdr *msg); +void get_incoming_data(int sock, struct mmsghdr *msg, int check_crc); int setup_rx_buffers(struct mmsghdr *msg); #endif diff --git a/poc-code/sctp-defrag-bug/server.c b/poc-code/sctp-defrag-bug/server.c index ac79f3d6..7918f3e1 100644 --- a/poc-code/sctp-defrag-bug/server.c +++ b/poc-code/sctp-defrag-bug/server.c @@ -1,168 +1,172 @@ #include "config.h" #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_NETINET_SCTP_H #include #include "common.h" int main(int argc, char **argv) { int err = 0; int rv; + int check_crc = 0; char defaddr[8] = "0.0.0.0"; char defport[8] = "50000"; char *address = NULL, *port = NULL; struct sockaddr_storage ss, newss; int sock, newsock; socklen_t sock_len = sizeof(ss); int rx_epoll; struct epoll_event ev; struct epoll_event events[32]; int i, nev; struct mmsghdr msg[256]; - while ((rv = getopt(argc, argv, "a:p:")) != EOF) { + while ((rv = getopt(argc, argv, "a:p:c")) != EOF) { switch(rv) { case 'a': address = optarg; break; case 'p': port = optarg; break; + case 'c': + check_crc = 1; + break; default: fprintf(stderr, "Unknown option\n"); return -1; break; } } memset(&msg, 0, sizeof(msg)); if (setup_rx_buffers(msg) < 0) { return -1; } rx_epoll = epoll_create(32); if (rx_epoll < 0) { fprintf(stderr, "Unable to create rx_epoll (%d): %s\n", errno, strerror(errno)); return -1; } /* * setup SCTP server socket */ if (!address) { address = defaddr; } if (!port) { port = defport; } if (strtoaddr(address, port, &ss, sizeof(struct sockaddr_storage)) < 0) { return -1; } sock = socket(ss.ss_family, SOCK_STREAM, IPPROTO_SCTP); if (sock < 0) { fprintf(stderr, "unable to create socket (%d): %s\n", errno, strerror(errno)); return -1; } if (setup_sctp_server_sock_opts(sock, &ss) < 0) { fprintf(stderr, "Unable to set socket options\n"); goto out; } if (bind(sock, (struct sockaddr *)&ss, sizeof(struct sockaddr_storage)) < 0) { fprintf(stderr, "Unable to bind socket (%d): %s\n", errno, strerror(errno)); goto out; } if (listen(sock, 5) < 0) { fprintf(stderr, "Unable to listen socket (%d): %s\n", errno, strerror(errno)); goto out; } memset(&ev, 0, sizeof(struct epoll_event)); ev.events = EPOLLIN; ev.data.fd = sock; if (epoll_ctl(rx_epoll, EPOLL_CTL_ADD, sock, &ev) < 0) { fprintf(stderr, "Unable to add listen socket to epoll (%d): %s\n", errno, strerror(errno)); goto out; } /* * main loop */ while(1) { nev = epoll_wait(rx_epoll, events, 32, -1); if (nev < 0) { fprintf(stderr, "SCTP listen handler EPOLL ERROR (%d): %s\n", errno, strerror(errno)); } for (i = 0; i < nev; i++) { if (events[i].data.fd == sock) { newsock = accept(sock, (struct sockaddr *)&newss, &sock_len); if (newsock < 0) { fprintf(stderr, "Error accepting connection (%d): %s\n", errno, strerror(errno)); continue; } if (setup_sctp_common_sock_opts(newsock, &newss) < 0) { fprintf(stderr, "Error setting sockopts\n"); close(newsock); continue; } memset(&ev, 0, sizeof(struct epoll_event)); ev.events = EPOLLIN; ev.data.fd = newsock; if (epoll_ctl(rx_epoll, EPOLL_CTL_ADD, newsock, &ev) < 0) { fprintf(stderr, "Unable to add accept new connection (%d): %s\n", errno, strerror(errno)); close(newsock); } fprintf(stderr, "Accepted socket: %d\n", newsock); } else { - get_incoming_data(events[i].data.fd, msg); + get_incoming_data(events[i].data.fd, msg, check_crc); } } } out: if (sock >= 0) { close(sock); } if (rx_epoll >= 0) { close(rx_epoll); } return err; } #else int main(void) { printf("SCTP unsupported in this build\n"); errno = EINVAL; return -1; } #endif