odin-lang / Odin

Odin Programming Language
https://odin-lang.org
BSD 3-Clause "New" or "Revised" License
7.01k stars 620 forks source link

possible buffer overrun of runtime.memory_compare() and runtime.memory_compare_zero() #4405

Open beaumccartney opened 1 month ago

beaumccartney commented 1 month ago

using either of the procedures on memory i've mapped myself crashes after trying to dereference just after the last mapped byte (i.e. not valid address space).

Odin Report:

Odin:    dev-2024-10:9f609dd74
OS:      macOS Sequoia 15.0.1 (build: 24A348, kernel: 24.0.0)
CPU:     Apple M2
RAM:     16384 MiB
Backend: LLVM 18.1.8

repro:

package repro

main :: proc() {
    a := reserve_and_commit(mem.DEFAULT_PAGE_SIZE) or_else panic("1")
    b := reserve_and_commit(mem.DEFAULT_PAGE_SIZE) or_else panic("2")

    // prove that sufficient space is mapped
    fmt.println("last a:", a[mem.DEFAULT_PAGE_SIZE-1])
    fmt.println("last b:", b[mem.DEFAULT_PAGE_SIZE-1])
    fmt.println("last b address:", &b[mem.DEFAULT_PAGE_SIZE-1])

    // XXX: all of these crash
    runtime.memory_compare_zero(raw_data(b), len(b))
    runtime.memory_compare(raw_data(a), raw_data(b), len(b))
    fmt.println(mem.compare(a, b))
}

// NOTE: copied directly from core:mem/virtual darwin

MAP_ANONYMOUS :: 0x1000 /* allocated from memory, swap space */
_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
    flags  := posix.Map_Flags{ .PRIVATE } + transmute(posix.Map_Flags)i32(MAP_ANONYMOUS)
    result := posix.mmap(nil, size, {}, flags)
    if result == posix.MAP_FAILED {
        return nil, .Out_Of_Memory
    }

    return ([^]byte)(uintptr(result))[:size], nil
}

_commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
    if posix.mprotect(data, size, { .READ, .WRITE }) != .OK {
        return .Out_Of_Memory
    }

    return nil
}

reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
    data = _reserve(size) or_return
    _commit(raw_data(data), size) or_return
    return
}

Allocator_Error :: runtime.Allocator_Error

import "core:fmt"
import "core:mem"
import "core:sys/posix"
import "base:runtime"
beaumccartney commented 1 month ago

also using memcmp from libc or something doesn't crash

tf2spi commented 1 month ago

In runtime.memory_compare_zero

    fast := n/SU + 1

This way of calculating fast looks to be an off-by-one error when considering this comparison loop.

    curr_block := uintptr(0)
// ...
    for /**/; curr_block < fast; curr_block += 1 {
        va := (^uintptr)(x + curr_block * size_of(uintptr))^
// ...

At the last iteration, curr_block would get 2048 on Darwin ARM64.

I think there are also alignment issues with this as well since x could've been an unaligned pointer.