compiler: Reject overflowing arena allocations

c9134fc12dbe35034126c557059b52f745dba755d44ef20cd20b2eb99a3c6338
Alexis Sellier committed ago 1 parent caa7b66e
lib/std/lang/alloc.rad +8 -6
4 4
//! byte buffer. Memory is never freed individually - the entire arena is
5 5
//! reset at once. This is ideal for compiler passes where all allocations
6 6
//! have the same lifetime.
7 7
@test mod tests;
8 8
9 -
use std::mem;
10 -
11 9
/// Error thrown by allocator.
12 10
export union AllocError: Copy {
13 11
    /// Allocator is out of memory.
14 12
    OutOfMemory,
15 13
}
37 35
/// appropriate type and initializing the memory.
38 36
export fn alloc(arena: &mut Arena, size: u32, alignment: u32) -> *mut opaque throws (AllocError) {
39 37
    assert alignment > 0;
40 38
    assert size > 0;
41 39
42 -
    let aligned = mem::alignUp(arena.offset, alignment);
43 -
    let newOffset = aligned + size;
44 -
45 -
    if newOffset > arena.data.len as u32 {
40 +
    let aligned64 = (arena.offset as u64 + alignment as u64 - 1) & ~(alignment as u64 - 1);
41 +
    if aligned64 > arena.data.len as u64 or size as u64 > arena.data.len as u64 - aligned64 {
46 42
        throw AllocError::OutOfMemory;
47 43
    }
44 +
    let aligned = aligned64 as u32;
45 +
    let newOffset = aligned + size;
46 +
48 47
    let base: *mut u8 = &mut arena.data[aligned];
49 48
    set arena.offset = newOffset;
50 49
51 50
    return base as *mut opaque;
52 51
}
97 96
/// Throws `AllocError` if the arena is exhausted.
98 97
export unsafe fn allocSlice(arena: &mut Arena, size: u32, alignment: u32, count: u32) -> *mut [opaque] throws (AllocError) {
99 98
    if count == 0 {
100 99
        return &mut [];
101 100
    }
101 +
    if size > 0xffffffff / count {
102 +
        throw AllocError::OutOfMemory;
103 +
    }
102 104
    let ptr = try alloc(arena, size * count, alignment);
103 105
104 106
    return @sliceOf(ptr, count);
105 107
}
106 108
lib/std/lang/alloc/tests.rad +25 -0
118 118
    try testing::expect(super::used(&arena) == 24);
119 119
120 120
    // Verify the pointers are distinct.
121 121
    try testing::expect(p1 as u64 <> p2 as u64);
122 122
}
123 +
124 +
/// Large counts and offsets must fail before arithmetic wraps or storage changes.
125 +
@test unsafe fn testAllocOverflow() throws (testing::TestError) {
126 +
    static bytes: [u8; 64] = [0; 64];
127 +
    let mut arena = super::new(&mut bytes[..]);
128 +
    set arena.offset = 8;
129 +
    let mut failed: u32 = 0;
130 +
    try super::allocSlice(&mut arena, 8, 8, 0x20000000) catch {
131 +
        set failed += 1;
132 +
    };
133 +
    try super::alloc(&mut arena, 0xffffffff, 8) catch {
134 +
        set failed += 1;
135 +
    };
136 +
    assert failed == 2 and arena.offset == 8;
137 +
    set arena.offset = 0xfffffff8;
138 +
    try super::alloc(&mut arena, 16, 16) catch {
139 +
        set failed += 1;
140 +
    };
141 +
    assert failed == 3 and arena.offset == 0xfffffff8;
142 +
    set arena.offset = 8;
143 +
    let storage = try super::alloc(&mut arena, 8, 8) catch {
144 +
        throw testing::TestError::Failed;
145 +
    };
146 +
    assert arena.offset == 16;
147 +
}