[U-Boot] Remote code execution vulnerabilities in U-Boot's NFS and other IP parsing code

Fermín Serna fermin at semmle.com
Mon Jul 22 22:40:28 UTC 2019


Hello,

Find attached more information about 13 vulnerabilities we found at
U-Boot and its NFS and networking code. Also, find attached a proposed
quick patch that should serve as a first initial one and should
probably go through iterations of code review.

Please note, these vulnerabilities are not patched yet at the source
repository. Tom Rini (U-boot's master custodian) requested the
attached report to be published at this mailing list. At this time,
and because of this email, we consider these vulnerabilities public.

For reference, MITRE has issued CVEs for the vulnerabilities:
CVE-2019-14192, CVE-2019-14193, CVE-2019-14194, CVE-2019-14195,
CVE-2019-14196, CVE-2019-14197, CVE-2019-14198, CVE-2019-14199,
CVE-2019-14200, CVE-2019-14201, CVE-2019-14202, CVE-2019-14203 and
CVE-2019-14204

Best regards,
--
Fermin
Semmle Security Research Team
-------------- next part --------------
Semmle security advisory: U-Boot, 13 vulnerabilities leading to potential remote code execution


Date: 5/23/2019
Status: Developers contacted, patch proposed and waiting for CVEs
Author(s): Fermin J. Serna, Pavel Avgustinov and Kevin Backhouse
CVE(s) assigned: CVE-2019-14192, CVE-2019-14193, CVE-2019-14194, CVE-2019-14195, CVE-2019-14196, CVE-2019-14197, CVE-2019-14198, CVE-2019-14199, CVE-2019-14200, CVE-2019-14201, CVE-2019-14202, CVE-2019-14203 and CVE-2019-14204




1.- Overview:


Das U-Boot [1] (commonly known as “universal boot loader”) is a popular primary
bootloader widely used in embedded devices to fetch from different sources and
run the next stage code, commonly but not limited to a Linux Kernel. It is
commonly used by IoT devices but also Kindle and ARM ChromeOS devices.


U-Boot supports fetching the next stage code from different file partition
formats (ext4 as an example) but also from the network (TFTP and NFS). Please
note, U-boot supports verified boot [2] where the image fetched is checked for
tampering, mitigating the risks of using insecure cleartext protocols such as
TFTP and NFS, so any vulnerability before the signature check could mean a
device jailbreak.


The first vulnerability was found via source code review and we used Semmle’s
LGTM.com and QL to find the others. We used the following query [3] that gave us
a very manageable list of 9 results to follow up manually.


We ended up identifying 7 different memcpy-ish call sites with attacked
controlled source buffer and unchecked lengths. Additionally, after some code
review we found 5 new additional stack based buffer overflows and an extra read out of bounds vulnerability.




2. - Technical description:


2.1. - Unbound memcpy with unvalidated length at nfs_readlink_reply (x2)


The problem exists at the nfs_readlink_reply [4] function that parses an nfs
reply coming from the network. It parses 4 bytes and without any further
validations it uses them as length for a memcpy in two different locations.


static int nfs_readlink_reply(uchar *pkt, unsigned len)
{


[...]


        /* new path length */
        rlen = ntohl(rpc_pkt.u.reply.data[1 + nfsv3_data_offset]);


        if (*((char *)&(rpc_pkt.u.reply.data[2 + nfsv3_data_offset])) != '/') {
                int pathlen;
                strcat(nfs_path, "/");
                pathlen = strlen(nfs_path);
                memcpy(nfs_path + pathlen,
                       (uchar *)&(rpc_pkt.u.reply.data[2 + nfsv3_data_offset]),
                       rlen);
                nfs_path[pathlen + rlen] = 0;
        } else {
                memcpy(nfs_path,
                       (uchar *)&(rpc_pkt.u.reply.data[2 + nfsv3_data_offset]),
                       rlen);
                nfs_path[rlen] = 0;
        }


        return 0;
}


The destination buffer nfs_path [5] is a global one that can hold up to 2048
bytes.




2.2. - Unbound memcpy with a failed length check at nfs_lookup_reply


The problem exists at the nfs_lookup_reply [6] function that again parses an 
nfs reply coming from the network. It parses 4 bytes and uses them as length for
a memcpy in two different locations.


A length check happens to make sure it is not bigger than the allocated buffer.
Unfortunately this check can be bypassed with a negative value that would lead
later to a large buffer overflow.


                filefh3_length = ntohl(rpc_pkt.u.reply.data[1]);
                if (filefh3_length > NFS3_FHSIZE)
                        filefh3_length  = NFS3_FHSIZE;
                memcpy(filefh, rpc_pkt.u.reply.data + 2, filefh3_length);




The destination buffer nfs_path [7] is a global one that can hold up to 64 bytes.




2.3.- Unbound memcpy with a failed length check at nfs_read_reply/store_block (x2)


The problem exists at the nfs_read_reply [8] function when reading a file and
storing it into other medium (flash or physical memory) for later processing.
Again, the data and length is fully controlled by the attacker and never
validated.


static int nfs_read_reply(uchar *pkt, unsigned len)
{


[...]


        if (supported_nfs_versions & NFSV2_FLAG) {
                rlen = ntohl(rpc_pkt.u.reply.data[18]); <- rlen is attacker controlled could be 0xFFFFFFFF
                data_ptr = (uchar *)&(rpc_pkt.u.reply.data[19]);


        } else {  /* NFSV3_FLAG */
                int nfsv3_data_offset =
                        nfs3_get_attributes_offset(rpc_pkt.u.reply.data);
                /* count value */
                rlen = ntohl(rpc_pkt.u.reply.data[1 + nfsv3_data_offset]); <- rlen is attacker controlled 
                /* Skip unused values :
                        EOF:            32 bits value,
                        data_size:      32 bits value,
                */
                data_ptr = (uchar *)
                        &(rpc_pkt.u.reply.data[4 + nfsv3_data_offset]);
        }


        if (store_block(data_ptr, nfs_offset, rlen)) <- We pass to store_block source and length controlled by the attacker
                        return -9999;


[...]


}


Focusing on physical memory part of the store_block function [9], it attempts to
reserve some memory using the arch specific function map_physmem, ending up
calling phys_to_virt. As you can see below at the x86 [10] implementation, when
reserving physical memory it clearly ignores length and gives you a raw pointer
without checking if surrounding areas are reserved (or not) for other purposes.


static inline void *phys_to_virt(phys_addr_t paddr)
{
        return (void *)(unsigned long)paddr;
}




Later at store_block there is a memcpy buffer overrun with attacker controlled
source and length.


static inline int store_block(uchar *src, unsigned offset, unsigned len)
{


[...]


 void *ptr = map_sysmem(load_addr + offset, len); ← essentially this is ptr = load_addr + offset
 memcpy(ptr, src, len); ← unrestricted overflow happens here
 unmap_sysmem(ptr);


[...]


}


Similar problem potentially exists with the flash_write code path.




2.4.- Unbound memcpy when parsing a UDP packet due to integer underflow (x2)


The function net_process_received_packet is subject to an integer underflow when
using ip->udp_len without validation [11]. Later this field is used for a memcpy
at nc_input_packet [12] and any udp packet handler set via net_set_udp_handler
(DNS, dhcp, ...).


#if defined(CONFIG_NETCONSOLE) && !defined(CONFIG_SPL_BUILD)
                nc_input_packet((uchar *)ip + IP_UDP_HDR_SIZE,
                                src_ip,
                                ntohs(ip->udp_dst),
                                ntohs(ip->udp_src),
                                ntohs(ip->udp_len) - UDP_HDR_SIZE); <- integer underflow
#endif
                /*
                 * IP header OK.  Pass the packet to the current handler.
                 */
                (*udp_packet_handler)((uchar *)ip + IP_UDP_HDR_SIZE,
                                      ntohs(ip->udp_dst),
                                      src_ip,
                                      ntohs(ip->udp_src),
                                      ntohs(ip->udp_len) - UDP_HDR_SIZE); <- integer underflow




Please note, we did not audit all potential udp handlers that are set for
different purposes (DNS, Dhcp, …). But we did on the nfs_handler and you can
read about it next.




2.5 - Multiple (5) stack based buffer overflow in nfs_handler reply helper functions


A code review variant of the previous one, where the integer underflows but also
with a large enough udp packet and ip->udp_len we end up calling nfs_handler.
In this function, again there is no validation of the length and we end up calling
helper functions such as nfs_readlink_reply where it blindly uses the length
without validation causing a stack based buffer overflow.


static int nfs_readlink_reply(uchar *pkt, unsigned len)
{


struct rpc_t rpc_pkt;


[...]


        memcpy((unsigned char *)&rpc_pkt, pkt, len);


We identified 5 different vulnerable functions subject to the same code pattern
leading to a stack based buffer overflow:


rpc_lookup_reply
nfs_mount_reply
nfs_umountall_reply
nfs_lookup_reply
nfs_readlink_reply


2.6 - Read out-of-bound data at nfs_read_reply


Very similar to the previous vulnerabilities discussed at 2.5 where the developers
tried to be careful and performing size checks while copy the data that come from 
the socket. While they checked to prevent the buffer overflow they did not check if
there was enough data at the source buffer leading to a potential read out of bounds. 


static int nfs_read_reply(uchar *pkt, unsigned len)
{


struct rpc_t rpc_pkt;


[...]


        memcpy(&rpc_pkt.u.data[0], pkt, sizeof(rpc_pkt.u.reply));


An attacker could supply an NFS packet with a read request but with a small packet
Request sent to the socket.




3.- Semmle’s QL query used for variant analysis:


import cpp


import semmle.code.cpp.dataflow.TaintTracking
import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis


class NetworkByteOrderTranslation extends Expr {
  NetworkByteOrderTranslation() {
    // On Windows, there are ntoh* functions.
    this.(Call).getTarget().getName().regexpMatch("ntoh(l|ll|s)")
    or
    // On Linux, and in some code bases, these are defined as macros.
    this = any(MacroInvocation mi |
        mi.getOutermostMacroAccess().getMacroName().regexpMatch("(?i)(^|.*_)ntoh(l|ll|s)")
      ).getExpr()
  }
}


class NetworkToMemFuncLength extends TaintTracking::Configuration {
  NetworkToMemFuncLength() { this = "NetworkToMemFuncLength" }


  override predicate isSource(DataFlow::Node source) {
       source.asExpr() instanceof NetworkByteOrderTranslation
  }
  
  override predicate isSink(DataFlow::Node sink) {
    exists (FunctionCall fc |
        fc.getTarget().getName().regexpMatch("memcpy|memmove") and
        fc.getArgument(2) = sink.asExpr() )
  } 
 
}


from Expr ntoh, Expr sizeArg, NetworkToMemFuncLength config
where config.hasFlow(DataFlow::exprNode(ntoh), DataFlow::exprNode(sizeArg))
select ntoh.getLocation(), sizeArg






4. - Recommendations:


In order to mitigate this vulnerabilities there are only two options: 
1) Apply patches as soon as they are released, or
2) While vulnerable, do not use mounting filesystems via NFS.




5.- Timeline:


This vulnerability report is subject to our disclosure policy available at https://lgtm.com/security/#disclosure_policy


5/15/2019 - Fermin finds initial two vulnerability and writes a dirty QL query.
            Uncovers 3 more problematic call sites
5/16/2019 - Pavel brings some QL magic and generalizes the query. Validates (and
            corrects) Fermin’s finding. Finds some more parsing ip and udp
            headers.
5/23/2019 - Kevin alerts us about our oversight around a stack based buffer
            overflow via nfs_handler.
5/23/2019 - After some other priorities, more QL magic and code review we
            decided to conclude the investigation and contact maintainers via
            email.
5/24/2019 - Tom Rini acknowledges receiving the security report.
7/19/2019 - Tom Rini requests to make this report public at their public mailing list  
            u-boot at lists.denx.de.  
7/22/2019 - In order to avoid a weekend disclosure, Fermin makes the report public at 
            u-boot at lists.denx.de.  




5.- References:


[1] https://en.wikipedia.org/wiki/Das_U-Boot
[2] https://github.com/u-boot/u-boot/blob/master/doc/uImage.FIT/verified-boot.txt
[3] https://lgtm.com/query/7709901104691588335/
[4] https://github.com/u-boot/u-boot/blob/11e409284ee593c980e16d93d12c5e19c99b62c0/net/nfs.c#L608
[5] https://github.com/u-boot/u-boot/blob/11e409284ee593c980e16d93d12c5e19c99b62c0/net/nfs.c#L75
[6] https://github.com/u-boot/u-boot/blob/5b4b680cfe29a67171ccbe439c66374cb31faca3/net/nfs.c#L571-L574
[7] https://github.com/u-boot/u-boot/blob/5b4b680cfe29a67171ccbe439c66374cb31faca3/net/nfs.c#L55
[8] https://github.com/u-boot/u-boot/blob/5b4b680cfe29a67171ccbe439c66374cb31faca3/net/nfs.c#L655
[9] https://github.com/u-boot/u-boot/blob/5b4b680cfe29a67171ccbe439c66374cb31faca3/net/nfs.c#L81
[10] https://github.com/u-boot/u-boot/blob/94905e1db8d8d42c4f39f14dbee2f9788390db5e/include/asm-generic/io.h#L30
[11] https://github.com/u-boot/u-boot/blob/5b4b680cfe29a67171ccbe439c66374cb31faca3/net/net.c#L1302-L1314
[12] https://github.com/u-boot/u-boot/blob/94905e1db8d8d42c4f39f14dbee2f9788390db5e/drivers/net/netconsole.c#L134
[13] https://github.com/u-boot/u-boot/blob/5b4b680cfe29a67171ccbe439c66374cb31faca3/net/net.c#L913
-------------- next part --------------
A non-text attachment was scrubbed...
Name: patch.diff
Type: application/octet-stream
Size: 5517 bytes
Desc: not available
URL: <http://lists.denx.de/pipermail/u-boot/attachments/20190722/583a8195/attachment.obj>


More information about the U-Boot mailing list