diff --git a/libc/fuzzing/CMakeLists.txt b/libc/fuzzing/CMakeLists.txt --- a/libc/fuzzing/CMakeLists.txt +++ b/libc/fuzzing/CMakeLists.txt @@ -3,4 +3,5 @@ add_subdirectory(math) add_subdirectory(stdlib) +add_subdirectory(stdio) add_subdirectory(string) diff --git a/libc/fuzzing/stdio/CMakeLists.txt b/libc/fuzzing/stdio/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/fuzzing/stdio/CMakeLists.txt @@ -0,0 +1,9 @@ +add_libc_fuzzer( + printf_parser_fuzz + SRCS + printf_parser_fuzz.cpp + DEPENDS + libc.src.stdio.printf_core.mock_parser + COMPILE_OPTIONS + -DLIBC_COPT_MOCK_ARG_LIST +) diff --git a/libc/fuzzing/stdio/printf_parser_fuzz.cpp b/libc/fuzzing/stdio/printf_parser_fuzz.cpp new file mode 100644 --- /dev/null +++ b/libc/fuzzing/stdio/printf_parser_fuzz.cpp @@ -0,0 +1,73 @@ +//===-- printf_parser_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 qsort implementation. +/// +//===----------------------------------------------------------------------===// + +#ifndef LIBC_COPT_MOCK_ARG_LIST +#error The printf Parser Fuzzer must be compiled with LIBC_COPT_MOCK_ARG_LIST, and the parser itself must also be compiled with that option when it's linked against the fuzzer. +#endif + +#include "src/__support/arg_list.h" +#include "src/stdio/printf_core/parser.h" + +#include +#include + +using namespace __llvm_libc; + +// The design for the printf parser fuzzer is fairly simple. The parser uses a +// mock arg list that will never fail, and is passed a randomized string. The +// format sections it outputs are checked against a count of the number of '%' +// signs are in the original string. This is a fairly basic test, and the main +// intent is to run this under sanitizers, which will check for buffer overruns. +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + char *in_str = new char[size + 1]; + + for (size_t i = 0; i < size; ++i) + in_str[i] = data[i]; + + in_str[size] = '\0'; + + auto mock_arg_list = internal::MockArgList(); + + auto parser = printf_core::Parser(in_str, mock_arg_list); + + int str_percent_count = 0; + + for (size_t i = 0; i < size && in_str[i] != '\0'; ++i) { + if (in_str[i] == '%') { + ++str_percent_count; + } + } + + int section_percent_count = 0; + + for (printf_core::FormatSection cur_section = parser.get_next_section(); + !cur_section.raw_string.empty(); + cur_section = parser.get_next_section()) { + if (cur_section.has_conv) { + ++section_percent_count; + if (cur_section.conv_name == '%') { + ++section_percent_count; + } + } else if (cur_section.raw_string[0] == '%') { + // If the conversion would be undefined, it's instead raw, but it still + // starts with a %. + ++section_percent_count; + } + } + + if (str_percent_count != section_percent_count) { + __builtin_trap(); + } + + delete[] in_str; + return 0; +} diff --git a/libc/src/__support/arg_list.h b/libc/src/__support/arg_list.h --- a/libc/src/__support/arg_list.h +++ b/libc/src/__support/arg_list.h @@ -16,6 +16,8 @@ namespace __llvm_libc { namespace internal { +#ifndef LIBC_COPT_MOCK_ARG_LIST + class ArgList { va_list vlist; @@ -32,6 +34,47 @@ template LIBC_INLINE T next_var() { return va_arg(vlist, T); } }; +#else // not defined LIBC_COPT_MOCK_ARG_LIST + +// This mock arg list is used for fuzz testing the printf parser. Instead of +// actually reading from va_args it uses a counter and casts its result. This +// makes passing randomized format strings safe since the arg list will never +// run out. +class MockArgList { + int arg_counter = 0; + +public: + LIBC_INLINE MockArgList() = default; + LIBC_INLINE MockArgList(va_list vlist) { + // Copy the vlist to suppress "unused parameter" warnings. + va_list copy_vlist; + va_copy(copy_vlist, vlist); + va_end(copy_vlist); + } + LIBC_INLINE MockArgList(MockArgList &other) { + arg_counter = other.arg_counter; + } + LIBC_INLINE ~MockArgList() = default; + + LIBC_INLINE MockArgList &operator=(MockArgList &rhs) { + arg_counter = rhs.arg_counter; + return *this; + } + + template LIBC_INLINE T next_var() { + ++arg_counter; + return arg_counter; + } +}; + +template <> LIBC_INLINE void *MockArgList::next_var() { + ++arg_counter; + return reinterpret_cast(arg_counter); +} + +using ArgList = MockArgList; +#endif // LIBC_COPT_MOCK_ARG_LIST + } // namespace internal } // namespace __llvm_libc diff --git a/libc/src/stdio/printf_core/CMakeLists.txt b/libc/src/stdio/printf_core/CMakeLists.txt --- a/libc/src/stdio/printf_core/CMakeLists.txt +++ b/libc/src/stdio/printf_core/CMakeLists.txt @@ -25,6 +25,25 @@ libc.src.__support.common ) +add_object_library( + mock_parser + SRCS + parser.cpp + HDRS + parser.h + DEPENDS + .core_structs + libc.src.__support.arg_list + libc.src.__support.ctype_utils + libc.src.__support.str_to_integer + libc.src.__support.CPP.bit + libc.src.__support.CPP.string_view + libc.src.__support.CPP.type_traits + libc.src.__support.common + COMPILE_OPTIONS + -DLIBC_COPT_MOCK_ARG_LIST +) + add_object_library( string_writer SRCS