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(check-libc ${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,10 @@ +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,3 @@ +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=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 @@ +#include "src/string/strcpy.h" +#include +#include +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { + if (!size) return 0; + + char *src = (char *)malloc(size + 1); + // Prevent failure from high memory pressure. + if (!src) return 0; + + char *dest = (char *)malloc(size + 1); + if (!dest) { + free(src); + return 0; + } + + memcpy(src, data, size); + for (size_t i = 0; i < size; i++) { + // Replace early null-termination with valid character. + if (src[i] == '\0') { + src[i] = 'a'; + } + } + // strcpy can only accept null-terminated strings. + src[size] = '\0'; + + __llvm_libc::strcpy(dest, src); + + if (strcmp(dest, src) != 0) __builtin_trap(); + + free(src); + free(dest); + + return 0; +}