Parcourir la source

Merge pull request #1120 from open62541/feature/fuzzing

Add fuzzing for oss-fuzz
Stefan Profanter il y a 7 ans
Parent
commit
f77b641cfc

+ 24 - 0
.travis-apt-pin.preferences

@@ -0,0 +1,24 @@
+Package: clang-3.9
+Pin: release o=Ubuntu
+Pin-Priority: -1
+
+Package: libclang-common-3.9-dev
+Pin: release o=Ubuntu
+Pin-Priority: -1
+
+Package: libclang1-3.9
+Pin: release o=Ubuntu
+Pin-Priority: -1
+
+Package: libllvm3.9v4
+Pin: release o=Ubuntu
+Pin-Priority: -1
+
+Package: clang-tidy-3.9
+Pin: release o=Ubuntu
+Pin-Priority: -1
+
+Package: libfuzzer-3.9-dev
+Pin: release o=Ubuntu
+Pin-Priority: -1
+

+ 16 - 11
.travis.yml

@@ -23,6 +23,7 @@ env:
     - ANALYZE=false
     - ANALYZE=true
     - DOCKER=true
+    - FUZZER=true
 
 matrix:
   exclude:
@@ -33,9 +34,14 @@ matrix:
       env: ANALYZE=true
     - os: osx
       env: DOCKER=true
+    - os: osx
+      env: FUZZER=true
     - os: linux
       compiler: clang
       env: DOCKER=true
+    - os: linux
+      compiler: gcc
+      env: FUZZER=true
 
 # Required for docker build test
 services:
@@ -48,25 +54,24 @@ addons:
     packages:
       - binutils-mingw-w64-i686
       - build-essential
-      - cmake
-      - gcc-multilib
-      - g++-multilib
-      - valgrind
       - check
+      - cmake
       - cppcheck
-      - mingw-w64
-      - g++-mingw-w64-x86-64
+      - gcc-multilib
       - g++-mingw-w64-i686
-      - libc6-dbg # for valgrind compilation
+      - g++-mingw-w64-x86-64
+      - g++-multilib
+      - graphviz
       - libsubunit-dev
       - libx11-dev
+      - mingw-w64
+      - texlive-fonts-recommended
+      - texlive-latex-extra
+      - texlive-latex-recommended
+      - valgrind
       - wget
       - xutils-dev
       - zip
-      - graphviz
-      - texlive-latex-recommended
-      - texlive-latex-extra
-      - texlive-fonts-recommended
   coverity_scan:
     project:
       name: "open62541/open62541"

+ 10 - 0
CMakeLists.txt

@@ -113,6 +113,9 @@ endif()
 # Build Targets
 option(UA_BUILD_EXAMPLES "Build example servers and clients" OFF)
 option(UA_BUILD_UNIT_TESTS "Build the unit tests" OFF)
+option(UA_BUILD_FUZZING "Build the fuzzing executables" OFF)
+option(UA_BUILD_OSS_FUZZ "Special build switch used in oss-fuzz" OFF)
+mark_as_advanced(UA_BUILD_OSS_FUZZ)
 option(UA_BUILD_EXAMPLES_NODESET_COMPILER "Generate an OPC UA information model from a nodeset XML (experimental)" OFF)
 
 # Advanced Build Targets
@@ -510,6 +513,13 @@ if(UA_BUILD_UNIT_TESTS)
     add_subdirectory(tests)
 endif()
 
+if(UA_BUILD_FUZZING OR UA_BUILD_OSS_FUZZ)
+    # Force enable discovery, to also fuzzy-test this code
+    set(UA_ENABLE_DISCOVERY ON CACHE STRING "" FORCE)
+    set(UA_ENABLE_DISCOVERY_MULTICAST ON CACHE STRING "" FORCE)
+    add_subdirectory(tests/fuzz)
+endif()
+
 ############################
 # Linting run (clang-tidy) #
 ############################

+ 1 - 1
include/ua_plugin_network.h

@@ -53,7 +53,7 @@ typedef enum {
                                 * is not done */
     UA_CONNECTION_ESTABLISHED, /* The socket is open and the connection
                                 * configured */
-    UA_CONNECTION_CLOSED,      /* The socket has been closed and the connection
+    UA_CONNECTION_CLOSED      /* The socket has been closed and the connection
                                 * will be deleted */
 } UA_ConnectionState;
 

+ 1 - 1
include/ua_types.h

@@ -508,7 +508,7 @@ typedef struct UA_DataType UA_DataType;
 typedef enum {
     UA_VARIANT_DATA,          /* The data has the same lifecycle as the
                                  variant */
-    UA_VARIANT_DATA_NODELETE, /* The data is "borrowed" by the variant and
+    UA_VARIANT_DATA_NODELETE /* The data is "borrowed" by the variant and
                                  shall not be deleted at the end of the
                                  variant's lifecycle. */
 } UA_VariantStorageType;

+ 2 - 0
src/server/ua_server_binary.c

@@ -544,6 +544,8 @@ static void processERR(UA_Server *server, UA_Connection *connection, const UA_By
     UA_LOG_ERROR(server->config.logger, UA_LOGCATEGORY_NETWORK,
                  "Client replied with an error message: %s %.*s",
                  UA_StatusCode_name(errorMessage.error), errorMessage.reason.length, errorMessage.reason.data);
+
+    UA_TcpErrorMessage_deleteMembers(&errorMessage);
 }
 
 /* Takes decoded messages starting at the nodeid of the content type. Only OPN

+ 63 - 0
tests/fuzz/CMakeLists.txt

@@ -0,0 +1,63 @@
+include_directories(${PROJECT_SOURCE_DIR}/include)
+include_directories(${PROJECT_SOURCE_DIR}/deps)
+include_directories(${PROJECT_SOURCE_DIR}/src)
+include_directories(${PROJECT_SOURCE_DIR}/src/server)
+include_directories(${PROJECT_SOURCE_DIR}/plugins)
+include_directories(${PROJECT_SOURCE_DIR}/tests)
+include_directories(${PROJECT_BINARY_DIR}/src_generated)
+
+remove_definitions(-std=c99 -Wmissing-prototypes -Wstrict-prototypes)
+set (CMAKE_CXX_STANDARD 11)
+
+if (NOT UA_BUILD_OSS_FUZZ)
+
+    if(NOT "x${CMAKE_C_COMPILER_ID}" STREQUAL "xClang")
+        MESSAGE(FATAL_ERROR "To build fuzzing, you need to use Clang as the compiler")
+    endif()
+
+    # oss-fuzz builds already include these flags
+    if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0)
+        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address")
+        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
+    else()
+        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fsanitize-coverage=trace-pc-guard")
+        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fsanitize-coverage=trace-pc-guard")
+    endif()
+    set(LIBS Fuzzer)
+    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+else()
+    set(LIBS $ENV{LIB_FUZZING_ENGINE})
+    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY $ENV{OUT})
+endif()
+list(APPEND $LIBS ${open62541_LIBRARIES})
+
+
+# Use different plugins for testing
+set(fuzzing_plugin_sources ${PROJECT_SOURCE_DIR}/plugins/ua_network_tcp.c
+        ${PROJECT_SOURCE_DIR}/plugins/ua_clock.c
+        ${PROJECT_SOURCE_DIR}/tests/testing_networklayers.c
+        ${PROJECT_SOURCE_DIR}/plugins/ua_log_stdout.c
+        ${PROJECT_SOURCE_DIR}/plugins/ua_config_standard.c
+        ${PROJECT_SOURCE_DIR}/plugins/ua_accesscontrol_default.c)
+
+add_library(open62541-fuzzplugins OBJECT ${fuzzing_plugin_sources})
+add_dependencies(open62541-fuzzplugins open62541)
+
+# the fuzzer test are built directly on the open62541 object files. so they can
+# access symbols that are hidden/not exported to the shared library
+
+add_executable(fuzz_binary_message fuzz_binary_message.cc $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-fuzzplugins>)
+target_include_directories(fuzz_binary_message PRIVATE ${PROJECT_SOURCE_DIR}/src/server)
+target_link_libraries(fuzz_binary_message ${LIBS})
+
+add_custom_target(
+        run_fuzzer
+        COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/fuzz_binary_message ${CMAKE_CURRENT_SOURCE_DIR}/fuzz_binary_message_corpus/client_Browse.bin
+        COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/fuzz_binary_message ${CMAKE_CURRENT_SOURCE_DIR}/fuzz_binary_message_corpus/client_CLO.bin
+        COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/fuzz_binary_message ${CMAKE_CURRENT_SOURCE_DIR}/fuzz_binary_message_corpus/client_CreateActivateSession.bin
+        COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/fuzz_binary_message ${CMAKE_CURRENT_SOURCE_DIR}/fuzz_binary_message_corpus/client_HELOPN.bin
+        COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/fuzz_binary_message ${CMAKE_CURRENT_SOURCE_DIR}/fuzz_binary_message_corpus/client_Read.bin
+        COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/fuzz_binary_message ${CMAKE_CURRENT_SOURCE_DIR}/fuzz_binary_message_corpus/client_Write.bin
+        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+        DEPENDS fuzz_binary_message
+        ${MAYBE_USES_TERMINAL})

+ 27 - 0
tests/fuzz/binary.dict

@@ -0,0 +1,27 @@
+#
+# AFL dictionary for OPC UA messages
+# -----------------------------
+#
+#
+# Stefan Profanter <git@s.profanter.me>
+#
+
+# Message header for final message (see Spec Part 6, Table 26)
+
+header_msg_final="MSGF"
+header_err_final="ERRF"
+header_opn_final="OPNF"
+header_hel_final="HELF"
+header_ack_final="ACKF"
+header_clo_final="CLOF"
+
+# Message header for message chunk (see Spec Part 6, Table 26)
+
+header_msg_chunk="MSGC"
+header_err_chunk="ERRC"
+header_opn_chunk="OPNC"
+header_hel_chunk="HELC"
+header_ack_chunk="ACKC"
+header_clo_chunk="CLOC"
+
+# TODO add dict for Security Header and Sequence Header

+ 34 - 0
tests/fuzz/fuzz_binary_message.cc

@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ua_server_internal.h"
+#include "ua_config_standard.h"
+#include "ua_log_stdout.h"
+#include "ua_plugin_log.h"
+#include "testing_networklayers.h"
+
+/*
+** Main entry point.  The fuzzer invokes this function with each
+** fuzzed input.
+*/
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+
+    UA_Connection c = createDummyConnection();
+    UA_ServerConfig config = UA_ServerConfig_standard;
+    config.logger = UA_Log_Stdout;
+    UA_Server *server = UA_Server_new(config);
+    UA_ByteString msg = {
+			size, //length
+			const_cast<UA_Byte*>(data) //data
+	};
+
+    config.logger = UA_Log_Stdout;
+    UA_Boolean reallocated = UA_FALSE;
+    UA_StatusCode retval = UA_Connection_completeMessages(&c, &msg, &reallocated);
+    if(retval == UA_STATUSCODE_GOOD && msg.length > 0)
+        UA_Server_processBinaryMessage(server, &c, &msg);
+    UA_Server_delete(server);
+    UA_Connection_deleteMembers(&c);
+    return 0;
+}

+ 2 - 0
tests/fuzz/fuzz_binary_message.options

@@ -0,0 +1,2 @@
+[libfuzzer]
+dict = binary.dict

BIN
tests/fuzz/fuzz_binary_message_corpus/client_Browse.bin


BIN
tests/fuzz/fuzz_binary_message_corpus/client_CLO.bin


BIN
tests/fuzz/fuzz_binary_message_corpus/client_CreateActivateSession.bin


BIN
tests/fuzz/fuzz_binary_message_corpus/client_HELOPN.bin


BIN
tests/fuzz/fuzz_binary_message_corpus/client_Read.bin


BIN
tests/fuzz/fuzz_binary_message_corpus/client_Write.bin


+ 19 - 0
tests/fuzz/oss-fuzz-copy.sh

@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+set -e
+
+# --------------------------------------------------------------------
+# Copies all the corpus files, dict and options to the $OUT directory.
+# This script is only used on oss-fuzz directly
+# --------------------------------------------------------------------
+
+fuzzerFiles=$(find $SRC/open62541/tests/fuzz/ -name "*.cc")
+
+for F in $fuzzerFiles; do
+	fuzzerName=$(basename $F .cc)
+
+	if [ -d "$SRC/open62541/tests/fuzz/${fuzzerName}_corpus" ]; then
+		zip -j $OUT/${fuzzerName}_seed_corpus.zip $SRC/open62541/tests/fuzz/${fuzzerName}_corpus/*
+	fi
+done
+
+cp $SRC/open62541/tests/fuzz/*.dict $SRC/open62541/tests/fuzz/*.options $OUT/

+ 8 - 0
tests/testing_networklayers.h

@@ -7,7 +7,15 @@
 
 #include "ua_server.h"
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 /** @brief Create the TCP networklayer and listen to the specified port */
 UA_Connection createDummyConnection(void);
 
+#ifdef __cplusplus
+}
+#endif
+
 #endif /* TESTING_NETWORKLAYERS_H_ */

+ 13 - 3
tools/travis/travis_linux_before_install.sh

@@ -1,15 +1,25 @@
 #!/bin/bash
 set -ev
 
+
 if [ -z ${DOCKER+x} ]; then
 	# Only on non-docker builds required
 
 	echo "=== Installing from external package sources ===" && echo -en 'travis_fold:start:before_install.external\\r'
-	wget -O - http://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
-	echo "deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-3.9 main" | sudo tee -a /etc/apt/sources.list
+
+	if [ "$CC" = "clang" ]; then
+		# the ubuntu repo has a somehow broken clang-3.9 compiler. We want to use the one from the llvm repo
+		# See https://github.com/openssl/openssl/commit/404c76f4ee1dc51c0d200e2b60a6340aadb44e38
+		sudo cp .travis-apt-pin.preferences /etc/apt/preferences.d/no-ubuntu-clang
+		curl -sSL "http://apt.llvm.org/llvm-snapshot.gpg.key" | sudo -E apt-key add -
+		echo "deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-3.9 main" | sudo tee -a /etc/apt/sources.list > /dev/null
+		sudo -E apt-add-repository -y "ppa:ubuntu-toolchain-r/test"
+		sudo -E apt-get -yq update
+		sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install clang-3.9 clang-tidy-3.9 libfuzzer-3.9-dev
+	fi
+
 	sudo add-apt-repository -y ppa:lttng/ppa
 	sudo apt-get update -qq
-	sudo apt-get install -y clang-3.9 clang-tidy-3.9
 	sudo apt-get install -y liburcu4 liburcu-dev
 	echo -en 'travis_fold:end:script.before_install.external\\r'
 

+ 13 - 0
tools/travis/travis_linux_script.sh

@@ -11,6 +11,18 @@ if ! [ -z ${DOCKER+x} ]; then
     exit 0
 fi
 
+# Fuzzer build test
+if ! [ -z ${FUZZER+x} ]; then
+	mkdir -p build && cd build
+	export CC=clang-3.9
+	export CXX=clang++-3.9
+	cmake -DUA_ENABLE_DISCOVERY=ON -DUA_ENABLE_DISCOVERY_MULTICAST=ON -DUA_BUILD_FUZZING=ON ..
+	make && make run_fuzzer
+	if [ $? -ne 0 ] ; then exit 1 ; fi
+	cd .. && rm build -rf
+    exit 0
+fi
+
 if [ $ANALYZE = "true" ]; then
     echo "=== Running static code analysis ===" && echo -en 'travis_fold:start:script.analyze\\r'
     if [ "$CC" = "clang" ]; then
@@ -201,4 +213,5 @@ else
         echo -en 'travis_fold:end:script.build.coveralls\\r'
     fi
     cd .. && rm build -rf
+
 fi