Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

netfilter: conntrack: dccp: copy entire header to stack buffer, not just basic one

Eric Dumazet says:
nf_conntrack_dccp_packet() has an unique:

dh = skb_header_pointer(skb, dataoff, sizeof(_dh), &_dh);

And nothing more is 'pulled' from the packet, depending on the content.
dh->dccph_doff, and/or dh->dccph_x ...)
So dccp_ack_seq() is happily reading stuff past the _dh buffer.

BUG: KASAN: stack-out-of-bounds in nf_conntrack_dccp_packet+0x1134/0x11c0
Read of size 4 at addr ffff000128f66e0c by task syz-executor.2/29371
[..]

Fix this by increasing the stack buffer to also include room for
the extra sequence numbers and all the known dccp packet type headers,
then pull again after the initial validation of the basic header.

While at it, mark packets invalid that lack 48bit sequence bit but
where RFC says the type MUST use them.

Compile tested only.

v2: first skb_header_pointer() now needs to adjust the size to
only pull the generic header. (Eric)

Heads-up: I intend to remove dccp conntrack support later this year.

Fixes: 2bc780499aa3 ("[NETFILTER]: nf_conntrack: add DCCP protocol support")
Reported-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: Florian Westphal <fw@strlen.de>
Reviewed-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>

authored by

Florian Westphal and committed by
Pablo Neira Ayuso
ff0a3a7d 6f67fbf8

+49 -3
+49 -3
net/netfilter/nf_conntrack_proto_dccp.c
··· 432 432 struct sk_buff *skb, unsigned int dataoff, 433 433 const struct nf_hook_state *state) 434 434 { 435 + static const unsigned long require_seq48 = 1 << DCCP_PKT_REQUEST | 436 + 1 << DCCP_PKT_RESPONSE | 437 + 1 << DCCP_PKT_CLOSEREQ | 438 + 1 << DCCP_PKT_CLOSE | 439 + 1 << DCCP_PKT_RESET | 440 + 1 << DCCP_PKT_SYNC | 441 + 1 << DCCP_PKT_SYNCACK; 435 442 unsigned int dccp_len = skb->len - dataoff; 436 443 unsigned int cscov; 437 444 const char *msg; 445 + u8 type; 446 + 447 + BUILD_BUG_ON(DCCP_PKT_INVALID >= BITS_PER_LONG); 438 448 439 449 if (dh->dccph_doff * 4 < sizeof(struct dccp_hdr) || 440 450 dh->dccph_doff * 4 > dccp_len) { ··· 469 459 goto out_invalid; 470 460 } 471 461 472 - if (dh->dccph_type >= DCCP_PKT_INVALID) { 462 + type = dh->dccph_type; 463 + if (type >= DCCP_PKT_INVALID) { 473 464 msg = "nf_ct_dccp: reserved packet type "; 474 465 goto out_invalid; 475 466 } 467 + 468 + if (test_bit(type, &require_seq48) && !dh->dccph_x) { 469 + msg = "nf_ct_dccp: type lacks 48bit sequence numbers"; 470 + goto out_invalid; 471 + } 472 + 476 473 return false; 477 474 out_invalid: 478 475 nf_l4proto_log_invalid(skb, state, IPPROTO_DCCP, "%s", msg); 479 476 return true; 477 + } 478 + 479 + struct nf_conntrack_dccp_buf { 480 + struct dccp_hdr dh; /* generic header part */ 481 + struct dccp_hdr_ext ext; /* optional depending dh->dccph_x */ 482 + union { /* depends on header type */ 483 + struct dccp_hdr_ack_bits ack; 484 + struct dccp_hdr_request req; 485 + struct dccp_hdr_response response; 486 + struct dccp_hdr_reset rst; 487 + } u; 488 + }; 489 + 490 + static struct dccp_hdr * 491 + dccp_header_pointer(const struct sk_buff *skb, int offset, const struct dccp_hdr *dh, 492 + struct nf_conntrack_dccp_buf *buf) 493 + { 494 + unsigned int hdrlen = __dccp_hdr_len(dh); 495 + 496 + if (hdrlen > sizeof(*buf)) 497 + return NULL; 498 + 499 + return skb_header_pointer(skb, offset, hdrlen, buf); 480 500 } 481 501 482 502 int nf_conntrack_dccp_packet(struct nf_conn *ct, struct sk_buff *skb, ··· 515 475 const struct nf_hook_state *state) 516 476 { 517 477 enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); 518 - struct dccp_hdr _dh, *dh; 478 + struct nf_conntrack_dccp_buf _dh; 519 479 u_int8_t type, old_state, new_state; 520 480 enum ct_dccp_roles role; 521 481 unsigned int *timeouts; 482 + struct dccp_hdr *dh; 522 483 523 - dh = skb_header_pointer(skb, dataoff, sizeof(_dh), &_dh); 484 + dh = skb_header_pointer(skb, dataoff, sizeof(*dh), &_dh.dh); 524 485 if (!dh) 525 486 return NF_DROP; 526 487 527 488 if (dccp_error(dh, skb, dataoff, state)) 528 489 return -NF_ACCEPT; 490 + 491 + /* pull again, including possible 48 bit sequences and subtype header */ 492 + dh = dccp_header_pointer(skb, dataoff, dh, &_dh); 493 + if (!dh) 494 + return NF_DROP; 529 495 530 496 type = dh->dccph_type; 531 497 if (!nf_ct_is_confirmed(ct) && !dccp_new(ct, skb, dh, state))