Browse Source

Fuzz: Add custom memory manager to simulate OOM

Adds custom memory manager to keep track of allocated and freed memory.
Based on the fuzzer input the maximum available RAM is set and if the
limit is reached no new allocation is allowed.

With this approach we can test OOM and memory allocation failure paths
in the source code.
Stefan Profanter 5 years ago
parent
commit
8c87552a5b

+ 13 - 0
CMakeLists.txt

@@ -413,6 +413,13 @@ if(MSVC)
   endif()
 endif()
 
+if(UA_BUILD_FUZZING OR UA_BUILD_OSS_FUZZ)
+    add_definitions(-DUA_malloc=UA_memoryManager_malloc)
+    add_definitions(-DUA_free=UA_memoryManager_free)
+    add_definitions(-DUA_calloc=UA_memoryManager_calloc)
+    add_definitions(-DUA_realloc=UA_memoryManager_realloc)
+endif()
+
 #########################
 # Generate Main Library #
 #########################
@@ -622,6 +629,12 @@ if(UA_ENABLE_DISCOVERY_MULTICAST)
         ${lib_sources})
 endif()
 
+if(UA_BUILD_FUZZING OR UA_BUILD_OSS_FUZZ)
+    set(lib_sources
+        ${lib_sources}
+        ${PROJECT_SOURCE_DIR}/tests/fuzz/custom_memory_manager.c)
+endif()
+
 #########################
 # Generate source files #
 #########################

+ 0 - 1
tests/CMakeLists.txt

@@ -43,7 +43,6 @@ set(test_plugin_sources ${PROJECT_SOURCE_DIR}/arch/ua_network_tcp.c
                         ${PROJECT_SOURCE_DIR}/plugins/ua_accesscontrol_default.c
                         ${PROJECT_SOURCE_DIR}/plugins/ua_pki_certificate.c
                         ${PROJECT_SOURCE_DIR}/plugins/ua_nodestore_default.c
-                        ${PROJECT_SOURCE_DIR}/tests/testing-plugins/testing_clock.c
                         ${PROJECT_SOURCE_DIR}/plugins/historydata/ua_historydatabackend_memory.c
                         ${PROJECT_SOURCE_DIR}/plugins/historydata/ua_historydatagathering_default.c
                         ${PROJECT_SOURCE_DIR}/plugins/historydata/ua_historydatabase_default.c

+ 3 - 1
tests/fuzz/CMakeLists.txt

@@ -70,7 +70,9 @@ if(UA_ENABLE_ENCRYPTION)
         ${PROJECT_SOURCE_DIR}/plugins/ua_securitypolicy_basic256sha256.c)
 endif()
 
-add_library(open62541-fuzzplugins OBJECT ${fuzzing_plugin_sources} ${PROJECT_SOURCE_DIR}/arch/${UA_ARCHITECTURE}/ua_architecture_functions.c)
+add_library(open62541-fuzzplugins OBJECT
+            ${fuzzing_plugin_sources}
+            ${PROJECT_SOURCE_DIR}/arch/${UA_ARCHITECTURE}/ua_architecture_functions.c)
 add_dependencies(open62541-fuzzplugins open62541)
 
 # the fuzzer test are built directly on the open62541 object files. so they can

+ 165 - 0
tests/fuzz/custom_memory_manager.c

@@ -0,0 +1,165 @@
+/* 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/.
+ *
+ *    Copyright 2018 (c) Stefan Profanter, fortiss GmbH
+ */
+
+/**
+ * This memory manager allows to manually reduce the available RAM.
+ *
+ * It keeps track of malloc and free calls and counts the available RAM.
+ * If the requested memory allocation results in hitting the limit,
+ * it will return NULL and prevent the new allocation.
+ */
+
+#include "custom_memory_manager.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <limits.h>
+
+#include <pthread.h>
+
+pthread_mutex_t mutex;
+
+struct UA_mm_entry {
+    size_t size;
+    void* address;
+    struct UA_mm_entry *next;
+    struct UA_mm_entry *prev;
+};
+
+unsigned long long totalMemorySize = 0;
+unsigned long long memoryLimit = ULONG_MAX;
+
+/*
+ * Head and Tail of the double linked list
+ */
+struct UA_mm_entry *address_map_first = NULL;
+struct UA_mm_entry *address_map_last = NULL;
+
+void UA_memoryManager_setLimit(unsigned long long newLimit) {
+    memoryLimit = newLimit;
+    printf("MemoryManager: Setting memory limit to %lld\n", newLimit);
+}
+
+int UA_memoryManager_setLimitFromLast4Bytes(const uint8_t *data, size_t size) {
+    if (size <4)
+        return 0;
+    // just cast the last 4 bytes to uint32
+    const uint32_t *newLimit = (const void*)&(data[size-4]);
+    UA_memoryManager_setLimit(*newLimit);
+    return 1;
+}
+
+/**
+ * Add address entry to the linked list after it was allocated.
+ *
+ * @param size Size of the allocated memory block
+ * @param addr Address of the allocated memory block
+ * @return 1 on success, 0 if it failed
+ */
+static int addToMap(size_t size, void *addr) {
+    struct UA_mm_entry *newEntry = (struct UA_mm_entry*)malloc(sizeof(struct UA_mm_entry));
+    if (!newEntry) {
+        printf("MemoryManager: Could not allocate memory");
+        return 0;
+    }
+    newEntry->size = size;
+    newEntry->address = addr;
+    newEntry->next = NULL;
+    newEntry->prev = address_map_last;
+    pthread_mutex_lock(&mutex);
+    if (address_map_last)
+        address_map_last->next = newEntry;
+    address_map_last = newEntry;
+    totalMemorySize += size;
+    if (address_map_first == NULL) {
+        address_map_first = newEntry;
+    }
+    pthread_mutex_unlock(&mutex);
+    //printf("Total size (malloc): %lld And new address: %p Entry %p\n", totalMemorySize, addr, (void*)newEntry);
+
+    return 1;
+}
+
+/**
+ * Remove entry from the list before the memory block is freed.
+ *
+ * @param addr Address of the memory block which is freed.
+ *
+ * @return 1 on success, 0 if the memory block was not found in the list.
+ */
+static int removeFromMap(void *addr) {
+    if (addr == NULL)
+        return 1;
+
+
+    pthread_mutex_lock(&mutex);
+
+    struct UA_mm_entry *e = address_map_last;
+
+    while (e) {
+        if (e->address == addr) {
+            if (e == address_map_first)
+                address_map_first = e->next;
+            if (e == address_map_last)
+                address_map_last = e->prev;
+            if (e->prev) {
+                e->prev->next = e->next;
+            }
+            if (e->next) {
+                e->next->prev = e->prev;
+            }
+            totalMemorySize -= e->size;
+
+            //printf("Total size (free): %lld after addr %p and deleting %p\n", totalMemorySize, addr, (void*)e);
+            free(e);
+            pthread_mutex_unlock(&mutex);
+            return 1;
+        }
+        e = e->prev;
+    }
+    pthread_mutex_unlock(&mutex);
+    printf("MemoryManager: Entry with address %p not found", addr);
+    return 0;
+}
+
+void* UA_memoryManager_malloc(size_t size) {
+    if (totalMemorySize + size > memoryLimit)
+        return NULL;
+    void *addr = malloc(size);
+    if (!addr)
+        return NULL;
+    addToMap(size, addr);
+    return addr;
+}
+
+void* UA_memoryManager_calloc(size_t num, size_t size) {
+    if (totalMemorySize + (size * num) > memoryLimit)
+        return NULL;
+    void *addr = calloc(num, size);
+    if (!addr)
+        return NULL;
+    addToMap(size*num, addr);
+    return addr;
+}
+
+void* UA_memoryManager_realloc(void *ptr, size_t new_size) {
+    removeFromMap(ptr);
+    if (totalMemorySize + new_size > memoryLimit)
+        return NULL;
+    void *addr = realloc(ptr, new_size);
+    if (!addr)
+        return NULL;
+    addToMap(new_size, addr);
+    return addr;
+
+}
+
+void UA_memoryManager_free(void* ptr) {
+    removeFromMap(ptr);
+    free(ptr);
+}
+

+ 37 - 0
tests/fuzz/custom_memory_manager.h

@@ -0,0 +1,37 @@
+/* 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/.
+ *
+ *    Copyright 2018 (c) Stefan Profanter, fortiss GmbH
+ */
+
+#ifndef OPEN62541_CUSTOM_MEMORY_MANAGER_H
+#define OPEN62541_CUSTOM_MEMORY_MANAGER_H
+
+
+#include "ua_types.h"
+
+_UA_BEGIN_DECLS
+
+/**
+ * Set memory limit for memory manager.
+ * This allows to reduce the available memory (RAM) for fuzzing tests.
+ *
+ * @param maxMemory Available memory in bytes
+ */
+void UA_EXPORT UA_memoryManager_setLimit(unsigned long long maxMemory);
+
+/**
+ * Extract the memory limit from the last four bytes of the byte array.
+ * The last four bytes will simply be casted to a uint32_t and that value
+ * represents the new memory limit.
+ *
+ * @param data byte array
+ * @param size size of the byte array
+ * @return 1 on success, 0 if the byte array is too short
+ */
+int UA_EXPORT UA_memoryManager_setLimitFromLast4Bytes(const uint8_t *data, size_t size);
+
+_UA_END_DECLS
+
+#endif //OPEN62541_CUSTOM_MEMORY_MANAGER_H

tests/fuzz/binary.dict → tests/fuzz/dicts/binary.dict


+ 5 - 0
tests/fuzz/fuzz_binary_decode.cc

@@ -2,6 +2,7 @@
  * 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 "custom_memory_manager.h"
 #include <ua_types.h>
 #include "ua_server_internal.h"
 #include "ua_config_default.h"
@@ -96,6 +97,10 @@ static UA_Boolean tortureExtensionObject(const uint8_t *data, size_t size, size_
 */
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
 
+    if (!UA_memoryManager_setLimitFromLast4Bytes(data, size))
+        return 0;
+    size -= 4;
+
     size_t offset;
     if (!tortureEncoding(data, size, &offset)) {
         return 0;

+ 16 - 2
tests/fuzz/fuzz_binary_message.cc

@@ -2,25 +2,39 @@
  * 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 "custom_memory_manager.h"
 #include "ua_server_internal.h"
 #include "ua_config_default.h"
 #include "ua_log_stdout.h"
 #include "ua_plugin_log.h"
 #include "testing_networklayers.h"
 
+#define RECEIVE_BUFFER_SIZE 65535
+
 /*
 ** 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(65535, NULL);
+
+    if (!UA_memoryManager_setLimitFromLast4Bytes(data, size))
+        return 0;
+    size -= 4;
+
+    UA_Connection c = createDummyConnection(RECEIVE_BUFFER_SIZE, NULL);
     UA_ServerConfig *config = UA_ServerConfig_new_default();
+    if (!config) {
+        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
+                     "Could not create server config using UA_ServerConfig_new_default");
+        return 0;
+    }
     UA_Server *server = UA_Server_new(config);
     if (server == NULL) {
+        UA_ServerConfig_delete(config);
         UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
                      "Could not create server instance using UA_Server_new");
-        return 1;
+        return 0;
     }
 
     // we need to copy the message because it will be freed in the processing function

+ 1 - 1
tests/fuzz/fuzz_binary_message.options

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