Skip to content

Commit 0bfb5e5

Browse files
committed
Add fuzz tests
1 parent 7e4e2fa commit 0bfb5e5

File tree

7 files changed

+361
-4
lines changed

7 files changed

+361
-4
lines changed

.github/workflows/nightly.yml

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,49 @@
22
name: Nightly
33

44
# This job is run at 00:00 UTC every day or on demand.
5-
on:
6-
workflow_dispatch:
7-
schedule:
8-
- cron: '0 0 * * *'
5+
on: [workflow_call]
96

107
permissions:
118
contents: read
129

1310
jobs:
11+
fuzz-test:
12+
name: Fuzz test
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
build_type: [Debug, Release]
17+
compiler: [{c: clang, cxx: clang++}]
18+
19+
runs-on: ubuntu-latest
20+
21+
steps:
22+
- name: Checkout repository
23+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
24+
25+
- name: Install apt packages
26+
run: |
27+
sudo apt-get update
28+
sudo apt-get install -y cmake hwloc libhwloc-dev libnuma-dev libtbb-dev
29+
30+
- name: Configure CMake
31+
run: >
32+
cmake
33+
-B ${{github.workspace}}/build
34+
-DCMAKE_BUILD_TYPE=${{matrix.build_type}}
35+
-DCMAKE_C_COMPILER=${{matrix.compiler.c}}
36+
-DCMAKE_CXX_COMPILER=${{matrix.compiler.cxx}}
37+
-DUMF_TESTS_FAIL_ON_SKIP=ON
38+
-DUMF_DEVELOPER_MODE=ON
39+
-DUMF_BUILD_FUZZTESTS=ON
40+
41+
- name: Build
42+
run: cmake --build ${{github.workspace}}/build --config ${{matrix.build_type}} -j$(nproc)
43+
44+
- name: Fuzz long test
45+
working-directory: ${{github.workspace}}/build
46+
run: ctest -C Debug --output-on-failure -L "fuzz-long"
47+
1448
valgrind:
1549
name: Valgrind
1650
strategy:

.github/workflows/pr_push.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,3 +205,5 @@ jobs:
205205
MultiNuma:
206206
needs: [Build]
207207
uses: ./.github/workflows/multi_numa.yml
208+
Nightly:
209+
uses: ./.github/workflows/nightly.yml

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ option(UMF_BUILD_GPU_TESTS "Build UMF GPU tests" OFF)
3636
option(UMF_BUILD_BENCHMARKS "Build UMF benchmarks" OFF)
3737
option(UMF_BUILD_BENCHMARKS_MT "Build UMF multithreaded benchmarks" OFF)
3838
option(UMF_BUILD_EXAMPLES "Build UMF examples" ON)
39+
option(UMF_BUILD_FUZZTESTS "Build UMF fuzz tests" OFF)
3940
option(UMF_BUILD_GPU_EXAMPLES "Build UMF GPU examples" OFF)
4041
option(UMF_DEVELOPER_MODE "Enable developer checks, treats warnings as errors"
4142
OFF)

test/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ if(LINUX) # OS-specific functions are implemented only for Linux now
187187
NAME mempolicy
188188
SRCS memspaces/mempolicy.cpp
189189
LIBS ${LIBNUMA_LIBRARIES})
190+
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND UMF_BUILD_FUZZTESTS)
191+
add_subdirectory(fuzz)
192+
endif()
190193
endif()
191194

192195
if(UMF_BUILD_GPU_TESTS AND UMF_BUILD_LEVEL_ZERO_PROVIDER)

test/fuzz/CMakeLists.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright (C) 2024 Intel Corporation
2+
# Part of the Unified-Runtime Project, under the Apache License v2.0 with LLVM Exceptions.
3+
# See LICENSE.TXT
4+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5+
6+
function(add_fuzz_test name label)
7+
add_test(
8+
NAME ${name}
9+
COMMAND fuzztest ${ARGN} -verbosity=1
10+
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
11+
set_tests_properties(${name} PROPERTIES LABELS ${label})
12+
endfunction()
13+
14+
set(TEST_TARGET_NAME fuzztest)
15+
add_umf_executable(
16+
NAME ${TEST_TARGET_NAME}
17+
SRCS umfFuzz.cpp
18+
LIBS umf)
19+
target_link_libraries(${TEST_TARGET_NAME} PRIVATE umf -fsanitize=fuzzer)
20+
target_compile_options(${TEST_TARGET_NAME} PRIVATE -g -fsanitize=fuzzer)
21+
target_include_directories(${TEST_TARGET_NAME}
22+
PRIVATE ${UMF_CMAKE_SOURCE_DIR}/include)
23+
target_link_directories(${TEST_TARGET_NAME} PRIVATE ${LIBHWLOC_LIBRARY_DIRS})
24+
25+
add_fuzz_test(base_long fuzz-long -max_total_time=600 -seed=1)

test/fuzz/umfFuzz.cpp

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
// Copyright (C) 2024 Intel Corporation
2+
// Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
5+
#include "utils.hpp"
6+
7+
namespace fuzz {
8+
9+
constexpr int MAX_PROVIDER_VECTOR_SIZE = 1024;
10+
constexpr int MAX_POOLS_VECTOR_SIZE = 20;
11+
constexpr int MAX_POOLS_ALLOC_SIZE = 1 * 1024; // 1 kB
12+
constexpr int MAX_PROVIDER_ALLOC_SIZE = 100 * 1024; // 100 kB
13+
14+
int umf_memory_provider_create(TestState &test_state) {
15+
std::cout << "Begin creating a provider" << std::endl;
16+
umf_memory_provider_ops_t *provider_ops = umfOsMemoryProviderOps();
17+
umf_os_memory_provider_params_t params = umfOsMemoryProviderParamsDefault();
18+
umf_result_t res =
19+
umfMemoryProviderCreate(provider_ops, &params, &test_state.provider);
20+
21+
if (res != UMF_RESULT_SUCCESS) {
22+
std::cout << "Failed to create a memory provider: " << res << std::endl;
23+
return -1;
24+
}
25+
std::cout << "OS memory provider created at " << (void *)test_state.provider
26+
<< std::endl;
27+
return 0;
28+
}
29+
30+
int umf_memory_provider_alloc(TestState &test_state) {
31+
std::cout << "Begin memory_provider_alloc" << std::endl;
32+
void *ptr;
33+
size_t alloc_size;
34+
constexpr size_t alignment = 0;
35+
36+
if (test_state.provider_memory_allocations.size() >=
37+
MAX_PROVIDER_VECTOR_SIZE) {
38+
return -1;
39+
}
40+
41+
int ret = test_state.get_next_alloc_size(test_state, alloc_size,
42+
MAX_PROVIDER_ALLOC_SIZE);
43+
if (ret != 0) {
44+
std::cout << "Failed to get alloc size" << std::endl;
45+
return -1;
46+
}
47+
48+
umf_result_t res = umfMemoryProviderAlloc(test_state.provider, alloc_size,
49+
alignment, &ptr);
50+
if (res != UMF_RESULT_SUCCESS) {
51+
std::cout << "Failed to allocate memory from the provider: " << res
52+
<< std::endl;
53+
return -1;
54+
}
55+
test_state.provider_memory_allocations.push_back(
56+
std::make_pair(ptr, alloc_size));
57+
std::cout << "Allocated memory at " << ptr << " with alloc_size "
58+
<< alloc_size << std::endl;
59+
std::cout << "Size of vector with allocated memory from the provider: "
60+
<< test_state.provider_memory_allocations.size() << std::endl;
61+
return 0;
62+
}
63+
64+
int umf_memory_provider_free(TestState &test_state) {
65+
std::cout << "Begin memory_provider_free" << std::endl;
66+
if (test_state.provider_memory_allocations.empty()) {
67+
std::cout << "No memory allocated" << std::endl;
68+
return -1;
69+
}
70+
71+
std::pair<void *, size_t> alloc =
72+
test_state.provider_memory_allocations.back();
73+
umf_result_t res =
74+
umfMemoryProviderFree(test_state.provider, alloc.first, alloc.second);
75+
76+
if (res != UMF_RESULT_SUCCESS) {
77+
std::cout << "Failed to free memory to the provider: " << res
78+
<< std::endl;
79+
;
80+
return -1;
81+
}
82+
83+
std::cout << "Freed memory from the provider at " << alloc.first
84+
<< " with alloc_size " << alloc.second << std::endl;
85+
test_state.provider_memory_allocations.pop_back();
86+
return 0;
87+
}
88+
89+
int umf_pool_create(TestState &test_state) {
90+
if (test_state.pools.size() > MAX_POOLS_VECTOR_SIZE) {
91+
std::cout << "Max pools limit reached" << std::endl;
92+
return -1;
93+
}
94+
95+
umf_memory_pool_ops_t *pool_ops = umfScalablePoolOps();
96+
void *pool_params = NULL;
97+
umf_pool_create_flags_t flags = 0;
98+
umf_memory_pool_handle_t pool;
99+
umf_result_t res =
100+
umfPoolCreate(pool_ops, test_state.provider, pool_params, flags, &pool);
101+
102+
if (res != UMF_RESULT_SUCCESS) {
103+
std::cout << "Failed to create a pool: " << res << std::endl;
104+
return -1;
105+
}
106+
107+
test_state.pools.insert(std::make_pair(pool, std::vector<void *>()));
108+
std::cout << "Scalable memory pool created at " << pool
109+
<< " and pools available: " << test_state.pools.size()
110+
<< std::endl;
111+
return 0;
112+
}
113+
114+
int umf_pool_destroy(TestState &test_state) {
115+
std::cout << "Begin destroy pool" << std::endl;
116+
if (test_state.pools.empty()) {
117+
std::cout << "No pools created" << std::endl;
118+
return -1;
119+
}
120+
auto pool = (*test_state.pools.begin()).first;
121+
umfPoolDestroy(pool);
122+
test_state.pools.erase(pool);
123+
std::cout << "Destroyed pool at " << pool << std::endl;
124+
return 0;
125+
}
126+
127+
int umf_pool_malloc(TestState &test_state) {
128+
std::cout << "Begin pool_malloc" << std::endl;
129+
if (test_state.pools.empty()) {
130+
std::cout << "No pools created" << std::endl;
131+
return -1;
132+
}
133+
size_t alloc_size;
134+
int ret = test_state.get_next_alloc_size(test_state, alloc_size,
135+
MAX_POOLS_ALLOC_SIZE);
136+
if (ret != 0) {
137+
std::cout << "Failed to get next allocation size" << std::endl;
138+
return -1;
139+
}
140+
auto &pool_entry = *test_state.pools.rbegin();
141+
void *ptr = umfPoolMalloc(pool_entry.first, alloc_size);
142+
if (!ptr) {
143+
std::cout
144+
<< "Failed to allocate memory in the pool with handle address: "
145+
<< pool_entry.first << std::endl;
146+
}
147+
148+
pool_entry.second.push_back(ptr);
149+
std::cout << "Allocated memory at " << ptr
150+
<< " with allocation size: " << alloc_size << std::endl;
151+
return 0;
152+
}
153+
154+
int umf_free(TestState &test_state) {
155+
std::cout << "Begin releasing pool memory" << std::endl;
156+
for (auto &pool : test_state.pools) {
157+
if (pool.second.empty()) {
158+
continue;
159+
} else {
160+
umfFree(pool.second.back());
161+
pool.second.pop_back();
162+
std::cout << "Freed memory from the pool at: " << pool.second.back()
163+
<< std::endl;
164+
break;
165+
}
166+
std::cout << "No pool memory to free" << std::endl;
167+
return -1;
168+
}
169+
return 0;
170+
}
171+
172+
void cleanup(TestState &test_state) {
173+
std::cout << "Begin cleanup state" << std::endl;
174+
for (auto &alloc : test_state.provider_memory_allocations) {
175+
umfMemoryProviderFree(test_state.provider, alloc.first, alloc.second);
176+
}
177+
178+
for (auto &pool_entry : test_state.pools) {
179+
for (auto &ptr : pool_entry.second) {
180+
umfFree(ptr);
181+
}
182+
umfPoolDestroy(pool_entry.first);
183+
}
184+
std::cout << "Freed all allocated memory from provider and pools and "
185+
"destroy all pools"
186+
<< std::endl;
187+
umfMemoryProviderDestroy(test_state.provider);
188+
std::cout << "Destroyed the provider" << std::endl;
189+
}
190+
191+
extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) {
192+
int next_api_call;
193+
auto data_provider = std::make_unique<FuzzedDataProvider>(data, size);
194+
TestState test_state(std::move(data_provider));
195+
int ret = -1;
196+
197+
int (*api_wrappers[])(TestState &) = {
198+
umf_memory_provider_alloc, umf_memory_provider_free, umf_pool_create,
199+
umf_pool_destroy, umf_pool_malloc, umf_free,
200+
};
201+
umf_memory_provider_create(test_state);
202+
203+
while ((next_api_call = test_state.get_next_api_call()) != -1) {
204+
ret = api_wrappers[next_api_call](test_state);
205+
if (ret) {
206+
cleanup(test_state);
207+
return -1;
208+
}
209+
}
210+
211+
cleanup(test_state);
212+
return 0;
213+
}
214+
} // namespace fuzz

test/fuzz/utils.hpp

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (C) 2024 Intel Corporation
2+
// Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
5+
#include "umf/pools/pool_scalable.h"
6+
#include "umf/providers/provider_os_memory.h"
7+
#include <fuzzer/FuzzedDataProvider.h>
8+
#include <iostream>
9+
#include <map>
10+
#include <memory>
11+
#include <vector>
12+
13+
namespace fuzz {
14+
15+
enum FuzzerAPICall : uint8_t {
16+
UMF_MEMORY_PROVIDER_ALLOC,
17+
UMF_MEMORY_PROVIDER_FREE,
18+
UMF_POOL_CREATE,
19+
UMF_POOL_DESTROY,
20+
UMF_POOL_MALLOC,
21+
UMF_POOL_FREE,
22+
kMaxValue = UMF_POOL_FREE,
23+
};
24+
25+
struct TestState {
26+
std::unique_ptr<FuzzedDataProvider> data_provider;
27+
umf_memory_provider_handle_t provider;
28+
std::vector<std::pair<void *, size_t>>
29+
provider_memory_allocations; //provider
30+
std::map<umf_memory_pool_handle_t, std::vector<void *>> pools; //pool
31+
32+
TestState(std::unique_ptr<FuzzedDataProvider> data_provider)
33+
: data_provider(std::move(data_provider)) {}
34+
35+
template <typename IntType> int get_next_input_data(IntType *data) {
36+
if (data_provider->remaining_bytes() < sizeof(IntType)) {
37+
return -1;
38+
}
39+
*data = data_provider->ConsumeIntegral<IntType>();
40+
41+
return 0;
42+
}
43+
44+
template <typename IntType>
45+
int get_next_input_data_in_range(IntType *data, IntType min, IntType max) {
46+
if (data_provider->remaining_bytes() < sizeof(IntType)) {
47+
return -1;
48+
}
49+
*data = data_provider->ConsumeIntegralInRange<IntType>(min, max);
50+
51+
return 0;
52+
}
53+
54+
template <typename EnumType> int get_next_input_data_enum(EnumType *data) {
55+
if (data_provider->remaining_bytes() < sizeof(EnumType)) {
56+
return -1;
57+
}
58+
*data = data_provider->ConsumeEnum<EnumType>();
59+
60+
return 0;
61+
}
62+
63+
int get_next_api_call() {
64+
FuzzerAPICall next_api_call;
65+
return get_next_input_data_enum(&next_api_call) == 0 ? next_api_call
66+
: -1;
67+
}
68+
69+
size_t get_next_alloc_size(TestState &state, size_t &alloc_size,
70+
size_t max_alloc_size) {
71+
if (state.get_next_input_data_in_range<size_t>(&alloc_size, 0,
72+
max_alloc_size) != 0) {
73+
return -1;
74+
}
75+
return 0;
76+
}
77+
};
78+
} // namespace fuzz

0 commit comments

Comments
 (0)