4

I found a couple of interesting integer underflows leading to memcpy() wild copies in a TLV parser process of some random IoT firmware. It is 32-bit ARMv7.

I'm able to emulate the userspace process using qemu and debug it, I can confirm the wild copy by inspecting register state before the memcpy() and memory state after it, but when I let it run, it won't crash. No page fault, no overwritten PC, it just exits.

I tried to come up with some C code that resembles what my code in the field does:

#include <string.h>
#include <stdlib.h>

void parse_stuff(char*);

int main(int argc, char** argv) {
        char buffer[128];
        char c;

        while (1) {
                parse_stuff(buffer);
                c = getc(stdin);
                if (c == 'e')
                        return 0;
        }

}

void parse_stuff(char* buf){
        char* heap = malloc(0x1000);
        for(int i=0;i<0x1000;i++)
                heap[i] = 0x41;

        memcpy(buf, heap, 0xffffffff);
}

If I compile this and run it in qemu, it won't crash.

There is a tight loop and the parser routine is called from that, so I'm guessing if the parse_stuff stack frame is a leaf, then a stack bof could only happen when the caller is the one trying to restore the PC to its caller (entry / libc_start_main, whatever), but I'm still puzzled by this, because on x86-64, this easily crashes so somehow the ARM binary or maybe the QEMU environment does not trigger that page violation that I'm expecting to happen due to the wild memcpy(), even if there is no PC control due to the cyclometric situation this way.

Has any of you run into something like this?

3
  • Hi and welcome to RE.SE. Uhm, I am surprised that this even compiles for you (#include <stdio.h> is missing and a cast of the malloc() return value). What compiler do you use (also exact version)? What compiler and linker options? Also, are you certain that this is the exact code you are observing and that this isn't perchance some sort of implementation where the 0xffffffff for the length happens to mean -1 and some "magic" happens (such as looking for a terminating character as with strings)? godbolt.org/z/Machnnr8Y
    – 0xC0000022L
    Mar 14, 2023 at 19:48
  • Which level of the optimizations are you using?
    – w s
    Mar 15, 2023 at 19:06
  • 1
    Can you add to the question the exact command line you running qemu and compiler with?
    – w s
    Mar 15, 2023 at 19:41

1 Answer 1

2

UPDATE: Ok, so the older glibc noncompliance I re-discovered was actually fixed back then, and it is a CVE by itself, CVE-2020-6096:

https://github.com/bminor/glibc/commit/beea361050728138b82c57dda0c4810402d342b9

Ok, so I resolved this. The glibc memcpy implementation in place has this code:

https://github.com/rilian-la-te/glibc/blob/master/sysdeps/arm/armv7/multiarch/memcpy_impl.S#L300

    mov dst, dstin  /* Preserve dstin, we need to return it.  */
    cmp count, #64
    bge .Lcpy_not_short

This is signed comparison, so a 0xffffffff-style wild copy is not possible, becuase it is treated as a short copy. Feels weird to perform a signed comparison of a size_t parameter in assembly without any additional comments by glibc authors, but it ends up being bit of a security measure.

On my system, though,

CHAR_BIT       = 8
MB_LEN_MAX     = 16

CHAR_MIN       = +0
CHAR_MAX       = +255
SCHAR_MIN      = -128
SCHAR_MAX      = +127
UCHAR_MAX      = 255

SHRT_MIN       = -32768
SHRT_MAX       = +32767
USHRT_MAX      = 65535

INT_MIN        = -2147483648
INT_MAX        = +2147483647
UINT_MAX       = 4294967295

LONG_MIN       = -2147483648
LONG_MAX       = +2147483647
ULONG_MAX      = 4294967295

LLONG_MIN      = -9223372036854775808
LLONG_MAX      = +9223372036854775807
ULLONG_MAX     = 18446744073709551615

PTRDIFF_MIN    = -2147483648
PTRDIFF_MAX    = +2147483647
SIZE_MAX       = 4294967295
SIG_ATOMIC_MIN = -2147483648
SIG_ATOMIC_MAX = +2147483647
WCHAR_MIN      = +0
WCHAR_MAX      = +4294967295
WINT_MIN       = 0
WINT_MAX       = 4294967295

Meaning that a memcpy of 2147483647+1 bytes is treated like this as well as a short copy that doesn't actually do much, even though that 'n' is well within SIZE_MAX. Not a practical thing to do, and this will mean that src and dst memory areas do overlap, but still feels like noncompliance.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.