diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 8f32e6058b..de61173c33 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -17,6 +17,7 @@ jobs: build_type: Release compiler: {c: clang, cxx: clang++} libbacktrace: '-DVAL_USE_LIBBACKTRACE_BACKTRACE=OFF' + fuzztest: ON - os: 'ubuntu-22.04' build_type: Release compiler: {c: gcc, cxx: g++} @@ -25,6 +26,7 @@ jobs: build_type: Release compiler: {c: clang, cxx: clang++} libbacktrace: '-DVAL_USE_LIBBACKTRACE_BACKTRACE=ON' + fuzztest: ON - os: 'ubuntu-20.04' build_type: Release compiler: {c: gcc-7, cxx: g++-7} @@ -89,6 +91,11 @@ jobs: working-directory: ${{github.workspace}}/build run: ctest -C ${{matrix.build_type}} --output-on-failure -L "python|umf|loader|validation|tracing|unit|urtrace" + - name: Fuzz test + working-directory: ${{github.workspace}}/build + if: matrix.fuzztest == 'ON' + run: ctest -C ${{matrix.build_type}} --output-on-failure -L "fuzz" + windows-build: name: Build - Windows strategy: diff --git a/source/adapters/null/ur_null.cpp b/source/adapters/null/ur_null.cpp index 18c8d89ef5..5a62761b67 100644 --- a/source/adapters/null/ur_null.cpp +++ b/source/adapters/null/ur_null.cpp @@ -163,5 +163,68 @@ context_t::context_t() { } return UR_RESULT_SUCCESS; }; + + ////////////////////////////////////////////////////////////////////////// + urDdiTable.USM.pfnHostAlloc = + [](ur_context_handle_t hContext, const ur_usm_desc_t *pUSMDesc, + ur_usm_pool_handle_t pool, size_t size, void **ppMem) { + if (size == 0) { + *ppMem = nullptr; + return UR_RESULT_ERROR_UNSUPPORTED_SIZE; + } + *ppMem = malloc(size); + if (ppMem == nullptr) { + return UR_RESULT_ERROR_OUT_OF_HOST_MEMORY; + } + return UR_RESULT_SUCCESS; + }; + + ////////////////////////////////////////////////////////////////////////// + urDdiTable.USM.pfnDeviceAlloc = + [](ur_context_handle_t hContext, ur_device_handle_t hDevice, + const ur_usm_desc_t *pUSMDesc, ur_usm_pool_handle_t pool, + size_t size, void **ppMem) { + if (size == 0) { + *ppMem = nullptr; + return UR_RESULT_ERROR_UNSUPPORTED_SIZE; + } + *ppMem = malloc(size); + if (ppMem == nullptr) { + return UR_RESULT_ERROR_OUT_OF_HOST_MEMORY; + } + return UR_RESULT_SUCCESS; + }; + + ////////////////////////////////////////////////////////////////////////// + urDdiTable.USM.pfnFree = [](ur_context_handle_t hContext, void *pMem) { + free(pMem); + return UR_RESULT_SUCCESS; + }; + + ////////////////////////////////////////////////////////////////////////// + urDdiTable.USM.pfnGetMemAllocInfo = + [](ur_context_handle_t hContext, const void *pMem, + ur_usm_alloc_info_t propName, size_t propSize, void *pPropValue, + size_t *pPropSizeRet) { + switch (propName) { + case UR_USM_ALLOC_INFO_TYPE: + *reinterpret_cast(pPropValue) = + pMem ? UR_USM_TYPE_DEVICE : UR_USM_TYPE_UNKNOWN; + if (pPropSizeRet != nullptr) { + *pPropSizeRet = sizeof(ur_usm_type_t); + } + break; + case UR_USM_ALLOC_INFO_SIZE: + *reinterpret_cast(pPropValue) = pMem ? SIZE_MAX : 0; + if (pPropSizeRet != nullptr) { + *pPropSizeRet = sizeof(size_t); + } + break; + default: + pPropValue = nullptr; + break; + } + return UR_RESULT_SUCCESS; + }; } } // namespace driver diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 79ca48236c..72564667ed 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -24,3 +24,6 @@ add_subdirectory(unit) if(UR_BUILD_TOOLS) add_subdirectory(tools) endif() +if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + add_subdirectory(fuzz) +endif() diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt new file mode 100644 index 0000000000..236d4e3e5e --- /dev/null +++ b/test/fuzz/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (C) 2023 Intel Corporation +# Part of the Unified-Runtime Project, under the Apache License v2.0 with LLVM Exceptions. +# See LICENSE.TXT +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +function(add_fuzz_test name) + set(TEST_TARGET_NAME fuzztest-${name}) + add_executable(${TEST_TARGET_NAME} + ${ARGN}) + target_link_libraries(${TEST_TARGET_NAME} + PRIVATE + ${PROJECT_NAME}::loader + ${PROJECT_NAME}::headers + ${PROJECT_NAME}::common + -fsanitize=fuzzer) + add_test(NAME ${TEST_TARGET_NAME} + COMMAND ${TEST_TARGET_NAME} -max_total_time=60 -seed=1 -shrink=1 + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + set_tests_properties(${TEST_TARGET_NAME} PROPERTIES LABELS "fuzz") + target_compile_options(${TEST_TARGET_NAME} PRIVATE -g -fsanitize=fuzzer) +endfunction() + +add_fuzz_test(base + urFuzz.cpp) diff --git a/test/fuzz/urFuzz.cpp b/test/fuzz/urFuzz.cpp new file mode 100644 index 0000000000..252aad2f82 --- /dev/null +++ b/test/fuzz/urFuzz.cpp @@ -0,0 +1,251 @@ +// Copyright (C) 2023 Intel Corporation +// Part of the Unified-Runtime Project, under the Apache License v2.0 with LLVM Exceptions. +// See LICENSE.TXT +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +#include +#include + +#include "ur_api.h" + +enum FuzzerData { + UR_PLATFORM_GET, + UR_DEVICE_GET, + UR_CONTEXT_CREATE, + UR_USM_POOL_CREATE, + UR_USM_POOL_RELEASE, + UR_USM_HOST_ALLOC, + UR_USM_DEVICE_ALLOC, + UR_USM_FREE, + UR_MAX_FUZZER_DATA, +}; + +typedef struct TestState { + static constexpr ur_device_type_t device_type = UR_DEVICE_TYPE_ALL; + static constexpr uint32_t num_entries = 1; + + std::vector adapters; + std::vector platforms; + std::vector devices; + std::vector contexts; + std::map> pool_host_allocs; + std::vector no_pool_host_allocs; + ur_platform_handle_t platform = nullptr; + + uint32_t num_adapters; + uint32_t num_platforms; + uint32_t num_devices; + uint8_t platform_num; + uint8_t device_num; + uint8_t context_num; +} TestState; + +bool check_context_exists(TestState &state); + +int ur_platform_get(TestState &state) { + ur_result_t res = urPlatformGet( + state.adapters.data(), state.adapters.size(), state.num_entries, + state.platforms.data(), &state.num_platforms); + if (res != UR_RESULT_SUCCESS) { + return -1; + } + state.platforms.resize(state.num_platforms); + + return 0; +} + +int ur_device_get(TestState &state) { + if (!state.platform) { + if (state.platform_num >= state.platforms.size()) { + return -1; + } + state.platform = state.platforms[state.platform_num]; + } + ur_result_t res = + urDeviceGet(state.platform, state.device_type, state.num_entries, + state.devices.data(), &state.num_devices); + if (res != UR_RESULT_SUCCESS) { + return -1; + } + state.devices.resize(state.num_devices); + + return 0; +} + +int ur_context_create(TestState &state) { + if (!state.devices.empty()) { + ur_context_handle_t context; + if (state.device_num >= state.devices.size()) { + return -1; + } + urContextCreate(state.device_num, state.devices.data(), nullptr, + &context); + state.contexts.push_back(context); + } + + return 0; +} + +int ur_usm_pool_create(TestState &state) { + if (!check_context_exists(state)) { + return -1; + } + + ur_usm_pool_handle_t pool; + urUSMPoolCreate(state.contexts[state.context_num], nullptr, &pool); + state.pool_host_allocs[pool] = {}; + + return 0; +} + +int ur_usm_pool_release(TestState &state) { + if (state.pool_host_allocs.empty() || !check_context_exists(state)) { + return -1; + } + + auto &[pool, allocs] = *state.pool_host_allocs.begin(); + if (!allocs.empty()) { + for (auto &ptr : allocs) { + urUSMFree(state.contexts[state.context_num], ptr); + } + } + urUSMPoolRelease(pool); + state.pool_host_allocs.erase(pool); + + return 0; +} + +int ur_usm_host_alloc(TestState &state) { + if (!check_context_exists(state)) { + return -1; + } + + void *ptr; + if (!state.pool_host_allocs.empty()) { + auto &[pool, allocs] = *state.pool_host_allocs.end(); + urUSMHostAlloc(state.contexts[state.context_num], nullptr, pool, 100, + &ptr); + allocs.push_back(ptr); + } else { + urUSMHostAlloc(state.contexts[state.context_num], nullptr, nullptr, 100, + &ptr); + state.no_pool_host_allocs.push_back(ptr); + } + + return 0; +} + +int ur_usm_device_alloc(TestState &state) { return 0; } + +int ur_usm_free(TestState &state) { + if (!state.no_pool_host_allocs.empty()) { + urUSMFree(state.contexts[state.context_num], + state.no_pool_host_allocs.back()); + state.no_pool_host_allocs.pop_back(); + } else if (!state.pool_host_allocs.empty()) { + for (auto pool_it = state.pool_host_allocs.rbegin(); + pool_it != state.pool_host_allocs.rend(); ++pool_it) { + auto &[pool, allocs] = *pool_it; + if (!allocs.empty()) { + urUSMFree(state.contexts[state.context_num], allocs.back()); + allocs.pop_back(); + break; + } + } + } + + return 0; +} + +int init_random_data(uint8_t **data, size_t size, TestState &state) { + if (size < 4) { + return -1; + } + + state.platform_num = **data; + (*data)++; + state.device_num = **data; + (*data)++; + state.context_num = **data; + (*data)++; + + return 0; +} + +int get_next_api_call(uint8_t *data) { + if (*data >= UR_MAX_FUZZER_DATA || *data < 0) { + return -1; + } else { + return *data; + } +} + +bool check_context_exists(TestState &state) { + if (state.contexts.empty() || state.context_num >= state.contexts.size()) { + return false; + } + + return true; +} + +void cleanup(TestState &state) { + for (const auto &[pool, allocs] : state.pool_host_allocs) { + for (auto &alloc : allocs) { + urUSMFree(state.contexts[state.context_num], alloc); + } + urUSMPoolRelease(pool); + } + for (auto &alloc : state.no_pool_host_allocs) { + urUSMFree(state.contexts[state.context_num], alloc); + } + for (auto &context : state.contexts) { + urContextRelease(context); + } + for (auto &device : state.devices) { + urDeviceRelease(device); + } +} + +extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { + uint8_t *data_ptr = data; + TestState test_state; + int next_api_call = -1; + int ret = -1; + + int (*api_wrappers[])(TestState &) = { + ur_platform_get, ur_device_get, ur_context_create, + ur_usm_pool_create, ur_usm_pool_release, ur_usm_host_alloc, + ur_usm_device_alloc, ur_usm_free, + }; + + ret = init_random_data(&data_ptr, size, test_state); + if (ret != 0) { + return -1; + } + + ur_result_t res = urInit(0, nullptr); + if (res != UR_RESULT_SUCCESS) { + return -1; + } + test_state.adapters.resize(test_state.num_entries); + res = urAdapterGet(test_state.num_entries, test_state.adapters.data(), + &test_state.num_adapters); + if (res != UR_RESULT_SUCCESS || test_state.num_adapters == 0) { + return -1; + } + + while (data_ptr != data + size) { + next_api_call = get_next_api_call(data_ptr++); + if (next_api_call == -1) { + cleanup(test_state); + return -1; + } + ret = api_wrappers[next_api_call](test_state); + if (ret) { + cleanup(test_state); + return -1; + } + } + + cleanup(test_state); + return 0; +}