CVE-2014-3673 (C, published 2014-11-10T11:55Z)
Description: value: The SCTP implementation in the Linux kernel through 3.17.2 allows remote attackers to cause a denial of service (system crash) via a malformed ASCONF chunk, related to net/sctp/sm_make_chunk.c and net/sctp/sm_statefuns.c.
Severity: HIGH (CVSS v3 base score: 7.5)
Weakness: CWE-20 \u2014 Improper Input Validation
The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
Repository: https://github.com/torvalds/linux
Files changed:
include/net/sctp/sm.h
net/sctp/sm_make_chunk.c
net/sctp/sm_statefuns.c
Commit message: net: sctp: fix skb_over_panic when receiving malformed ASCONF chunks

Commit 6f4c618ddb0 ("SCTP : Add paramters validity check for
ASCONF chunk") added basic verification of ASCONF chunks, however,
it is still possible to remotely crash a server by sending a
special crafted ASCONF chunk, even up to pre 2.6.12 kernels:

skb_over_panic: text:ffffffffa01ea1c3 len:31056 put:30768
 head:ffff88011bd81800 data:ffff88011bd81800 tail:0x7950
 end:0x440 dev:<NULL>
 ------------[ cut here ]------------
kernel BUG at net/core/skbuff.c:129!
[...]
Call Trace:
 <IRQ>
 [<ffffffff8144fb1c>] skb_put+0x5c/0x70
 [<ffffffffa01ea1c3>] sctp_addto_chunk+0x63/0xd0 [sctp]
 [<ffffffffa01eadaf>] sctp_process_asconf+0x1af/0x540 [sctp]
 [<ffffffff8152d025>] ? _read_unlock_bh+0x15/0x20
 [<ffffffffa01e0038>] sctp_sf_do_asconf+0x168/0x240 [sctp]
 [<ffffffffa01e3751>] sctp_do_sm+0x71/0x1210 [sctp]
 [<ffffffff8147645d>] ? fib_rules_lookup+0xad/0xf0
 [<ffffffffa01e6b22>] ? sctp_cmp_addr_exact+0x32/0x40 [sctp]
 [<ffffffffa01e8393>] sctp_assoc_bh_rcv+0xd3/0x180 [sctp]
 [<ffffffffa01ee986>] sctp_inq_push+0x56/0x80 [sctp]
 [<ffffffffa01fcc42>] sctp_rcv+0x982/0xa10 [sctp]
 [<ffffffffa01d5123>] ? ipt_local_in_hook+0x23/0x28 [iptable_filter]
 [<ffffffff8148bdc9>] ? nf_iterate+0x69/0xb0
 [<ffffffff81496d10>] ? ip_local_deliver_finish+0x0/0x2d0
 [<ffffffff8148bf86>] ? nf_hook_slow+0x76/0x120
 [<ffffffff81496d10>] ? ip_local_deliver_finish+0x0/0x2d0
 [<ffffffff81496ded>] ip_local_deliver_finish+0xdd/0x2d0
 [<ffffffff81497078>] ip_local_deliver+0x98/0xa0
 [<ffffffff8149653d>] ip_rcv_finish+0x12d/0x440
 [<ffffffff81496ac5>] ip_rcv+0x275/0x350
 [<ffffffff8145c88b>] __netif_receive_skb+0x4ab/0x750
 [<ffffffff81460588>] netif_receive_skb+0x58/0x60

This can be triggered e.g., through a simple scripted nmap
connection scan injecting the chunk after the handshake, for
example, ...

  -------------- INIT[ASCONF; ASCONF_ACK] ------------->
  <----------- INIT-ACK[ASCONF; ASCONF_ACK] ------------
  -------------------- COOKIE-ECHO -------------------->
  <-------------------- COOKIE-ACK ---------------------
  ------------------ ASCONF; UNKNOWN ------------------>

... where ASCONF chunk of length 280 contains 2 parameters ...

  1) Add IP address parameter (param length: 16)
  2) Add/del IP address parameter (param length: 255)

... followed by an UNKNOWN chunk of e.g. 4 bytes. Here, the
Address Parameter in the ASCONF chunk is even missing, too.
This is just an example and similarly-crafted ASCONF chunks
could be used just as well.

The ASCONF chunk passes through sctp_verify_asconf() as all
parameters passed sanity checks, and after walking, we ended
up successfully at the chunk end boundary, and thus may invoke
sctp_process_asconf(). Parameter walking is done with
WORD_ROUND() to take padding into account.

In sctp_process_asconf()'s TLV processing, we may fail in
sctp_process_asconf_param() e.g., due to removal of the IP
address that is also the source address of the packet containing
the ASCONF chunk, and thus we need to add all TLVs after the
failure to our ASCONF response to remote via helper function
sctp_add_asconf_response(), which basically invokes a
sctp_addto_chunk() adding the error parameters to the given
skb.

When walking to the next parameter this time, we proceed
with ...

  length = ntohs(asconf_param->param_hdr.length);
  asconf_param = (void *)asconf_param + length;

... instead of the WORD_ROUND()'ed length, thus resulting here
in an off-by-one that leads to reading the follow-up garbage
parameter length of 12336, and thus throwing an skb_over_panic
for the reply when trying to sctp_addto_chunk() next time,
which implicitly calls the skb_put() with that length.

Fix it by using sctp_walk_params() [ which is also used in
INIT parameter processing ] macro in the verification *and*
in ASCONF processing: it will make sure we don't spill over,
that we walk parameters WORD_ROUND()'ed. Moreover, we're being
more defensive and guard against unknown parameter types and
missized addresses.

Joint work with Vlad Yasevich.

Fixes: b896b82be4ae ("[SCTP] ADDIP: Support for processing incoming ASCONF_ACK chunks.")
Signed-off-by: Daniel Borkmann <dborkman@redhat.com>
Signed-off-by: Vlad Yasevich <vyasevich@gmail.com>
Acked-by: Neil Horman <nhorman@tuxdriver.com>
Signed-off-by: David S. Miller <davem@davemloft.net>


Vulnerable code:
int sctp_verify_asconf(const struct sctp_association *asoc,
		       struct sctp_paramhdr *param_hdr, void *chunk_end,
		       struct sctp_paramhdr **errp);
/* Verify the ASCONF packet before we process it.  */
int sctp_verify_asconf(const struct sctp_association *asoc,
		       struct sctp_paramhdr *param_hdr, void *chunk_end,
		       struct sctp_paramhdr **errp) {
	sctp_addip_param_t *asconf_param;
	int length, plen;

	param.v = (sctp_paramhdr_t *) param_hdr;
	while (param.v <= chunk_end - sizeof(sctp_paramhdr_t)) {
		length = ntohs(param.p->length);
		*errp = param.p;
		if (param.v > chunk_end - length ||
		    length < sizeof(sctp_paramhdr_t))
			return 0;
			asconf_param = (sctp_addip_param_t *)param.v;
			plen = ntohs(asconf_param->param_hdr.length);
			if (plen < sizeof(sctp_addip_param_t) +
			    sizeof(sctp_paramhdr_t))
				return 0;
				return 0;

			break;

		param.v += WORD_ROUND(length);
	if (param.v != chunk_end)
		return 0;
	return 1;

	int	all_param_pass = 1;
	while (chunk_len > 0) {
						     asconf_param);
		if (SCTP_ERROR_NO_ERROR != err_code)
			all_param_pass = 0;

			sctp_add_asconf_response(asconf_ack,
						 asconf_param->crr_id, err_code,
						 asconf_param);
		if (SCTP_ERROR_RSRC_LOW == err_code)

		/* Move to the next ASCONF param. */
		length = ntohs(asconf_param->param_hdr.length);
		asconf_param = (void *)asconf_param + length;
		chunk_len -= length;

	union sctp_addr_param	*addr_param;
	int			length;
	addr_param = (union sctp_addr_param *)hdr->params;
	length = ntohs(addr_param->p.length);
	if (length < sizeof(sctp_paramhdr_t))
		return sctp_sf_violation_paramlen(net, ep, asoc, type, arg,
			   (void *)addr_param, commands);

	if (!sctp_verify_asconf(asoc,
			    (sctp_paramhdr_t *)((void *)addr_param + length),
			    (void *)chunk->chunk_end,
			    &err_param))
	if (!sctp_verify_asconf(asoc,
	    (sctp_paramhdr_t *)addip_hdr->params,
	    (void *)asconf_ack->chunk_end,
	    &err_param))

Fixed code:
bool sctp_verify_asconf(const struct sctp_association *asoc,
			struct sctp_chunk *chunk, bool addr_param_needed,
			struct sctp_paramhdr **errp);
/* Verify the ASCONF packet before we process it. */
bool sctp_verify_asconf(const struct sctp_association *asoc,
			struct sctp_chunk *chunk, bool addr_param_needed,
			struct sctp_paramhdr **errp)
{
	sctp_addip_chunk_t *addip = (sctp_addip_chunk_t *) chunk->chunk_hdr;
	bool addr_param_seen = false;
	sctp_walk_params(param, addip, addip_hdr.params) {
		size_t length = ntohs(param.p->length);
		*errp = param.p;
		case SCTP_PARAM_ERR_CAUSE:
			break;
		case SCTP_PARAM_IPV4_ADDRESS:
			if (length != sizeof(sctp_ipv4addr_param_t))
				return false;
			addr_param_seen = true;
			break;
		case SCTP_PARAM_IPV6_ADDRESS:
			if (length != sizeof(sctp_ipv6addr_param_t))
				return false;
			addr_param_seen = true;
			break;
			/* In ASCONF chunks, these need to be first. */
			if (addr_param_needed && !addr_param_seen)
				return false;
			length = ntohs(param.addip->param_hdr.length);
			if (length < sizeof(sctp_addip_param_t) +
				     sizeof(sctp_paramhdr_t))
				return false;
				return false;
			/* This is unkown to us, reject! */
			return false;
	/* Remaining sanity checks. */
	if (addr_param_needed && !addr_param_seen)
		return false;
	if (!addr_param_needed && addr_param_seen)
		return false;
	if (param.v != chunk->chunk_end)
		return false;
	return true;
	sctp_addip_chunk_t *addip = (sctp_addip_chunk_t *) asconf->chunk_hdr;
	bool all_param_pass = true;
	union sctp_params param;
	sctp_walk_params(param, addip, addip_hdr.params) {
		/* Skip preceeding address parameters. */
		if (param.p->type == SCTP_PARAM_IPV4_ADDRESS ||
		    param.p->type == SCTP_PARAM_IPV6_ADDRESS)
			continue;

						     param.addip);
		if (err_code != SCTP_ERROR_NO_ERROR)
			all_param_pass = false;
			sctp_add_asconf_response(asconf_ack, param.addip->crr_id,
						 err_code, param.addip);
		if (err_code == SCTP_ERROR_RSRC_LOW)
	if (!sctp_verify_asconf(asoc, chunk, true, &err_param))
	if (!sctp_verify_asconf(asoc, asconf_ack, false, &err_param))