Skip to content

Add fuzz tests #572

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,43 @@ permissions:
contents: read

jobs:
fuzz-test:
name: Fuzz test
strategy:
fail-fast: false
matrix:
build_type: [Debug, Release]
compiler: [{c: clang, cxx: clang++}]

runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

- name: Install apt packages
run: |
sudo apt-get update
sudo apt-get install -y cmake hwloc libhwloc-dev libnuma-dev libtbb-dev
- name: Configure CMake
run: >
cmake
-B ${{github.workspace}}/build
-DCMAKE_BUILD_TYPE=${{matrix.build_type}}
-DCMAKE_C_COMPILER=${{matrix.compiler.c}}
-DCMAKE_CXX_COMPILER=${{matrix.compiler.cxx}}
-DUMF_TESTS_FAIL_ON_SKIP=ON
-DUMF_DEVELOPER_MODE=ON
-DUMF_BUILD_FUZZTESTS=ON
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{matrix.build_type}} --verbose -j$(nproc)

- name: Fuzz long test
working-directory: ${{github.workspace}}/build
run: ctest -C ${{matrix.build_type}} --output-on-failure --verbose -L "fuzz-long"

valgrind:
name: Valgrind
strategy:
Expand Down
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ option(UMF_BUILD_GPU_TESTS "Build UMF GPU tests" OFF)
option(UMF_BUILD_BENCHMARKS "Build UMF benchmarks" OFF)
option(UMF_BUILD_BENCHMARKS_MT "Build UMF multithreaded benchmarks" OFF)
option(UMF_BUILD_EXAMPLES "Build UMF examples" ON)
option(UMF_BUILD_FUZZTESTS "Build UMF fuzz tests" OFF)
option(UMF_BUILD_GPU_EXAMPLES "Build UMF GPU examples" OFF)
option(UMF_DEVELOPER_MODE "Enable developer checks, treats warnings as errors"
OFF)
Expand Down Expand Up @@ -164,6 +165,13 @@ if(USE_MSAN)
"prevent reporting false-positives")
add_sanitizer_flag(memory)
endif()
# Fuzzer instrumentation for the whole library
if(UMF_BUILD_FUZZTESTS
AND CMAKE_CXX_COMPILER_ID MATCHES "Clang"
AND LINUX)
add_compile_options("-fsanitize=fuzzer-no-link")
add_link_options("-fsanitize=fuzzer-no-link")
endif()

# A header only library to specify include directories in transitive
# dependencies.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ List of options provided by CMake:
| UMF_BUILD_GPU_TESTS | Build UMF GPU tests | ON/OFF | OFF |
| UMF_BUILD_BENCHMARKS | Build UMF benchmarks | ON/OFF | OFF |
| UMF_BUILD_EXAMPLES | Build UMF examples | ON/OFF | ON |
| UMF_BUILD_FUZZTESTS | Build UMF fuzz tests | ON/OFF | OFF |
| UMF_BUILD_GPU_EXAMPLES | Build UMF GPU examples | ON/OFF | OFF |
| UMF_DEVELOPER_MODE | Treat warnings as errors and enables additional checks | ON/OFF | OFF |
| UMF_FORMAT_CODE_STYLE | Add clang, cmake, and black -format-check and -format-apply targets to make | ON/OFF | OFF |
Expand Down
3 changes: 3 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ if(LINUX) # OS-specific functions are implemented only for Linux now
NAME mempolicy
SRCS memspaces/mempolicy.cpp
LIBS ${LIBNUMA_LIBRARIES})
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND UMF_BUILD_FUZZTESTS)
add_subdirectory(fuzz)
endif()
endif()

if(UMF_BUILD_GPU_TESTS AND UMF_BUILD_LEVEL_ZERO_PROVIDER)
Expand Down
26 changes: 26 additions & 0 deletions test/fuzz/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright (C) 2024 Intel Corporation
# Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

message(STATUS "Fuzzing tests enabled.")

function(add_fuzz_test name label)
add_test(
NAME "fuzz-${name}"
COMMAND fuzztest ${ARGN} -verbosity=1
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
set_tests_properties("fuzz-${name}" PROPERTIES LABELS ${label})
endfunction()

set(TEST_TARGET_NAME fuzztest)
add_umf_executable(
NAME ${TEST_TARGET_NAME}
SRCS umfFuzz.cpp
LIBS umf)
target_link_libraries(${TEST_TARGET_NAME} PRIVATE umf -fsanitize=fuzzer)
target_compile_options(${TEST_TARGET_NAME} PRIVATE -g -fsanitize=fuzzer)
target_include_directories(${TEST_TARGET_NAME}
PRIVATE ${UMF_CMAKE_SOURCE_DIR}/include)
target_link_directories(${TEST_TARGET_NAME} PRIVATE ${LIBHWLOC_LIBRARY_DIRS})

add_fuzz_test(basic_long fuzz-long -max_total_time=600 -seed=1)
220 changes: 220 additions & 0 deletions test/fuzz/umfFuzz.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
// Copyright (C) 2024 Intel Corporation
// Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include "utils.hpp"

namespace fuzz {

constexpr int MAX_PROVIDER_VECTOR_SIZE = 1024;
constexpr int MAX_POOLS_VECTOR_SIZE = 20;
constexpr int MAX_POOLS_ALLOC_SIZE = 1 * 1024; // 1 kB
constexpr int MAX_PROVIDER_ALLOC_SIZE = 100 * 1024; // 100 kB

int umf_memory_provider_create(TestState &test_state) {
std::cout << "Begin creating a provider" << std::endl;
umf_memory_provider_ops_t *provider_ops = umfOsMemoryProviderOps();
umf_os_memory_provider_params_t params = umfOsMemoryProviderParamsDefault();
umf_result_t res =
umfMemoryProviderCreate(provider_ops, &params, &test_state.provider);

if (res != UMF_RESULT_SUCCESS) {
std::cout << "Failed to create a memory provider: " << res << std::endl;
return -1;
}
std::cout << "OS memory provider created at " << (void *)test_state.provider
<< std::endl;
return 0;
}

int umf_memory_provider_alloc(TestState &test_state) {
std::cout << "Begin memory_provider_alloc" << std::endl;
void *ptr;
size_t alloc_size;
constexpr size_t alignment = 0;

if (test_state.provider_memory_allocations.size() >=
MAX_PROVIDER_VECTOR_SIZE) {
return -1;
}

int ret = test_state.get_next_alloc_size(test_state, alloc_size,
MAX_PROVIDER_ALLOC_SIZE);
if (ret != 0) {
std::cout << "Failed to get alloc size" << std::endl;
return -1;
}

umf_result_t res = umfMemoryProviderAlloc(test_state.provider, alloc_size,
alignment, &ptr);
if (res != UMF_RESULT_SUCCESS) {
std::cout << "Failed to allocate memory from the provider: " << res
<< std::endl;
return -1;
}
test_state.provider_memory_allocations.push_back(
std::make_pair(ptr, alloc_size));
std::cout << "Allocated memory at " << ptr << " with alloc_size "
<< alloc_size << std::endl;
std::cout << "Size of vector with allocated memory from the provider: "
<< test_state.provider_memory_allocations.size() << std::endl;
return 0;
}

int umf_memory_provider_free(TestState &test_state) {
std::cout << "Begin memory_provider_free" << std::endl;
if (test_state.provider_memory_allocations.empty()) {
std::cout << "No memory allocated" << std::endl;
return -1;
}

std::pair<void *, size_t> alloc =
test_state.provider_memory_allocations.back();
umf_result_t res =
umfMemoryProviderFree(test_state.provider, alloc.first, alloc.second);

if (res != UMF_RESULT_SUCCESS) {
std::cout << "Failed to free memory to the provider: " << res
<< std::endl;
;
return -1;
}

std::cout << "Freed memory from the provider at " << alloc.first
<< " with alloc_size " << alloc.second << std::endl;
test_state.provider_memory_allocations.pop_back();
return 0;
}

int umf_pool_create(TestState &test_state) {
if (test_state.pools.size() > MAX_POOLS_VECTOR_SIZE) {
std::cout << "Max pools limit reached" << std::endl;
return -1;
}

umf_memory_pool_ops_t *pool_ops = umfScalablePoolOps();
void *pool_params = NULL;
umf_pool_create_flags_t flags = 0;
umf_memory_pool_handle_t pool;
umf_result_t res =
umfPoolCreate(pool_ops, test_state.provider, pool_params, flags, &pool);

if (res != UMF_RESULT_SUCCESS) {
std::cout << "Failed to create a pool: " << res << std::endl;
return -1;
}

test_state.pools.insert(std::make_pair(pool, std::vector<void *>()));
std::cout << "Scalable memory pool created at " << pool
<< " and pools available: " << test_state.pools.size()
<< std::endl;
return 0;
}

int umf_pool_destroy(TestState &test_state) {
std::cout << "Begin destroy pool" << std::endl;
if (test_state.pools.empty()) {
std::cout << "No pools created" << std::endl;
return -1;
}
auto pool = (*test_state.pools.begin()).first;
umfPoolDestroy(pool);
test_state.pools.erase(pool);
std::cout << "Destroyed pool at " << pool << std::endl;
return 0;
}

int umf_pool_malloc(TestState &test_state) {
std::cout << "Begin pool_malloc" << std::endl;
if (test_state.pools.empty()) {
std::cout << "No pools created" << std::endl;
return -1;
}
size_t alloc_size;
int ret = test_state.get_next_alloc_size(test_state, alloc_size,
MAX_POOLS_ALLOC_SIZE);
if (ret != 0) {
std::cout << "Failed to get next allocation size" << std::endl;
return -1;
}
auto &pool_entry = *test_state.pools.rbegin();
void *ptr = umfPoolMalloc(pool_entry.first, alloc_size);
if (!ptr) {
std::cout
<< "Failed to allocate memory in the pool with handle address: "
<< pool_entry.first << std::endl;
}

pool_entry.second.push_back(ptr);
std::cout << "Allocated memory at " << ptr
<< " with allocation size: " << alloc_size << std::endl;
return 0;
}

int umf_free(TestState &test_state) {
std::cout << "Begin releasing pool memory" << std::endl;
for (auto &pool : test_state.pools) {
if (pool.second.empty()) {
continue;
} else {
umfFree(pool.second.back());
pool.second.pop_back();
std::cout << "Freed memory from the pool at: " << pool.second.back()
<< std::endl;
break;
}
std::cout << "No pool memory to free" << std::endl;
return -1;
}
return 0;
}

void cleanup(TestState &test_state) {
std::cout << "Begin cleanup state" << std::endl;
for (auto &alloc : test_state.provider_memory_allocations) {
umfMemoryProviderFree(test_state.provider, alloc.first, alloc.second);
}

for (auto &pool_entry : test_state.pools) {
for (auto &ptr : pool_entry.second) {
umfFree(ptr);
}
umfPoolDestroy(pool_entry.first);
}
std::cout << "Freed all allocated memory from provider and pools and "
"destroyed all pools"
<< std::endl;
umfMemoryProviderDestroy(test_state.provider);
std::cout << "Destroyed the provider" << std::endl;
}

extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) {
int next_api_call;
auto data_provider = std::make_unique<FuzzedDataProvider>(data, size);
TestState test_state(std::move(data_provider));
int ret = -1;

// clang-format off
int (*api_wrappers[])(TestState &) = {
umf_memory_provider_alloc,
umf_memory_provider_free,
umf_pool_create,
umf_pool_destroy,
umf_pool_malloc,
umf_free,
};
// clang-format on
umf_memory_provider_create(test_state);

while ((next_api_call = test_state.get_next_api_call()) != -1) {
ret = api_wrappers[next_api_call](test_state);
if (ret) {
cleanup(test_state);
return -1;
}
}

cleanup(test_state);
return 0;
}
} // namespace fuzz
Loading