CVE-2011-4914 (C, published 2012-06-21T23:55Z)
Description: value: The ROSE protocol implementation in the Linux kernel before 2.6.39 does not verify that certain data-length values are consistent with the amount of data sent, which might allow remote attackers to obtain sensitive information from kernel memory or cause a denial of service (out-of-bounds read) via crafted data to a ROSE socket.
Severity: MEDIUM (CVSS v3 base score: )
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/rose.h
net/rose/af_rose.c
net/rose/rose_loopback.c
net/rose/rose_route.c
net/rose/rose_subr.c
Commit message: rose: Add length checks to CALL_REQUEST parsing

Define some constant offsets for CALL_REQUEST based on the description
at <http://www.techfest.com/networking/wan/x25plp.htm> and the
definition of ROSE as using 10-digit (5-byte) addresses.  Use them
consistently.  Validate all implicit and explicit facilities lengths.
Validate the address length byte rather than either trusting or
assuming its value.

Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
Signed-off-by: David S. Miller <davem@davemloft.net>


Vulnerable code:
extern int  rose_parse_facilities(unsigned char *, struct rose_facilities_struct *);
	int n, len;
	len  = (((skb->data[3] >> 4) & 0x0F) + 1) >> 1;
	len += (((skb->data[3] >> 0) & 0x0F) + 1) >> 1;
	if (!rose_parse_facilities(skb->data + len + 4, &facilities)) {
		dest      = (rose_address *)(skb->data + 4);
	int len, res = 0;
	src_addr  = (rose_address *)(skb->data + 9);
	dest_addr = (rose_address *)(skb->data + 4);
	len  = (((skb->data[3] >> 4) & 0x0F) + 1) >> 1;
	len += (((skb->data[3] >> 0) & 0x0F) + 1) >> 1;

	if (!rose_parse_facilities(skb->data + len + 4, &facilities)) {
		*dptr++ = 0xAA;
int rose_parse_facilities(unsigned char *p,
	if (facilities_len == 0)
	while (facilities_len > 0) {
		if (*p == 0x00) {
			facilities_len--;
			p++;

			switch (*p) {
			case FAC_NATIONAL:		/* National */
				len = rose_parse_national(p + 1, facilities, facilities_len - 1);
				if (len < 0)
					return 0;
				facilities_len -= len + 1;
				p += len + 1;
				break;

			case FAC_CCITT:		/* CCITT */
				len = rose_parse_ccitt(p + 1, facilities, facilities_len - 1);
				if (len < 0)
					return 0;
				facilities_len -= len + 1;
				p += len + 1;
				break;

			default:
				printk(KERN_DEBUG "ROSE: rose_parse_facilities - unknown facilities family %02X\n", *p);
				facilities_len--;
				p++;
				break;
			}
		} else
			break;	/* Error in facilities format */
	return 1;

Fixed code:
#define	ROSE_CALL_REQ_ADDR_LEN_OFF	3
#define	ROSE_CALL_REQ_ADDR_LEN_VAL	0xAA	/* each address is 10 digits */
#define	ROSE_CALL_REQ_DEST_ADDR_OFF	4
#define	ROSE_CALL_REQ_SRC_ADDR_OFF	9
#define	ROSE_CALL_REQ_FACILITIES_OFF	14

extern int  rose_parse_facilities(unsigned char *, unsigned int, struct rose_facilities_struct *);
	int n;
	if (!rose_parse_facilities(skb->data + ROSE_CALL_REQ_FACILITIES_OFF,
				   skb->len - ROSE_CALL_REQ_FACILITIES_OFF,
				   &facilities)) {
		if (skb->len < ROSE_MIN_LEN) {
			kfree_skb(skb);
			continue;
		}
		if (frametype == ROSE_CALL_REQUEST &&
		    (skb->len <= ROSE_CALL_REQ_FACILITIES_OFF ||
		     skb->data[ROSE_CALL_REQ_ADDR_LEN_OFF] !=
		     ROSE_CALL_REQ_ADDR_LEN_VAL)) {
			kfree_skb(skb);
			continue;
		}
		dest      = (rose_address *)(skb->data + ROSE_CALL_REQ_DEST_ADDR_OFF);
	int res = 0;
	if (skb->len < ROSE_MIN_LEN)
		return res;
	if (frametype == ROSE_CALL_REQUEST &&
	    (skb->len <= ROSE_CALL_REQ_FACILITIES_OFF ||
	     skb->data[ROSE_CALL_REQ_ADDR_LEN_OFF] !=
	     ROSE_CALL_REQ_ADDR_LEN_VAL))
		return res;
	src_addr  = (rose_address *)(skb->data + ROSE_CALL_REQ_SRC_ADDR_OFF);
	dest_addr = (rose_address *)(skb->data + ROSE_CALL_REQ_DEST_ADDR_OFF);
	if (!rose_parse_facilities(skb->data + ROSE_CALL_REQ_FACILITIES_OFF,
				   skb->len - ROSE_CALL_REQ_FACILITIES_OFF,
				   &facilities)) {
		*dptr++ = ROSE_CALL_REQ_ADDR_LEN_VAL;
			if (len < 2)
				return -1;
			if (len < 3)
				return -1;
			if (len < 4)
				return -1;
			if (len < 2)
				return -1;
			if (len < 2 + l)
				return -1;
					if (l < AX25_ADDR_LEN)
						return -1;
					if (l < AX25_ADDR_LEN)
						return -1;
				if (l < AX25_ADDR_LEN)
					return -1;
				if (l < 1 + ROSE_ADDR_LEN)
					return -1;
				if (l % AX25_ADDR_LEN)
					return -1;
			if (len < 2)
				return -1;
			if (len < 3)
				return -1;
			if (len < 4)
				return -1;
			if (len < 2)
				return -1;
int rose_parse_facilities(unsigned char *p, unsigned packet_len,
	if (facilities_len == 0 || (unsigned)facilities_len > packet_len)
	while (facilities_len >= 3 && *p == 0x00) {
		facilities_len--;
		p++;

		switch (*p) {
		case FAC_NATIONAL:		/* National */
			len = rose_parse_national(p + 1, facilities, facilities_len - 1);
			break;

		case FAC_CCITT:		/* CCITT */
			len = rose_parse_ccitt(p + 1, facilities, facilities_len - 1);
			break;

		default:
			printk(KERN_DEBUG "ROSE: rose_parse_facilities - unknown facilities family %02X\n", *p);
			len = 1;
			break;
		}

		if (len < 0)
			return 0;
		if (WARN_ON(len >= facilities_len))
			return 0;
		facilities_len -= len + 1;
		p += len + 1;
	return facilities_len == 0;