diff --git a/libc/CMakeLists.txt b/libc/CMakeLists.txt --- a/libc/CMakeLists.txt +++ b/libc/CMakeLists.txt @@ -32,3 +32,4 @@ # of the other directories. add_subdirectory(lib) add_subdirectory(test) +add_subdirectory(fuzzing) diff --git a/libc/cmake/modules/LLVMLibCRules.cmake b/libc/cmake/modules/LLVMLibCRules.cmake --- a/libc/cmake/modules/LLVMLibCRules.cmake +++ b/libc/cmake/modules/LLVMLibCRules.cmake @@ -300,7 +300,7 @@ if(NOT LLVM_INCLUDE_TESTS) return() endif() - + cmake_parse_arguments( "LIBC_UNITTEST" "" # No optional arguments @@ -375,6 +375,71 @@ add_dependencies(check-libc ${suite_name}) endfunction(add_libc_testsuite) +# Rule to add a fuzzer test. +# Usage +# add_libc_fuzzer( +# +# SRCS +# HDRS +# DEPENDS +# ) +function(add_libc_fuzzer target_name) + cmake_parse_arguments( + "LIBC_FUZZER" + "" # No optional arguments + "" # Single value arguments + "SRCS;HDRS;DEPENDS" # Multi-value arguments + ${ARGN} + ) + if(NOT LIBC_FUZZER_SRCS) + message(FATAL_ERROR "'add_libc_fuzzer' target requires a SRCS list of .cpp files.") + endif() + if(NOT LIBC_FUZZER_DEPENDS) + message(FATAL_ERROR "'add_libc_fuzzer' target requires a DEPENDS list of 'add_entrypoint_object' targets.") + endif() + + set(library_deps "") + foreach(dep IN LISTS LIBC_FUZZER_DEPENDS) + get_target_property(dep_type ${dep} "TARGET_TYPE") + if (dep_type) + string(COMPARE EQUAL ${dep_type} ${ENTRYPOINT_OBJ_TARGET_TYPE} dep_is_entrypoint) + if(dep_is_entrypoint) + get_target_property(obj_file ${dep} "OBJECT_FILE_RAW") + list(APPEND library_deps ${obj_file}) + continue() + endif() + endif() + # TODO: Check if the dep is a normal CMake library target. If yes, then add it + # to the list of library_deps. + endforeach(dep) + + add_executable( + ${target_name} + EXCLUDE_FROM_ALL + ${LIBC_FUZZER_SRCS} + ${LIBC_FUZZER_HDRS} + ) + target_include_directories( + ${target_name} + PRIVATE + ${LIBC_SOURCE_DIR} + ${LIBC_BUILD_DIR} + ${LIBC_BUILD_DIR}/include + ) + + if(library_deps) + target_link_libraries(${target_name} PRIVATE ${library_deps}) + endif() + + set_target_properties(${target_name} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + + add_dependencies( + ${target_name} + ${LIBC_FUZZER_DEPENDS} + ) + add_dependencies(libc-fuzzer ${target_name}) +endfunction(add_libc_fuzzer) + # Rule to add header only libraries. # Usage # add_header_library( diff --git a/libc/docs/fuzzing.rst b/libc/docs/fuzzing.rst new file mode 100644 --- /dev/null +++ b/libc/docs/fuzzing.rst @@ -0,0 +1,15 @@ +Fuzzing for LLVM-libc +--------------------- + +Fuzzing tests are used to ensure quality and security of LLVM-libc +implementations. + +Each fuzzing test lives under the fuzzing directory in a subdirectory +corresponding with the src layout. + +Currently we use system libc for functions that have yet to be implemented, +however as they are implemented the fuzzers will be changed to use our +implementation to increase coverage for testing. + +Fuzzers will be run on `oss-fuzz `_ and the +check-libc target will ensure that they build correctly. diff --git a/libc/docs/source_layout.rst b/libc/docs/source_layout.rst --- a/libc/docs/source_layout.rst +++ b/libc/docs/source_layout.rst @@ -7,6 +7,7 @@ + libc - cmake - docs + - fuzzing - include - lib - loader @@ -31,6 +32,13 @@ The ``docs`` directory contains design docs and also informative documents like this document on source layout. +The ``fuzzing`` directory +---------------------- + +This directory contains fuzzing tests for the various components of llvm-libc. The +directory structure within this directory mirrors the directory structure of the +top-level ``libc`` directory itself. For more details, see :doc:`fuzzing`. + The ``include`` directory ------------------------- @@ -62,7 +70,7 @@ This directory contains the implementations of the llvm-libc entrypoints. It is further organized as follows: -1. There is a toplevel CMakeLists.txt file. +1. There is a top-level CMakeLists.txt file. 2. For every public header file provided by llvm-libc, there exists a corresponding directory in the ``src`` directory. The name of the directory is same as the base name of the header file. For example, the directory diff --git a/libc/fuzzing/CMakeLists.txt b/libc/fuzzing/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/fuzzing/CMakeLists.txt @@ -0,0 +1,5 @@ +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=fuzzer") +add_custom_target(libc-fuzzer) +add_dependencies(check-libc libc-fuzzer) + +add_subdirectory(string) diff --git a/libc/fuzzing/string/CMakeLists.txt b/libc/fuzzing/string/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/fuzzing/string/CMakeLists.txt @@ -0,0 +1,7 @@ +add_libc_fuzzer( + strcpy_fuzz + SRCS + strcpy_fuzz.cpp + DEPENDS + strcpy +) diff --git a/libc/fuzzing/string/strcpy_fuzz.cpp b/libc/fuzzing/string/strcpy_fuzz.cpp new file mode 100644 --- /dev/null +++ b/libc/fuzzing/string/strcpy_fuzz.cpp @@ -0,0 +1,38 @@ +//===--------------------- strcpy_fuzz.cpp --------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// Fuzzing test for llvm-libc strcpy implementation. +/// +//===----------------------------------------------------------------------===// +#include "src/string/strcpy.h" +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + // Validate input + if (!size) return 0; + if (data[size - 1] != '\0') return 0; + const char *src = (const char *)data; + + char *dest = new char[size]; + if (!dest) __builtin_trap(); + + __llvm_libc::strcpy(dest, src); + + size_t i; + for (i = 0; src[i] != '\0'; i++) { + // Ensure correctness of strcpy + if (dest[i] != src[i]) __builtin_trap(); + } + // Ensure strcpy null terminates dest + if (dest[i] != src[i]) __builtin_trap(); + + delete[] dest; + + return 0; +} +