Commit message: SECURITY: Do not inspect or continue on very short packets

Code introduced in Openswan to ensure the IKEv2 minor was ignored
introduced a vulnerability that caused mangled short IKE packets to
be processed as valid IKE packets despite in_struct() returning a
failure, resulting in pluto crashing and restarting.

Reported by Nick Howitt.

Additionally, with the introduction of IKEv2, incoming packets always
assumed it could at least read the IKE Major version number, and would
crash when the packet was overly short and did not contain such a number

This patch ensures the code not attempt to read the IKE version and might cause an
IKEv1 packet to be sent as response to a badly mangled IKEv2 packet, as
we default to IKEv1 for this type of error. It also no longer skips aborting
a failed in_struct() read.

It turns the version number in a loose enum.

Signed-off-by: Paul Wouters <pwouters@redhat.com>
CVE: CVE-2013-4564
CWE: CWE-189
Vulnerability description: Libreswan 3.6 allows remote attackers to cause a denial of service (crash) via a small length value and (1) no version or (2) an invalid major number in an IKE packet.

Project: libreswan
File: demux.c

void process_packet(struct msg_digest **mdp)
{
	struct msg_digest *md = *mdp;
	struct state *st = NULL;
	int vmaj, vmin;
	enum state_kind from_state = STATE_UNDEFINED;   /* state we started in */

#define SEND_NOTIFICATION(t) { \
		if (st) \
			send_notification_from_state(st, from_state, t); \
		else \
			send_notification_from_md(md, t); }

	if (!in_struct(&md->hdr, &isakmp_hdr_desc, &md->packet_pbs,
		       &md->message_pbs)) {
		/*
		 * The packet was very badly mangled. We can't be sure of any
		 * content - not even to look for major version number!
		 * So we'll just silently drop it
		 */
		libreswan_log("Received packet with mangled IKE header - dropped");
		SEND_NOTIFICATION(PAYLOAD_MALFORMED);
		return;
	}

	if (md->packet_pbs.roof < md->message_pbs.roof) {
		/* I don't think this can happen if in_struct() did not fail */
		libreswan_log(
			"received packet size (%u) is smaller than from "
			"size specified in ISAKMP HDR (%u) - packet dropped",
			(unsigned) pbs_room(&md->packet_pbs),
			md->hdr.isa_length);
		/* abort processing corrupt packet */
		return;
	} else if (md->packet_pbs.roof > md->message_pbs.roof) {
		/*
		 * Some (old?) versions of the Cisco VPN client send an additional
		 * 16 bytes of zero bytes - Complain but accept it
		 */
		DBG(DBG_CONTROL, {
			DBG_log(
			"size (%u) in received packet is larger than the size "
			"specified in ISAKMP HDR (%u) - ignoring extraneous bytes",
			(unsigned) pbs_room(&md->packet_pbs),
			md->hdr.isa_length);
			DBG_dump("extraneous bytes:", md->message_pbs.roof,
				md->packet_pbs.roof - md->message_pbs.roof);
		});
	}

	vmaj = md->hdr.isa_version >> ISA_MAJ_SHIFT;
	vmin = md->hdr.isa_version & ISA_MIN_MASK;

	switch (vmaj) {
	case ISAKMP_MAJOR_VERSION:
		if (vmin > ISAKMP_MINOR_VERSION) {
			/* RFC2408 3.1 ISAKMP Header Format:
			 *
			 * Minor Version (4 bits) - indicates the minor
			 * version of the ISAKMP protocol in use.
			 * Implementations based on this version of the
			 * ISAKMP Internet-Draft MUST set the Minor
			 * Version to 0.  Implementations based on
			 * previous versions of ISAKMP Internet- Drafts
			 * MUST set the Minor Version to 1.
			 * Implementations SHOULD never accept packets
			 * with a minor version number larger than its
			 * own, given the major version numbers are
			 * identical.
			 */
			libreswan_log("ignoring packet with IKEv1 minor version number %d greater than %d", vmin, ISAKMP_MINOR_VERSION);
			SEND_NOTIFICATION(INVALID_MINOR_VERSION);
			return;
		}
		DBG(DBG_CONTROL,
		    DBG_log(" processing version=%u.%u packet with exchange type=%s (%d)",
			    vmaj, vmin,
			    enum_name(&exchange_names_ikev1orv2, md->hdr.isa_xchg),
			    md->hdr.isa_xchg));
		process_v1_packet(mdp);
		break;

	case IKEv2_MAJOR_VERSION:
		if (vmin != IKEv2_MINOR_VERSION) {
			/*
			 * Unlike IKEv1, for IKEv2 we are supposed to try and
			 * continue on unknown minors
			 */
			libreswan_log("ignoring unknown IKEv2 minor version number %d", vmin);
		}
		DBG(DBG_CONTROL,
		    DBG_log(" processing version=%u.%u packet with exchange type=%s (%d)",
			    vmaj, vmin,
			    enum_name(&exchange_names_ikev1orv2, md->hdr.isa_xchg),
			    md->hdr.isa_xchg));
		process_v2_packet(mdp);
		break;

	default:
		libreswan_log("Unexpected IKE major '%d'",vmaj);
		SEND_NOTIFICATION(INVALID_MAJOR_VERSION);
		return;
	}
#undef SEND_NOTIFICATION
}

Project: libreswan
File: demux.c

void comm_handle(const struct iface_port *ifp)
{
	static struct msg_digest *md;

#if defined(IP_RECVERR) && defined(MSG_ERRQUEUE)
	/* Even though select(2) says that there is a message,
	 * it might only be a MSG_ERRQUEUE message.  At least
	 * sometimes that leads to a hanging recvfrom.  To avoid
	 * what appears to be a kernel bug, check_msg_errqueue
	 * uses poll(2) and tells us if there is anything for us
	 * to read.
	 *
	 * This is early enough that teardown isn't required:
	 * just return on failure.
	 */
	if (!check_msg_errqueue(ifp, POLLIN))
		return; /* no normal message to read */

#endif /* defined(IP_RECVERR) && defined(MSG_ERRQUEUE) */

	md = alloc_md();
	md->iface = ifp;

	if (read_packet(md))
		process_packet(&md);

	if (md != NULL)
		release_md(md);

	cur_state = NULL;
	reset_cur_connection();
	cur_from = NULL;
}

Project: libreswan
File: demux.c

static bool read_packet(struct msg_digest *md)
{
	const struct iface_port *ifp = md->iface;
	int packet_len;
	/* ??? this buffer seems *way* too big */
	u_int8_t bigbuffer[MAX_INPUT_UDP_SIZE];

	u_int8_t *_buffer = bigbuffer;
	union {
		struct sockaddr sa;
		struct sockaddr_in sa_in4;
		struct sockaddr_in6 sa_in6;
	} from
#if defined(HAVE_UDPFROMTO)
	, to
#endif
	;
	socklen_t from_len = sizeof(from);
#if defined(HAVE_UDPFROMTO)
	socklen_t to_len   = sizeof(to);
#endif
	err_t from_ugh = NULL;
	static const char undisclosed[] = "unknown source";

	happy(anyaddr(addrtypeof(&ifp->ip_addr), &md->sender));
	zero(&from.sa);

#if defined(HAVE_UDPFROMTO)
	packet_len = recvfromto(ifp->fd, bigbuffer,
				sizeof(bigbuffer), /*flags*/ 0,
				&from.sa, &from_len,
				&to.sa, &to_len);
#else
	packet_len = recvfrom(ifp->fd, bigbuffer,
			      sizeof(bigbuffer), /*flags*/ 0,
			      &from.sa, &from_len);
#endif

	/* we do not do anything with *to* addresses yet... we will */

	/* First: digest the from address.
	 * We presume that nothing here disturbs errno.
	 */
	if (packet_len == -1 &&
	    from_len == sizeof(from) &&
	    all_zero((const void *)&from.sa, sizeof(from))) {
		/* "from" is untouched -- not set by recvfrom */
		from_ugh = undisclosed;
	} else if (from_len   <
		   (int) (offsetof(struct sockaddr,
				   sa_family) + sizeof(from.sa.sa_family))) {
		from_ugh = "truncated";
	} else {
		const struct af_info *afi = aftoinfo(from.sa.sa_family);

		if (afi == NULL) {
			from_ugh = "unexpected Address Family";
		} else if (from_len != afi->sa_sz) {
			from_ugh = "wrong length";
		} else {
			switch (from.sa.sa_family) {
			case AF_INET:
				from_ugh = initaddr(
					(void *) &from.sa_in4.sin_addr,
					sizeof(from.sa_in4.sin_addr),
					AF_INET, &md->sender);
				setportof(from.sa_in4.sin_port, &md->sender);
				md->sender_port = ntohs(from.sa_in4.sin_port);
				break;
			case AF_INET6:
				from_ugh = initaddr(
					(void *) &from.sa_in6.sin6_addr,
					sizeof(from.sa_in6.
					       sin6_addr),
					AF_INET6, &md->sender);
				setportof(from.sa_in6.sin6_port, &md->sender);
				md->sender_port = ntohs(from.sa_in6.sin6_port);
				break;
			}
		}
	}

	/* now we report any actual I/O error */
	if (packet_len == -1) {
		if (from_ugh == undisclosed &&
		    errno == ECONNREFUSED) {
			/* Tone down scary message for vague event:
			 * We get "connection refused" in response to some
			 * datagram we sent, but we cannot tell which one.
			 */
			libreswan_log(
				"some IKE message we sent has been rejected with ECONNREFUSED (kernel supplied no details)");
		} else if (from_ugh != NULL) {
			log_errno((e,
				   "recvfrom on %s failed; Pluto cannot decode source sockaddr in rejection: %s",
				   ifp->ip_dev->id_rname, from_ugh));
		} else {
			log_errno((e, "recvfrom on %s from %s:%u failed",
				   ifp->ip_dev->id_rname,
				   ip_str(&md->sender),
				   (unsigned)md->sender_port));
		}

		return FALSE;
	} else if (from_ugh != NULL) {
		libreswan_log(
			"recvfrom on %s returned malformed source sockaddr: %s",
			ifp->ip_dev->id_rname, from_ugh);
		return FALSE;
	}
	cur_from = &md->sender;
	cur_from_port = md->sender_port;

	if (ifp->ike_float == TRUE) {
		u_int32_t non_esp;
		if (packet_len < (int)sizeof(u_int32_t)) {
			libreswan_log("recvfrom %s:%u too small packet (%d)",
				      ip_str(
					      cur_from), (unsigned) cur_from_port,
				      packet_len);
			return FALSE;
		}
		memcpy(&non_esp, _buffer, sizeof(u_int32_t));
		if (non_esp != 0) {
			libreswan_log("recvfrom %s:%u has no Non-ESP marker",
				      ip_str(
					      cur_from),
				      (unsigned) cur_from_port);
			return FALSE;
		}
		_buffer += sizeof(u_int32_t);
		packet_len -= sizeof(u_int32_t);
	}

	/* Clone actual message contents
	 * and set up md->packet_pbs to describe it.
	 */
	init_pbs(&md->packet_pbs
		 , clone_bytes(_buffer, packet_len,
			       "message buffer in comm_handle()")
		 , packet_len, "packet");

	DBG(DBG_RAW | DBG_CRYPT | DBG_PARSING | DBG_CONTROL,
	    DBG_log("*received %d bytes from %s:%u on %s (port=%d)",
		    (int) pbs_room(&md->packet_pbs),
		    ip_str(cur_from), (unsigned) cur_from_port,
		    ifp->ip_dev->id_rname,
		    ifp->port)
	    );

	DBG(DBG_RAW,
	    DBG_dump("", md->packet_pbs.start, pbs_room(&md->packet_pbs)));

	/* We think that in 2013 Feb, Apple iOS Racoon
	 * sometimes generates an extra useless buggy confusing
	 * Non ESP Marker
	 */
	{
		static const u_int8_t non_ESP_marker[NON_ESP_MARKER_SIZE] =
		{ 0x00, };

		if (md->iface->ike_float &&
		    pbs_left(&md->packet_pbs) >= NON_ESP_MARKER_SIZE &&
		    memcmp(md->packet_pbs.cur, non_ESP_marker,
			   NON_ESP_MARKER_SIZE) == 0) {
			bool happy = in_raw(NULL, NON_ESP_MARKER_SIZE,
					    &md->packet_pbs,
					    "spurious extra Non ESP Marker");
			libreswan_log(
				"Removed spurious non-esp marker from IKE packet - Racoon bug");
			passert(happy);
		}
	}

	if ((pbs_room(&md->packet_pbs) == 1) &&
	    (md->packet_pbs.start[0] == 0xff)) {
		/**
		 * NAT-T Keep-alive packets should be discared by kernel ESPinUDP
		 * layer. But boggus keep-alive packets (sent with a non-esp marker)
		 * can reach this point. Complain and discard them.
		 */
		DBG(DBG_NATT,
		    DBG_log(
			    "NAT-T keep-alive (boggus ?) should not reach this point. "
			    "Ignored. Sender: %s:%u", ip_str(cur_from),
			    (unsigned) cur_from_port);
		    );
		return FALSE;
	}

	return TRUE;
}