ROSE: prevent heap corruption with bad facilities
Subsystems (2)
PATCH v2 1/1 ROSE: prevent heap corruption with bad facilities
When parsing the FAC_NATIONAL_DIGIS facilities field, it's possible for a remote host to provide more digipeaters than expected, resulting in heap corruption. Check against ROSE_MAX_DIGIS to prevent overflows, and abort facilities parsing on failure.
Additionally, when parsing the FAC_CCITT_DEST_NSAP and FAC_CCITT_SRC_NSAP facilities fields, a remote host can provide a length of less than 10, resulting in an underflow in a memcpy size, causing a kernel panic due to massive heap corruption. A length of greater than 20 results in a stack overflow of the callsign array. Abort facilities parsing on these invalid length values.
--- net/rose/rose_subr.c | 18 ++++++++++++++++-- 1 files changed, 16 insertions(+), 2 deletions(-)
net/rose/rose_subr.c +16 -2
@@ -290,10 +290,15 @@ static int rose_parse_national(unsigned char *p, struct rose_facilities_struct * facilities->source_ndigis =0 ; facilities->dest_ndigis =0 ;for (pt = p +2 , lg =0 ; lg < l ; pt +=AX25_ADDR_LEN , lg +=AX25_ADDR_LEN ) { -if (pt[6 ] &AX25_HBIT ) +if (pt[6 ] &AX25_HBIT ) { +if (facilities->dest_ndigis >=ROSE_MAX_DIGIS ) +return -1 ;memcpy (&facilities->dest_digis[facilities->dest_ndigis++], pt,AX25_ADDR_LEN ); -else + }else { +if (facilities->source_ndigis >=ROSE_MAX_DIGIS ) +return -1 ;memcpy (&facilities->source_digis[facilities->source_ndigis++], pt,AX25_ADDR_LEN ); + } } } p += l +2 ;
@@ -333,6 +338,11 @@ static int rose_parse_ccitt(unsigned char *p, struct rose_facilities_struct *faccase 0xC0 : l = p[1 ]; + +/* Prevent overflows*/ +if (l <10 || l >20 ) +return -1 ; +if (*p ==FAC_CCITT_DEST_NSAP ) {memcpy (&facilities->source_addr, p +7 ,ROSE_ADDR_LEN );memcpy (callsign, p +12 , l -10 );
@@ -373,12 +383,16 @@ int rose_parse_facilities(unsigned char *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 ;
Quoted 11 lines · Dan Rosenberg
When parsing the
FAC_NATIONAL_DIGISfacilities field, it's possible for a remote host to provide more digipeaters than expected, resulting in heap corruption. Check againstROSE_MAX_DIGISto prevent overflows, and abort facilities parsing on failure.Additionally, when parsing the
FAC_CCITT_DEST_NSAPandFAC_CCITT_SRC_NSAPfacilities fields, a remote host can provide a length of less than 10, resulting in an underflow in a memcpy size, causing a kernel panic due to massive heap corruption. A length of greater than 20 results in a stack overflow of the callsign array. Abort facilities parsing on these invalid length values.
[...]
Look further and you'll see that the *entire* parsing code makes not the slightest attempt to validate lengths.
This applies on top of your patch, and hopefully fixes the remaining bugs. It's compile-tested only.
Ben.
---
Subject: [PATCH] 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.
--- include/net/rose.h | 8 ++++- net/rose/af_rose.c | 8 ++-- net/rose/rose_loopback.c | 13 ++++++- net/rose/rose_route.c | 20 +++++++---- net/rose/rose_subr.c | 91 +++++++++++++++++++++++++++++----------------- 5 files changed, 93 insertions(+), 47 deletions(-)
include/net/rose.h +7 -1
@@ -14,6 +14,12 @@#define ROSE_MIN_LEN 3 +#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 +#define ROSE_GFI 0x10#define ROSE_Q_BIT 0x80#define ROSE_D_BIT 0x40
@@ -214,7 +220,7 @@ extern void rose_requeue_frames(struct sock *);extern int rose_validate_nr (struct sock *,unsigned short );extern void rose_write_internal (struct sock *,int );extern int rose_decode (struct sk_buff *,int *,int *,int *,int *,int *); -extern int rose_parse_facilities (unsigned char *,struct rose_facilities_struct *); +extern int rose_parse_facilities (unsigned char *,unsigned int ,struct rose_facilities_struct *);extern void rose_disconnect (struct sock *,int ,int ,int );/* rose_timer.c */
net/rose/af_rose.c +4 -4
@@ -978,7 +978,7 @@ int rose_rx_call_request(struct sk_buff *skb, struct net_device *dev, struct rosstruct sock *make;struct rose_sock *make_rose;struct rose_facilities_struct facilities; -int n, len; +int n; skb->sk =NULL ;/* Initially we don't know who it's for */
@@ -987,9 +987,9 @@ int rose_rx_call_request(struct sk_buff *skb, struct net_device *dev, struct ros*/ memset (&facilities,0x00 ,sizeof (struct rose_facilities_struct )); - 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)) { +if (!rose_parse_facilities (skb->data +ROSE_CALL_REQ_FACILITIES_OFF , + skb->len -ROSE_CALL_REQ_FACILITIES_OFF , + &facilities)) {rose_transmit_clear_request (neigh, lci,ROSE_INVALID_FACILITY ,76 );return 0 ; }
net/rose/rose_loopback.c +12 -1
@@ -73,9 +73,20 @@ static void rose_loopback_timer(unsigned long param)unsigned int lci_i, lci_o;while ((skb =skb_dequeue (&loopback_queue)) !=NULL ) { +if (skb->len <ROSE_MIN_LEN ) { +kfree_skb (skb); +continue ; + } lci_i = ((skb->data[0 ] <<8 ) &0xF00 ) + ((skb->data[1 ] <<0 ) &0x0FF ); frametype = skb->data[2 ]; - dest = (rose_address *)(skb->data +4 ); +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 ); lci_o =ROSE_DEFAULT_MAXVC +1 - lci_i;skb_reset_transport_header (skb);
net/rose/rose_route.c +13 -7
@@ -861,7 +861,7 @@ int rose_route_frame(struct sk_buff *skb, ax25_cb *ax25)unsigned int lci, new_lci;unsigned char cause, diagnostic;struct net_device *dev; -int len, res =0 ; +int res =0 ;char buf[11 ];#if 0
@@ -869,10 +869,17 @@ int rose_route_frame(struct sk_buff *skb, ax25_cb *ax25)return res;#endif +if (skb->len <ROSE_MIN_LEN ) +return res; frametype = skb->data[2 ]; lci = ((skb->data[0 ] <<8 ) &0xF00 ) + ((skb->data[1 ] <<0 ) &0x0FF ); - src_addr = (rose_address *)(skb->data +9 ); - dest_addr = (rose_address *)(skb->data +4 ); +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 );spin_lock_bh (&rose_neigh_list_lock);spin_lock_bh (&rose_route_list_lock);
@@ -1010,12 +1017,11 @@ int rose_route_frame(struct sk_buff *skb, ax25_cb *ax25) goto out; } - len = (((skb->data[3 ] >>4 ) &0x0F ) +1 ) >>1 ; - len += (((skb->data[3 ] >>0 ) &0x0F ) +1 ) >>1 ; -memset (&facilities,0x00 ,sizeof (struct rose_facilities_struct )); -if (!rose_parse_facilities (skb->data + len +4 , &facilities)) { +if (!rose_parse_facilities (skb->data +ROSE_CALL_REQ_FACILITIES_OFF , + skb->len -ROSE_CALL_REQ_FACILITIES_OFF , + &facilities)) {rose_transmit_clear_request (rose_neigh, lci,ROSE_INVALID_FACILITY ,76 ); goto out; }
net/rose/rose_subr.c +57 -34
181 lines not on this page · raw message
Signature · 5 lines
1.7.4.1 -- Ben Hutchings Once a job is fouled up, anything done to improve it makes it worse.
From: Ben Hutchings <[email protected]>
Date: Sun, 20 Mar 2011 16:48:05 +0000
Quoted 10 lines
Subject: [PATCH] rose: Add length checks to
CALL_REQUESTparsingDefine some constant offsets for
CALL_REQUESTbased 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 <[email protected]>
Applied.
Quoted 12 lines · David Miller
From: Ben Hutchings <[email protected]>
Date: Sun, 20 Mar 2011 16:48:05 +0000Quoted 9 lines
Subject: [PATCH] rose: Add length checks to
CALL_REQUESTparsingDefine some constant offsets for
CALL_REQUESTbased 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.
Quoted 3 lines
Quoted 1 line
Signed-off-by Ben Hutchings <[email protected]>Applied.
Ralf, I would really appreciate it if you could test this soon...
Ben.
Signature · 2 lines
Ben Hutchings Once a job is fouled up, anything done to improve it makes it worse.
Ralf Baechle
Acked-byQuoted 13 lines · Ben Hutchings
Quoted 12 lines · David Miller
From: Ben Hutchings <[email protected]>
Date: Sun, 20 Mar 2011 16:48:05 +0000Quoted 9 lines
Subject: [PATCH] rose: Add length checks to
CALL_REQUESTparsingDefine some constant offsets for
CALL_REQUESTbased 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.
Quoted 5 lines
Quoted 3 lines
Quoted 1 line
Signed-off-by Ben Hutchings <[email protected]>Applied.
Ralf, I would really appreciate it if you could test this soon...
Actual testing is a problem atm. But I've reviewed the patche and it appears ok.
Ralf
Quoted 66 lines · Ben Hutchings
(quoted diff) +22
-32@@ -365,49 +393,44 @@ static int rose_parse_ccitt(unsigned char *p, struct rose_facilities_struct *fac return n; } -int rose_parse_facilities(unsigned char *p, +int rose_parse_facilities(unsigned char *p, unsigned packet_len, struct rose_facilities_struct *facilities) { int facilities_len, len; facilities_len = *p++; - if (facilities_len == 0) + if (facilities_len == 0 || (unsigned)facilities_len > packet_len) return 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 */ + 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)
Review static int rose_parse_ccitt(unsigned char *p, struct rose_facilities_struct *fac · line 434
Quoted 10 lines
(quoted diff) +6
-1+ return 0; + if (WARN_ON(len >= facilities_len)) + return 0; + facilities_len -= len + 1; + p += len + 1; } - return 1; + return facilities_len == 0; }
This last hunk does not look correct. In the default branch of the switch, you set len = 1, which means
p += 2; facilities_len -= 2.
The original code does
facilities_len--; p++;
... and it looks correct. So, to get the old behaviour back:
net/rose/rose_subr.c +1 -1
@@ -418,7 +418,7 @@ int rose_parse_facilities(unsigned char *p, unsigned packet_len,default :printk (KERN_DEBUG "ROSE: rose_parse_facilities - unknown facilities family %02X\n" , *p); - len =1 ; + len =0 ;break ; }
However, I wonder how much sense it makes to continue parsing the facilities if an unknown facility family appears. We don't know the length of its data, so we will interpret each 16 bytes a new facilities header, hopefully soon bailing out on *p != 0x00.
In case of a long packet where every other byte is zero, the loop will spam the kernel log with the printk ... which could probably be classified as a security problem on its own. So how about the following instead? I have no idea if this breaks some rose specification, though.
net/rose/rose_subr.c +1 -2
@@ -418,8 +418,7 @@ int rose_parse_facilities(unsigned char *p, unsigned packet_len,default :printk (KERN_DEBUG "ROSE: rose_parse_facilities - unknown facilities family %02X\n" , *p); - len =1 ; -break ; +return 0 ; }if (len <0 )
Signature · 2 lines
Jiri Bohac <[email protected]> SUSE Labs, SUSE CZ
Quoted 3 lines · Jiri Bohac
However, I wonder how much sense it makes to continue parsing the facilities if an unknown facility family appears. We don't know the length of its data, so we will interpret each 16 bytes a new
oops, typo:
s/16 bytes a new/16 bits as a new/
Quoted 7 lines
facilities header, hopefully soon bailing out on *p !=
0x00.In case of a long packet where every other byte is zero, the loop will spam the kernel log with the printk ... which could probably be classified as a security problem on its own. So how about the following instead? I have no idea if this breaks some rose specification, though.
Signature · 2 lines
Jiri Bohac <[email protected]> SUSE Labs, SUSE CZ
David Miller
maintainerFrom: Jiri Bohac <[email protected]>
Date: Thu, 31 Mar 2011 20:02:25 +0200
Quoted 1 line
So, to get the old behaviour back:
Jiri, please do not submit two patches in one email, it's beyond confusing.
Instead, please submit a proper two-patch series.
Thanks.
Quoted 8 lines · David Miller
From: Jiri Bohac <[email protected]>
Date: Thu, 31 Mar 2011 20:02:25 +0200Quoted 1 line
So, to get the old behaviour back:
Jiri, please do not submit two patches in one email, it's beyond confusing.
Instead, please submit a proper two-patch series.
The two one line patches were two suggestions what to do. It's either one or the other...
Sorry for the confusion.
Signature · 2 lines
Jiri Bohac <[email protected]> SUSE Labs, SUSE CZ
On Thu, 2011-03-31 at 20:02 +0200, Jiri Bohac wrote:
[...]
Quoted 13 lines
This last hunk does not look correct. In the default branch of the switch, you set len = 1, which means
p += 2; facilities_len -= 2.The original code does
facilities_len--; p++;... and it looks correct. So, to get the old behaviour back:
net/rose/rose_subr.c +0
-0
net/rose/rose_subr.c int rose_parse_facilities(unsigned char *p, unsigned packet_len, · line 423
Quoted 7 lines
net/rose/rose_subr.c +1
-1@@ -418,7 +418,7 @@ int rose_parse_facilities(unsigned char *p, unsigned packet_len,default :printk (KERN_DEBUG "ROSE: rose_parse_facilities - unknown facilities family %02X\n" , *p); - len =1 ; + len =0 ;break ; }
Yes, agreed.
Quoted 10 lines
However, I wonder how much sense it makes to continue parsing the facilities if an unknown facility family appears. We don't know the length of its data, so we will interpret each 16 bytes a new facilities header, hopefully soon bailing out on *p !=
0x00.In case of a long packet where every other byte is zero, the loop will spam the kernel log with the printk ... which could probably be classified as a security problem on its own. So how about the following instead? I have no idea if this breaks some rose specification, though.
[...]
I don't know any more than you do; maybe Ralf knows or can find out.
Ben.
Signature · 2 lines
Ben Hutchings Once a job is fouled up, anything done to improve it makes it worse.
From: Dan Rosenberg <[email protected]>
Date: Sun, 20 Mar 2011 02:43:43 -0400
Quoted 12 lines
When parsing the
FAC_NATIONAL_DIGISfacilities field, it's possible for a remote host to provide more digipeaters than expected, resulting in heap corruption. Check againstROSE_MAX_DIGISto prevent overflows, and abort facilities parsing on failure.Additionally, when parsing the
FAC_CCITT_DEST_NSAPandFAC_CCITT_SRC_NSAPfacilities fields, a remote host can provide a length of less than 10, resulting in an underflow in a memcpy size, causing a kernel panic due to massive heap corruption. A length of greater than 20 results in a stack overflow of the callsign array. Abort facilities parsing on these invalid length values.
Quoted 2 lines
Signed-off-by Dan Rosenberg <[email protected]>
Applied.