diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index f5d01a0b..f68ba0aa 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -38,6 +38,8 @@ option(WITH_UPNP "Include support for UPnP client" OFF) option(WITH_GIT_VERSION "Use git commit info as version" OFF) option(WITH_ADDRSANITIZER "Build with address sanitizer unix only" OFF) option(WITH_THREADSANITIZER "Build with thread sanitizer unix only" OFF) +option(WITH_UNDEFSANITIZER "Build with undefined sanitizer (unix only)" OFF) +option(BUILD_FUZZING "Build fuzzers (Clang only)" OFF) option(BUILD_TESTING "Build tests" OFF) IF(BUILD_TESTING) @@ -208,20 +210,46 @@ if(WITH_AESNI AND (ARCHITECTURE MATCHES "x86_64" OR ARCHITECTURE MATCHES "i386") add_definitions(-D__AES__) endif() + +set(_SANITIZE_FLAGS "") + if(WITH_ADDRSANITIZER) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") + list(APPEND _SANITIZE_FLAGS -fsanitize=address) endif() if(WITH_THREADSANITIZER) if(WITH_ADDRSANITIZER) message(FATAL_ERROR "thread sanitizer option cannot be combined with address sanitizer") else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=thread") + list(APPEND _SANITIZE_FLAGS -fsanitize=thread) + endif() +endif() + +if(WITH_UNDEFSANITIZER) + list(APPEND _SANITIZE_FLAGS -fsanitize=undefined) + list(APPEND _SANITIZE_FLAGS -fno-sanitize=vptr) + list(APPEND _SANITIZE_FLAGS -fno-sanitize=enum) +endif() + +if(BUILD_FUZZING) + if(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") + list(APPEND _SANITIZE_FLAGS -fsanitize=fuzzer-no-link) + else() + message(FATAL_ERROR "Fuzzing not supported by your compiler") endif() endif() +if(NOT "${_SANITIZE_FLAGS}" STREQUAL "") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer") + + list(JOIN _SANITIZE_FLAGS " " _X) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${_X}") + + # Is this really needed? Compiler (and CXX flags) used to link + #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${_X}") +endif() + + # Use std::atomic instead of GCC builtins on macOS PowerPC: # For more information refer to: https://github.com/PurpleI2P/i2pd/issues/1726#issuecomment-1306335111 # This has been fixed in Boost 1.81, nevertheless we retain the setting for the sake of compatibility. @@ -336,6 +364,8 @@ message(STATUS " GIT VERSION : ${WITH_GIT_VERSION}") endif() message(STATUS " ADDRSANITIZER : ${WITH_ADDRSANITIZER}") message(STATUS " THREADSANITIZER : ${WITH_THREADSANITIZER}") +message(STATUS " UNDEFSANITIZER : ${WITH_UNDEFSANITIZER}") +message(STATUS " FUZZING : ${BUILD_FUZZING}") message(STATUS "---------------------------------------") if(WITH_BINARY) @@ -390,3 +420,7 @@ endif() if(BUILD_TESTING) add_subdirectory(${CMAKE_SOURCE_DIR}/tests ${CMAKE_CURRENT_BINARY_DIR}/tests) endif() + +if(BUILD_FUZZING) + add_subdirectory(${CMAKE_SOURCE_DIR}/fuzzing ${CMAKE_CURRENT_BINARY_DIR}/fuzzing) +endif() diff --git a/fuzzing/CMakeLists.txt b/fuzzing/CMakeLists.txt new file mode 100644 index 00000000..b18b4e9a --- /dev/null +++ b/fuzzing/CMakeLists.txt @@ -0,0 +1,54 @@ + +include_directories( + ../libi2pd + ${Boost_INCLUDE_DIRS} + ${OPENSSL_INCLUDE_DIR} +) + + +set(LIBS + libi2pd + ${Boost_LIBRARIES} + OpenSSL::SSL + OpenSSL::Crypto + ZLIB::ZLIB + Threads::Threads + ${CMAKE_REQUIRED_LIBRARIES} +) + +add_library(fuzzing OBJECT + fuzzing_impl.cc + fuzzing_throttle.cc + fuzzing.h) + +link_libraries(fuzzing) + +set(FUZZERS + Base32ToByteStream + Base64ToByteStream + BlindedPublicKey + ByteStreamToBase32 + ByteStreamToBase64 + HandleI2NPMessage + IdentityEx + LeaseSet + LeaseSet2 + NetDb-AddRouterInfo + NetDb-HandleDatabaseSearchReplyMsg + NetDb-HandleDatabaseStoreMsg + NetDb-HandleDatabaseLookupMsg + NetDb-HandleNTCP2RouterInfoMsg + NetDb-PostI2NPMsg + RouterContext-DecryptTunnelBuildRecord + RouterContext-ProcessDeliveryStatusMessage + RouterContext-ProcessGarlicMessage +) + +string(REPLACE "fuzzer-no-link" "fuzzer" _LINK_FLAGS "${_SANITIZE_FLAGS}") + +foreach(F IN LISTS FUZZERS) + add_executable(fuzz-${F} fuzz-${F}.cc) + target_link_libraries(fuzz-${F} ${LIBS}) + target_link_options(fuzz-${F} PRIVATE ${_LINK_FLAGS}) +endforeach() + diff --git a/fuzzing/fuzz-Base32ToByteStream.cc b/fuzzing/fuzz-Base32ToByteStream.cc new file mode 100644 index 00000000..e42741f5 --- /dev/null +++ b/fuzzing/fuzz-Base32ToByteStream.cc @@ -0,0 +1,32 @@ + + +#include +#include + +#include + +#include "fuzzing.h" + + +bool +fuzzing_testinput(const uint8_t * data, size_t size) +{ + size_t outlen; + uint8_t * out; + + + if(size < 2) + return true; + + outlen = (data[0] << 8) | data[1]; + outlen++; + + data += 2; + size -= 2; + + out = new uint8_t[outlen]; + i2p::data::Base32ToByteStream((const char *) data, size, out, outlen); + delete [] out; + + return true; +} diff --git a/fuzzing/fuzz-Base64ToByteStream.cc b/fuzzing/fuzz-Base64ToByteStream.cc new file mode 100644 index 00000000..257bc159 --- /dev/null +++ b/fuzzing/fuzz-Base64ToByteStream.cc @@ -0,0 +1,32 @@ + + +#include +#include + +#include + +#include "fuzzing.h" + + +bool +fuzzing_testinput(const uint8_t * data, size_t size) +{ + size_t outlen; + uint8_t * out; + + + if(size < 2) + return true; + + outlen = (data[0] << 8) | data[1]; + outlen++; + + data += 2; + size -= 2; + + out = new uint8_t[outlen]; + i2p::data::Base64ToByteStream((const char *) data, size, out, outlen); + delete [] out; + + return true; +} diff --git a/fuzzing/fuzz-BlindedPublicKey.cc b/fuzzing/fuzz-BlindedPublicKey.cc new file mode 100644 index 00000000..656b2516 --- /dev/null +++ b/fuzzing/fuzz-BlindedPublicKey.cc @@ -0,0 +1,23 @@ + + +#include +#include +#include + +#include + +#include "fuzzing.h" + + +bool +fuzzing_testinput(const uint8_t * data, size_t size) +{ + std::string str((const char *) data, size); + i2p::data::BlindedPublicKey * bpk; + + + bpk = new i2p::data::BlindedPublicKey(str); + delete bpk; + + return true; +} diff --git a/fuzzing/fuzz-ByteStreamToBase32.cc b/fuzzing/fuzz-ByteStreamToBase32.cc new file mode 100644 index 00000000..876cf141 --- /dev/null +++ b/fuzzing/fuzz-ByteStreamToBase32.cc @@ -0,0 +1,32 @@ + + +#include +#include + +#include + +#include "fuzzing.h" + + +bool +fuzzing_testinput(const uint8_t * data, size_t size) +{ + size_t outlen; + char * out; + + + if(size < (2 + 1)) + return true; + + outlen = (data[0] << 8) | data[1]; + outlen++; + + data += 2; + size -= 2; + + out = new char[outlen]; + i2p::data::ByteStreamToBase32(data, size, out, outlen); + delete [] out; + + return true; +} diff --git a/fuzzing/fuzz-ByteStreamToBase64.cc b/fuzzing/fuzz-ByteStreamToBase64.cc new file mode 100644 index 00000000..749dd35b --- /dev/null +++ b/fuzzing/fuzz-ByteStreamToBase64.cc @@ -0,0 +1,32 @@ + + +#include +#include + +#include + +#include "fuzzing.h" + + +bool +fuzzing_testinput(const uint8_t * data, size_t size) +{ + size_t outlen; + char * out; + + + if(size < (2 + 1)) + return true; + + outlen = (data[0] << 8) | data[1]; + outlen++; + + data += 2; + size -= 2; + + out = new char[outlen]; + i2p::data::ByteStreamToBase64(data, size, out, outlen); + delete [] out; + + return true; +} diff --git a/fuzzing/fuzz-HandleI2NPMessage.cc b/fuzzing/fuzz-HandleI2NPMessage.cc new file mode 100644 index 00000000..fc7410be --- /dev/null +++ b/fuzzing/fuzz-HandleI2NPMessage.cc @@ -0,0 +1,29 @@ + + +#include +#include + +#include + +#include "fuzzing.h" + + +bool +fuzzing_testinput(const uint8_t * data, size_t size) +{ + i2p::I2NPMessageType msgType; + + + if(size < 1) + return true; + + msgType = (i2p::I2NPMessageType) data[0]; + + data++; + size--; + + i2p::HandleI2NPMessage( + i2p::CreateI2NPMessage(msgType, data, size)); + + return true; +} diff --git a/fuzzing/fuzz-IdentityEx.cc b/fuzzing/fuzz-IdentityEx.cc new file mode 100644 index 00000000..a909324c --- /dev/null +++ b/fuzzing/fuzz-IdentityEx.cc @@ -0,0 +1,21 @@ + + +#include +#include + +#include + +#include "fuzzing.h" + + +bool +fuzzing_testinput(const uint8_t * data, size_t size) +{ + i2p::data::IdentityEx * ident; + + + ident = new i2p::data::IdentityEx(data, size); + delete ident; + + return true; +} diff --git a/fuzzing/fuzz-LeaseSet.cc b/fuzzing/fuzz-LeaseSet.cc new file mode 100644 index 00000000..1620716d --- /dev/null +++ b/fuzzing/fuzz-LeaseSet.cc @@ -0,0 +1,21 @@ + + +#include +#include + +#include + +#include "fuzzing.h" + + +bool +fuzzing_testinput(const uint8_t * data, size_t size) +{ + i2p::data::LeaseSet * ls; + + + ls = new i2p::data::LeaseSet(data, size, false); + delete ls; + + return true; +} diff --git a/fuzzing/fuzz-LeaseSet2.cc b/fuzzing/fuzz-LeaseSet2.cc new file mode 100644 index 00000000..5675eb8d --- /dev/null +++ b/fuzzing/fuzz-LeaseSet2.cc @@ -0,0 +1,38 @@ + + +#include +#include + +#include + +#include "fuzzing.h" + + +bool +fuzzing_testinput(const uint8_t * data, size_t size) +{ + uint8_t storeType; + i2p::data::LeaseSet2 * ls; + + + if(size < 1) + return true; + + storeType = data[0]; + + // Same check as in NetDb::HandleDatabaseStoreMsg() + if(storeType == i2p::data::NETDB_STORE_TYPE_LEASESET) + return true; + + data++; + size--; + + // Same check as in NetDb::HandleDatabaseStoreMsg() + if(size > i2p::data::MAX_LS_BUFFER_SIZE) + return true; + + ls = new i2p::data::LeaseSet2(storeType, data, size, false); + delete ls; + + return true; +} diff --git a/fuzzing/fuzz-NetDb-AddRouterInfo.cc b/fuzzing/fuzz-NetDb-AddRouterInfo.cc new file mode 100644 index 00000000..fd698a4c --- /dev/null +++ b/fuzzing/fuzz-NetDb-AddRouterInfo.cc @@ -0,0 +1,17 @@ + + +#include +#include + +#include + +#include "fuzzing.h" + + +bool +fuzzing_testinput(const uint8_t * data, size_t size) +{ + i2p::data::netdb.AddRouterInfo(data, size); + + return true; +} diff --git a/fuzzing/fuzz-NetDb-HandleDatabaseLookupMsg.cc b/fuzzing/fuzz-NetDb-HandleDatabaseLookupMsg.cc new file mode 100644 index 00000000..17360df0 --- /dev/null +++ b/fuzzing/fuzz-NetDb-HandleDatabaseLookupMsg.cc @@ -0,0 +1,30 @@ + + +#include +#include + +#include +#include + +#include "fuzzing.h" + + +bool +fuzzing_testinput(const uint8_t * data, size_t size) +{ + i2p::I2NPMessageType msgType; + + + if(size < 1) + return true; + + msgType = (i2p::I2NPMessageType) data[0]; + + data++; + size--; + + i2p::data::netdb.HandleDatabaseLookupMsg( + i2p::CreateI2NPMessage(msgType, data, size)); + + return true; +} diff --git a/fuzzing/fuzz-NetDb-HandleDatabaseSearchReplyMsg.cc b/fuzzing/fuzz-NetDb-HandleDatabaseSearchReplyMsg.cc new file mode 100644 index 00000000..574b6a87 --- /dev/null +++ b/fuzzing/fuzz-NetDb-HandleDatabaseSearchReplyMsg.cc @@ -0,0 +1,30 @@ + + +#include +#include + +#include +#include + +#include "fuzzing.h" + + +bool +fuzzing_testinput(const uint8_t * data, size_t size) +{ + i2p::I2NPMessageType msgType; + + + if(size < 1) + return true; + + msgType = (i2p::I2NPMessageType) data[0]; + + data++; + size--; + + i2p::data::netdb.HandleDatabaseSearchReplyMsg( + i2p::CreateI2NPMessage(msgType, data, size)); + + return true; +} diff --git a/fuzzing/fuzz-NetDb-HandleDatabaseStoreMsg.cc b/fuzzing/fuzz-NetDb-HandleDatabaseStoreMsg.cc new file mode 100644 index 00000000..3f0a225b --- /dev/null +++ b/fuzzing/fuzz-NetDb-HandleDatabaseStoreMsg.cc @@ -0,0 +1,30 @@ + + +#include +#include + +#include +#include + +#include "fuzzing.h" + + +bool +fuzzing_testinput(const uint8_t * data, size_t size) +{ + i2p::I2NPMessageType msgType; + + + if(size < 1) + return true; + + msgType = (i2p::I2NPMessageType) data[0]; + + data++; + size--; + + i2p::data::netdb.HandleDatabaseStoreMsg( + i2p::CreateI2NPMessage(msgType, data, size)); + + return true; +} diff --git a/fuzzing/fuzz-NetDb-HandleNTCP2RouterInfoMsg.cc b/fuzzing/fuzz-NetDb-HandleNTCP2RouterInfoMsg.cc new file mode 100644 index 00000000..c9327dff --- /dev/null +++ b/fuzzing/fuzz-NetDb-HandleNTCP2RouterInfoMsg.cc @@ -0,0 +1,30 @@ + + +#include +#include + +#include +#include + +#include "fuzzing.h" + + +bool +fuzzing_testinput(const uint8_t * data, size_t size) +{ + i2p::I2NPMessageType msgType; + + + if(size < 1) + return true; + + msgType = (i2p::I2NPMessageType) data[0]; + + data++; + size--; + + i2p::data::netdb.HandleNTCP2RouterInfoMsg( + i2p::CreateI2NPMessage(msgType, data, size)); + + return true; +} diff --git a/fuzzing/fuzz-NetDb-PostI2NPMsg.cc b/fuzzing/fuzz-NetDb-PostI2NPMsg.cc new file mode 100644 index 00000000..adffef40 --- /dev/null +++ b/fuzzing/fuzz-NetDb-PostI2NPMsg.cc @@ -0,0 +1,30 @@ + + +#include +#include + +#include +#include + +#include "fuzzing.h" + + +bool +fuzzing_testinput(const uint8_t * data, size_t size) +{ + i2p::I2NPMessageType msgType; + + + if(size < 1) + return true; + + msgType = (i2p::I2NPMessageType) data[0]; + + data++; + size--; + + i2p::data::netdb.PostI2NPMsg( + i2p::CreateI2NPMessage(msgType, data, size)); + + return true; +} diff --git a/fuzzing/fuzz-RouterContext-DecryptTunnelBuildRecord.cc b/fuzzing/fuzz-RouterContext-DecryptTunnelBuildRecord.cc new file mode 100644 index 00000000..0921ed40 --- /dev/null +++ b/fuzzing/fuzz-RouterContext-DecryptTunnelBuildRecord.cc @@ -0,0 +1,24 @@ + + +#include +#include + +#include +#include + +#include "fuzzing.h" + + +bool +fuzzing_testinput(const uint8_t * data, size_t size) +{ + uint8_t clearText[i2p::ECIES_BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; + + + if(size < i2p::TUNNEL_BUILD_RECORD_SIZE) + return true; + + i2p::context.DecryptTunnelBuildRecord(data, clearText); + + return true; +} diff --git a/fuzzing/fuzz-RouterContext-ProcessDeliveryStatusMessage.cc b/fuzzing/fuzz-RouterContext-ProcessDeliveryStatusMessage.cc new file mode 100644 index 00000000..e85e866d --- /dev/null +++ b/fuzzing/fuzz-RouterContext-ProcessDeliveryStatusMessage.cc @@ -0,0 +1,30 @@ + + +#include +#include + +#include +#include + +#include "fuzzing.h" + + +bool +fuzzing_testinput(const uint8_t * data, size_t size) +{ + i2p::I2NPMessageType msgType; + + + if(size < 1) + return true; + + msgType = (i2p::I2NPMessageType) data[0]; + + data++; + size--; + + i2p::context.ProcessDeliveryStatusMessage( + i2p::CreateI2NPMessage(msgType, data, size)); + + return true; +} diff --git a/fuzzing/fuzz-RouterContext-ProcessGarlicMessage.cc b/fuzzing/fuzz-RouterContext-ProcessGarlicMessage.cc new file mode 100644 index 00000000..004722ca --- /dev/null +++ b/fuzzing/fuzz-RouterContext-ProcessGarlicMessage.cc @@ -0,0 +1,30 @@ + + +#include +#include + +#include +#include + +#include "fuzzing.h" + + +bool +fuzzing_testinput(const uint8_t * data, size_t size) +{ + i2p::I2NPMessageType msgType; + + + if(size < 1) + return true; + + msgType = (i2p::I2NPMessageType) data[0]; + + data++; + size--; + + i2p::context.ProcessGarlicMessage( + i2p::CreateI2NPMessage(msgType, data, size)); + + return true; +} diff --git a/fuzzing/fuzzing.h b/fuzzing/fuzzing.h new file mode 100644 index 00000000..9f1b3b4c --- /dev/null +++ b/fuzzing/fuzzing.h @@ -0,0 +1,15 @@ +#ifndef _FUZZING_H_ +#define _FUZZING_H_ + +#include +#include + + +void fuzzing_tick(void); + +void fuzzing_throttle(void); + +bool fuzzing_testinput(const uint8_t * data, size_t size); + + +#endif /* !_FUZZING_H_ */ diff --git a/fuzzing/fuzzing_impl.cc b/fuzzing/fuzzing_impl.cc new file mode 100644 index 00000000..c1066e77 --- /dev/null +++ b/fuzzing/fuzzing_impl.cc @@ -0,0 +1,162 @@ + + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fuzzing.h" + + +static bool (*runner)(const uint8_t *, size_t); + + +static +bool +run_single(const uint8_t * data, size_t size) +{ + bool status; + + + status = fuzzing_testinput(data, size); + + fuzzing_tick(); + fuzzing_throttle(); + + return status; +} + + +static +bool +run_batch(const uint8_t * data, size_t size) +{ + bool status; + size_t chunklen; + + + if(size < 2) + { + // XXX - Test something to prevent fuzzer from giving up + status = fuzzing_testinput(data, size); + fuzzing_tick(); + } + else + { + status = false; + + while(size >= 2) + { + chunklen = (data[0] << 8) | data[1]; + + data += 2; + size -= 2; + + if(chunklen > size) + chunklen = size; + + if(fuzzing_testinput(data, chunklen)) + status = true; + + data += chunklen; + size -= chunklen; + + fuzzing_tick(); + } + } + + fuzzing_throttle(); + + return status; +} + + +static +void +do_stop(void) +{ + i2p::tunnel::tunnels.Stop(); + i2p::transport::transports.Stop(); + i2p::data::netdb.Stop(); + i2p::log::Logger().Stop(); +} + + +static +void +do_setup(void) +{ + i2p::log::Logger().Start(); + i2p::log::Logger().SetLogLevel("critical"); + + i2p::config::Init(); + i2p::config::ParseCmdline(1, (char *[]) { (char *) "foo" }); + + // Disable networking + i2p::config::SetOption("ipv4", false); + i2p::config::SetOption("ipv6", false); + + i2p::fs::DetectDataDir("testdata", false); + i2p::fs::Init(); + + i2p::context.SetNetID(I2PD_NET_ID); + i2p::context.Init(); + + i2p::data::netdb.Start(); + + i2p::transport::transports.Start(true, true); + + i2p::tunnel::tunnels.Start(); + + // Stop threads before destructor called to avoid crash on exit + atexit(do_stop); +} + + +static +bool +do_init(void) +{ + do_setup(); + + // + // If FUZZING_BATCH env variable set, run batch mode. + // + // Pros: + // More data queued at once before time to process/empty all of it + // Better change of hitting thread bugs + // + // Cons: + // Input test data limited to 64k + // Input buffer under/over-reads may go un-noticed + // + if(getenv("FUZZING_BATCH") != nullptr) + runner = run_batch; + else + runner = run_single; + + return true; +} + + +extern "C" +int +LLVMFuzzerTestOneInput(const uint8_t * data, size_t size) +{ + static bool inited = do_init(); + + + // Suppress compiler warning + (void) inited; + + return runner(data, size) ? 0 : -1; +} diff --git a/fuzzing/fuzzing_throttle.cc b/fuzzing/fuzzing_throttle.cc new file mode 100644 index 00000000..5591fa3e --- /dev/null +++ b/fuzzing/fuzzing_throttle.cc @@ -0,0 +1,33 @@ + +#include + +#include "fuzzing.h" + + +static unsigned int counter = 0; + + +void +fuzzing_tick(void) +{ + counter++; +} + + +void +fuzzing_throttle(void) +{ + unsigned int delay; + + + // Give queues time to drain (avoid OOM or crash) + // - Too high a delay slows down fuzzing + // - Too low a delay causes intermittent crash on exit + delay = 50 + (counter / 50); + counter = 0; + + if(delay > 5000) + delay = 5000; + + std::this_thread::sleep_for (std::chrono::milliseconds(delay)); +}