1 //===-- freelist_heap_fuzz.cpp --------------------------------------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 /// Fuzzing test for llvm-libc freelist-based heap implementation.
11 //===----------------------------------------------------------------------===//
13 #include "src/__support/CPP/bit.h"
14 #include "src/__support/CPP/optional.h"
15 #include "src/__support/freelist_heap.h"
16 #include "src/string/memory_utils/inline_memcpy.h"
17 #include "src/string/memory_utils/inline_memmove.h"
18 #include "src/string/memory_utils/inline_memset.h"
20 using LIBC_NAMESPACE::FreeListHeap
;
21 using LIBC_NAMESPACE::inline_memset
;
22 using LIBC_NAMESPACE::cpp::nullopt
;
23 using LIBC_NAMESPACE::cpp::optional
;
25 // Record of an outstanding allocation.
30 uint8_t canary
; // Byte written to the allocation
33 // A simple vector that tracks allocations using the heap.
36 AllocVec(FreeListHeap
&heap
) : heap(&heap
), size_(0), capacity(0) {
40 bool empty() const { return !size_
; }
42 size_t size() const { return size_
; }
44 bool push_back(Alloc alloc
) {
45 if (size_
== capacity
) {
46 size_t new_cap
= capacity
? capacity
* 2 : 1;
47 Alloc
*new_allocs
= reinterpret_cast<Alloc
*>(
48 heap
->realloc(allocs
, new_cap
* sizeof(Alloc
)));
54 allocs
[size_
++] = alloc
;
58 Alloc
&operator[](size_t idx
) { return allocs
[idx
]; }
60 void erase_idx(size_t idx
) {
61 LIBC_NAMESPACE::inline_memmove(&allocs
[idx
], &allocs
[idx
+ 1],
62 sizeof(Alloc
) * (size_
- idx
- 1));
73 // Choose a T value by casting libfuzzer data or exit.
75 optional
<T
> choose(const uint8_t *&data
, size_t &remainder
) {
76 if (sizeof(T
) > remainder
)
79 LIBC_NAMESPACE::inline_memcpy(&out
, data
, sizeof(T
));
81 remainder
-= sizeof(T
);
85 // The type of allocation to perform
86 enum class AllocType
: uint8_t {
95 optional
<AllocType
> choose
<AllocType
>(const uint8_t *&data
, size_t &remainder
) {
96 auto raw
= choose
<uint8_t>(data
, remainder
);
99 return static_cast<AllocType
>(
100 *raw
% static_cast<uint8_t>(AllocType::NUM_ALLOC_TYPES
));
103 constexpr size_t heap_size
= 64 * 1024;
105 optional
<size_t> choose_size(const uint8_t *&data
, size_t &remainder
) {
106 auto raw
= choose
<size_t>(data
, remainder
);
109 return *raw
% heap_size
;
112 optional
<size_t> choose_alloc_idx(const AllocVec
&allocs
, const uint8_t *&data
,
116 auto raw
= choose
<size_t>(data
, remainder
);
119 return *raw
% allocs
.size();
122 #define ASSIGN_OR_RETURN(TYPE, NAME, EXPR) \
123 auto maybe_##NAME = EXPR; \
126 TYPE NAME = *maybe_##NAME
128 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data
, size_t remainder
) {
129 LIBC_NAMESPACE::FreeListHeapBuffer
<heap_size
> heap
;
130 AllocVec
allocs(heap
);
134 ASSIGN_OR_RETURN(auto, should_alloc
, choose
<bool>(data
, remainder
));
136 ASSIGN_OR_RETURN(auto, alloc_type
, choose
<AllocType
>(data
, remainder
));
137 ASSIGN_OR_RETURN(size_t, alloc_size
, choose_size(data
, remainder
));
139 // Perform allocation.
141 size_t alignment
= alignof(max_align_t
);
142 switch (alloc_type
) {
143 case AllocType::MALLOC
:
144 ptr
= heap
.allocate(alloc_size
);
146 case AllocType::ALIGNED_ALLOC
: {
147 ASSIGN_OR_RETURN(size_t, alignment
, choose_size(data
, remainder
));
148 alignment
= LIBC_NAMESPACE::cpp::bit_ceil(alignment
);
149 ptr
= heap
.aligned_allocate(alignment
, alloc_size
);
152 case AllocType::REALLOC
: {
155 ASSIGN_OR_RETURN(size_t, idx
,
156 choose_alloc_idx(allocs
, data
, remainder
));
157 Alloc
&alloc
= allocs
[idx
];
158 ptr
= heap
.realloc(alloc
.ptr
, alloc_size
);
160 // Extend the canary region if necessary.
161 if (alloc_size
> alloc
.size
)
162 inline_memset(static_cast<char *>(ptr
) + alloc
.size
, alloc
.canary
,
163 alloc_size
- alloc
.size
);
165 alloc
.size
= alloc_size
;
166 alloc
.alignment
= alignof(max_align_t
);
170 case AllocType::CALLOC
: {
171 ASSIGN_OR_RETURN(size_t, count
, choose_size(data
, remainder
));
173 if (__builtin_mul_overflow(count
, alloc_size
, &total
))
175 ptr
= heap
.calloc(count
, alloc_size
);
177 for (size_t i
= 0; i
< total
; ++i
)
178 if (static_cast<char *>(ptr
)[i
] != 0)
182 case AllocType::NUM_ALLOC_TYPES
:
183 __builtin_unreachable();
187 // aligned_allocate should automatically apply a minimum alignment.
188 if (alignment
< alignof(max_align_t
))
189 alignment
= alignof(max_align_t
);
191 if (reinterpret_cast<uintptr_t>(ptr
) % alignment
)
194 // Reallocation is treated specially above, since we would otherwise
195 // lose the original size.
196 if (alloc_type
!= AllocType::REALLOC
) {
197 // Fill the object with a canary byte.
198 inline_memset(ptr
, canary
, alloc_size
);
200 // Track the allocation.
201 if (!allocs
.push_back({ptr
, alloc_size
, alignment
, canary
}))
207 // Select a random allocation.
208 ASSIGN_OR_RETURN(size_t, idx
, choose_alloc_idx(allocs
, data
, remainder
));
209 Alloc
&alloc
= allocs
[idx
];
212 if (reinterpret_cast<uintptr_t>(alloc
.ptr
) % alloc
.alignment
)
216 uint8_t *ptr
= reinterpret_cast<uint8_t *>(alloc
.ptr
);
217 for (size_t i
= 0; i
< alloc
.size
; ++i
)
218 if (ptr
[i
] != alloc
.canary
)
221 // Free the allocation and untrack it.
222 heap
.free(alloc
.ptr
);
223 allocs
.erase_idx(idx
);