CVE-2020-6096
An exploitable signed comparison vulnerability exists in the ARMv7 memcpy() implementation of GNU glibc. Calling memcpy() (on ARMv7 targets that utilize the GNU glibc implementation) with a negative value for the ‘num’ parameter results in a signed comparison vulnerability.
If an attacker underflows the ‘num’ parameter to memcpy(), this vulnerability could lead to undefined behavior such as writing to out-of-bounds memory and potentially remote code execution. Furthermore, this memcpy() implementation allows for program execution to continue in scenarios where a segmentation fault or crash should have occurred. The dangers occur in that subsequent execution and iterations of this code will be executed with this corrupted data.
GNU glibc 2.30.9000
https://www.gnu.org/software/libc/
8.1 - CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-195 - Signed to Unsigned Conversion Error
The GNU C Library project provides the core libraries for the GNU system and GNU/Linux systems, as well as many other systems that use Linux as the kernel. These libraries provide critical APIs including ISO C11, POSIX.1-2008, BSD, OS-specific APIs and more. These APIs include such foundational facilities as open, read, write, malloc, printf, getaddrinfo, dlopen, pthread_create, crypt, login, exit and more.
If an attacker underflows the num
parameter to memcpy()
, this vulnerability could lead to undefined behavior such as writing to out-of-bounds memory and potentially remote code execution. Furthermore, this memcpy()
implementation allows for program execution to continue in scenarios where a segmentation fault or crash should have occurred. The dangers occur in that subsequent execution and iterations of this code will be executed with this corrupted data.
By definition, memcpy()
takes three parameters. The third parameter, num
, is a size_t
data type that contains the number of bytes to copy (http://man7.org/linux/man-pages/man3/memcpy.3.html). By definition, size_t
data types are treated as unsigned integers. The implementation of memcpy()
for ARMv7 targets does not properly handle the size_t
data type for the num
parameter. Instead of treating size_t
data types as unsigned, the ARMv7 memcpy()
implementation utilizes signed branch and arithmetic operations while operating on the num
parameter.
269 mov r12, r0
270 cmp r2, #64
271 bge 0x405ffcb4
At the beginning of the ARMv7 memcpy() implementation, the function parameter num
(number of bytes to copy) is compared with 64. If 64 or more bytes need to be copied, then program execution will branch, otherwise, execution will continue downward to copy under 64 bytes before returning.
By definition, the CMP instruction will subtract the value of the operand (64) from the register value (parameter num
, or register r2
) to generate the appropriate condition codes. If num
contains a negative value, the CMP
instruction will result in a negative result, thus setting the negative condition code. The before and after condition codes are shown below.
n z c v
(Before) 0 0 1 0
(After) 1 0 1 0
Because bge
is a signed branch (signed greater than or equal), it will not branch if the n
condition code is set. As a result, when presented with a negative value for num
, the ARMv7 memcpy()
function will not take this branch and will continue downward to copy less than 64 bytes.
To exhibit the differences between the memcpy()
implementation on ARMv7 versus other platforms and the vulnerability, a small test program was written. This program attempts to copy a total of 0xfffffd37 (+4294966583 or -713 in unsigned and signed decimal form respectively) bytes to a location in memory.
#include <string.h>
#include <stdio.h>
struct linebuffer {
int bufsz;
int nl_pos;
int len;
char buf[2048];
};
void do_memcpy(void)
{
struct linebuffer lb;
memset(&lb, 0, sizeof(lb));
memset(lb.buf, 'A', 2048);
lb.len = 0xfffffd37;
memcpy(lb.buf, lb.buf + 1024, lb.len);
}
int main(int argc, char **argv)
{
puts("before memcpy");
do_memcpy();
puts("after memcpy");
}
When run on x86 platforms, this program segmentation faults, because the num
argument to memcpy()
, 0xfffffd37, is interpreted properly as a size_t
value (which is an unsigned integer).
When run on ARMv7 hosts, memcpy()
does not treat the num
parameter as a size_t value, but rather as a signed integer. The num
value of 0xfffffd37 is interpreted as -713 which means only 55 bytes will be copied. Once copied, this program will successfully complete and the program will exit.
By the memcpy()
definition, the number of bytes to copy (num
) is expected to be an unsigned integer. To account for this, signed branch operations should not be performed on the num
parameter at any point during the memcpy()
implementation. Instead of bge
, the unsigned greater than or equal comparison, bhs
/bcs
, should be used. This would ensure that the num
parameter is treated as unsigned.
Several instances were found throughout the ARMv7 memcpy()
implementation (located in sysdeps/arm/armv7/multiarch/memcpy_impl.S
) of signed branches being used in place of their unsigned branch equivalents. These instances are chronicled below.
Line 271: bge .Lcpy_not_short
Line 354: blt .Ltail63aligned
Line 357: bge .Lcpy_body_long
Line 381: bge 1b
Line 415: bge 1b
Line 485: blt 2f
Line 497: bge 1b
2020-03-02 - Vendor Disclosure
2020-05-21 - Public Release
Discovered by Jason Royes and Samuel Dytrych of Cisco Security Assessment and Penetration Team.