From 4a922db49836c0301b6ecd140d80dadb47eb94d5 Mon Sep 17 00:00:00 2001 From: Arjun Date: Fri, 8 Nov 2024 23:58:26 +0530 Subject: [PATCH] fuzzing: add fuzzing tests Signed-off-by: Arjun --- FUZZING.md | 40 +++++++++++++ configure.ac | 22 +++++++ src/common/Makefile-common.am | 12 ++++ src/common/fuzz_authorize.c | 59 +++++++++++++++++++ .../basic_fixtures.bin | 1 + .../parse_negotiate_fixtures.bin | 1 + .../parse_x_conversation_fixtures.bin | 1 + src/common/fuzz_base64.c | 25 ++++++++ .../fuzz_base64_seed_corpus/decoded.bin | 1 + .../fuzz_base64_seed_corpus/encoded.bin | 1 + src/websocket/Makefile-websocket.am | 7 +++ src/websocket/fuzz_websocket.c | 56 ++++++++++++++++++ .../fuzz_websocket_seed_corpus/headers.bin | 1 + .../fuzz_websocket_seed_corpus/req_line.bin | 2 + .../status_line.bin | 2 + 15 files changed, 231 insertions(+) create mode 100644 FUZZING.md create mode 100644 src/common/fuzz_authorize.c create mode 100644 src/common/fuzz_authorize_seed_corpus/basic_fixtures.bin create mode 100644 src/common/fuzz_authorize_seed_corpus/parse_negotiate_fixtures.bin create mode 100644 src/common/fuzz_authorize_seed_corpus/parse_x_conversation_fixtures.bin create mode 100644 src/common/fuzz_base64.c create mode 100644 src/common/fuzz_base64_seed_corpus/decoded.bin create mode 100644 src/common/fuzz_base64_seed_corpus/encoded.bin create mode 100644 src/websocket/fuzz_websocket.c create mode 100644 src/websocket/fuzz_websocket_seed_corpus/headers.bin create mode 100644 src/websocket/fuzz_websocket_seed_corpus/req_line.bin create mode 100644 src/websocket/fuzz_websocket_seed_corpus/status_line.bin diff --git a/FUZZING.md b/FUZZING.md new file mode 100644 index 000000000000..ae23390a5dc9 --- /dev/null +++ b/FUZZING.md @@ -0,0 +1,40 @@ +# Fuzzing cockpit + +## Build cockpit fuzzer using libFuzzer. + +### Export flags for fuzzing. + +Note that in `CFLAGS` and `CXXFLAGS`, any type of sanitizers can be added. + +- [AddressSanitizer](https://clang.llvm.org/docs/AddressSanitizer.html), + [ThreadSanitizer](https://clang.llvm.org/docs/ThreadSanitizer.html), + [MemorySanitizer](https://clang.llvm.org/docs/MemorySanitizer.html), + [UndefinedBehaviorSanitizer](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html), + [LeakSanitizer](https://clang.llvm.org/docs/LeakSanitizer.html). + +```shell +$ export CC=clang +$ export CXX=clang++ +$ export CFLAGS="-g -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -fsanitize=address,undefined -fsanitize=fuzzer-no-link" +$ export CXXFLAGS="-g -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -fsanitize=address,undefined -fsanitize=fuzzer-no-link" +$ export LIB_FUZZING_ENGINE="-fsanitize=fuzzer" +``` + +### Build cockpit for fuzzing. + +```shell +$ ./autogen.sh --enable-fuzzing --disable-doc +$ make -j$(nproc) +``` + +### Running fuzzer. + +```shell +$ mkdir -p fuzz_authorize_seed fuzz_base64_seed fuzz_websocket_seed + +$ ./fuzz_authorize fuzz_authorize_seed src/common/fuzz_authorize_seed_corpus +$ ./fuzz_base64 fuzz_base64_seed src/common/fuzz_base64_seed_corpus +$ ./fuzz_websocket fuzz_websocket_seed src/websocket/fuzz_websocket_seed_corpus +``` + +Here is more information about [LibFuzzer](https://llvm.org/docs/LibFuzzer.html). diff --git a/configure.ac b/configure.ac index 6010e8d6ee91..6e8cc679b87a 100644 --- a/configure.ac +++ b/configure.ac @@ -146,6 +146,27 @@ AC_DEFINE_UNQUOTED([PATH_SSH_ADD], ["$SSH_ADD"], [Location of ssh-add binary]) AC_PATH_PROG([SSH_AGENT], [ssh-agent], [/usr/bin/ssh-agent], [$PATH:/usr/local/bin:/usr/bin:/bin]) AC_DEFINE_UNQUOTED([PATH_SSH_AGENT], ["$SSH_AGENT"], [Location of ssh-agent binary]) +# Fuzzing +AC_MSG_CHECKING([for fuzzing]) +AC_ARG_ENABLE(fuzzing, + AS_HELP_STRING([--enable-fuzzing=no/yes], + [Turn the fuzzing on or off]) + ) + +if test "$enable_fuzzing" = "yes"; then + if test -z "$LIB_FUZZING_ENGINE"; then + FUZZING_ENGINE="-fsanitize=fuzzer" + else + FUZZING_ENGINE="$LIB_FUZZING_ENGINE" + fi + AC_SUBST(FUZZING_ENGINE) + fuzzing_status="yes" +else + fuzzing_status="no" +fi +AM_CONDITIONAL(WITH_FUZZING, test "$enable_fuzzing" = "yes") +AC_MSG_RESULT($fuzzing_status) + # Address sanitizer AC_MSG_CHECKING([for asan flags]) AC_ARG_ENABLE(asan, @@ -371,6 +392,7 @@ echo " Debug mode: ${debug_status} Node environment: ${NODE_ENV} With coverage: ${enable_coverage} + With fuzzing: ${fuzzing_status} With address sanitizer: ${asan_status} SELinux Policy: ${enable_selinux_policy} diff --git a/src/common/Makefile-common.am b/src/common/Makefile-common.am index ec4b30df190c..ead5596c7f9d 100644 --- a/src/common/Makefile-common.am +++ b/src/common/Makefile-common.am @@ -189,3 +189,15 @@ TEST_PROGRAM += test-webserver test_webserver_CPPFLAGS = $(libcockpit_common_a_CPPFLAGS) $(TEST_CPP) test_webserver_LDADD = $(TEST_LIBS) test_webserver_SOURCES = src/common/test-webserver.c + +if WITH_FUZZING +noinst_PROGRAMS += fuzz_authorize +fuzz_authorize_SOURCES = src/common/fuzz_authorize.c +fuzz_authorize_CPPFLAGS = $(libcockpit_common_a_CPPFLAGS) +fuzz_authorize_LDADD = $(libcockpit_common_a_LIBS) $(FUZZING_ENGINE) + +noinst_PROGRAMS += fuzz_base64 +fuzz_base64_SOURCES = src/common/fuzz_base64.c +fuzz_base64_CPPFLAGS = $(libcockpit_common_a_CPPFLAGS) +fuzz_base64_LDADD = $(libcockpit_common_a_LIBS) $(FUZZING_ENGINE) +endif diff --git a/src/common/fuzz_authorize.c b/src/common/fuzz_authorize.c new file mode 100644 index 000000000000..7488211edee1 --- /dev/null +++ b/src/common/fuzz_authorize.c @@ -0,0 +1,59 @@ +#include "config.h" + +#include "cockpitauthorize.h" + +#include +#include +#include + +#define kMinInputLength 2 +#define kMaxInputLength 1024 + +extern int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); + +int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + char *data_in; + + if (size < kMinInputLength || size > kMaxInputLength) + return 0; + + data_in = calloc(size + 1, sizeof(char)); + if (data_in == NULL) + return 0; + + memcpy(data_in, data, size); + + { + char *user = "a"; + char *password = NULL; + + password = cockpit_authorize_parse_basic(data_in, &user); + if (password != NULL) { + free(password); + free(user); + } + } + { + void *result = NULL; + + result = cockpit_authorize_parse_negotiate(data_in, NULL); + if (result != NULL) + free(result); + } + { + void *result = NULL; + char *conversation = NULL; + + result = cockpit_authorize_parse_x_conversation(data_in, &conversation); + if (result != NULL) + free(result); + + if (conversation != NULL) + free(conversation); + } + + free(data_in); + return 0; +} diff --git a/src/common/fuzz_authorize_seed_corpus/basic_fixtures.bin b/src/common/fuzz_authorize_seed_corpus/basic_fixtures.bin new file mode 100644 index 000000000000..93212a33d308 --- /dev/null +++ b/src/common/fuzz_authorize_seed_corpus/basic_fixtures.bin @@ -0,0 +1 @@ +Basic c2NydWZmeTp6ZXJvZw== \ No newline at end of file diff --git a/src/common/fuzz_authorize_seed_corpus/parse_negotiate_fixtures.bin b/src/common/fuzz_authorize_seed_corpus/parse_negotiate_fixtures.bin new file mode 100644 index 000000000000..9cc04467c223 --- /dev/null +++ b/src/common/fuzz_authorize_seed_corpus/parse_negotiate_fixtures.bin @@ -0,0 +1 @@ +Negotiate c2NydWZmeTp6ZXJvZw== \ No newline at end of file diff --git a/src/common/fuzz_authorize_seed_corpus/parse_x_conversation_fixtures.bin b/src/common/fuzz_authorize_seed_corpus/parse_x_conversation_fixtures.bin new file mode 100644 index 000000000000..9373934006b2 --- /dev/null +++ b/src/common/fuzz_authorize_seed_corpus/parse_x_conversation_fixtures.bin @@ -0,0 +1 @@ +X-Conversation abcdefghi c2NydWZmeTp6ZXJvZw== \ No newline at end of file diff --git a/src/common/fuzz_base64.c b/src/common/fuzz_base64.c new file mode 100644 index 000000000000..6287e9b2b3a1 --- /dev/null +++ b/src/common/fuzz_base64.c @@ -0,0 +1,25 @@ +#include "config.h" + +#include "cockpitbase64.h" + +#include + +#define kMinInputLength 2 +#define kMaxInputLength 1024 + +extern int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); + +int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + char encoded[2048]; + uint8_t decoded[2048]; + + if (size < kMinInputLength || size > kMaxInputLength) + return 0; + + cockpit_base64_ntop(data, size, encoded, sizeof(encoded)); + cockpit_base64_pton((char *)data, size, decoded, sizeof(decoded)); + + return 0; +} diff --git a/src/common/fuzz_base64_seed_corpus/decoded.bin b/src/common/fuzz_base64_seed_corpus/decoded.bin new file mode 100644 index 000000000000..b40efc7ae9d8 --- /dev/null +++ b/src/common/fuzz_base64_seed_corpus/decoded.bin @@ -0,0 +1 @@ +QUFBQQ== \ No newline at end of file diff --git a/src/common/fuzz_base64_seed_corpus/encoded.bin b/src/common/fuzz_base64_seed_corpus/encoded.bin new file mode 100644 index 000000000000..a9a22e66dbef --- /dev/null +++ b/src/common/fuzz_base64_seed_corpus/encoded.bin @@ -0,0 +1 @@ +AAAA \ No newline at end of file diff --git a/src/websocket/Makefile-websocket.am b/src/websocket/Makefile-websocket.am index 9b8a376b3866..6293e5c499fa 100644 --- a/src/websocket/Makefile-websocket.am +++ b/src/websocket/Makefile-websocket.am @@ -63,3 +63,10 @@ TEST_PROGRAM += test-websocket test_websocket_CPPFLAGS = $(libwebsocket_a_CPPFLAGS) $(TEST_CPP) test_websocket_LDADD = $(libwebsocket_a_LIBS) $(TEST_LIBS) test_websocket_SOURCES = src/websocket/test-websocket.c + +if WITH_FUZZING +noinst_PROGRAMS += fuzz_websocket +fuzz_websocket_SOURCES = src/websocket/fuzz_websocket.c +fuzz_websocket_CPPFLAGS = $(libwebsocket_a_CPPFLAGS) +fuzz_websocket_LDADD = $(libwebsocket_a_LIBS) $(libcockpit_common_a_LIBS) $(FUZZING_ENGINE) +endif diff --git a/src/websocket/fuzz_websocket.c b/src/websocket/fuzz_websocket.c new file mode 100644 index 000000000000..847e6f26bc01 --- /dev/null +++ b/src/websocket/fuzz_websocket.c @@ -0,0 +1,56 @@ +#include "config.h" + +#include "websocket.h" +#include "websocketprivate.h" + +#include + +#define kMinInputLength 2 +#define kMaxInputLength 1024 + +extern int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); + +int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + char *data_in; + + if (size < kMinInputLength || size > kMaxInputLength) + return 0; + + data_in = calloc(size + 1, sizeof(char)); + if (data_in == NULL) + return 0; + + memcpy(data_in, data, size); + + { + gchar *path = NULL; + gchar *method = NULL; + + web_socket_util_parse_req_line((char *)data, size, &method, &path); + if (method != NULL) + g_free(method); + + if (path != NULL) + g_free(path); + } + { + guint status; + gchar *reason = NULL; + + web_socket_util_parse_status_line(data_in, size + 1, NULL, &status, &reason); + if (reason != NULL) + g_free(reason); + } + { + GHashTable *headers = NULL; + + web_socket_util_parse_headers(data_in, size + 1, &headers); + if (headers != NULL) + g_hash_table_unref(headers); + } + + free(data_in); + return 0; +} diff --git a/src/websocket/fuzz_websocket_seed_corpus/headers.bin b/src/websocket/fuzz_websocket_seed_corpus/headers.bin new file mode 100644 index 000000000000..6487589da80a --- /dev/null +++ b/src/websocket/fuzz_websocket_seed_corpus/headers.bin @@ -0,0 +1 @@ +Host:https://cockpit-project.org diff --git a/src/websocket/fuzz_websocket_seed_corpus/req_line.bin b/src/websocket/fuzz_websocket_seed_corpus/req_line.bin new file mode 100644 index 000000000000..d0ab61b8490e --- /dev/null +++ b/src/websocket/fuzz_websocket_seed_corpus/req_line.bin @@ -0,0 +1,2 @@ +GET /path/part HTTP/1.0 + \ No newline at end of file diff --git a/src/websocket/fuzz_websocket_seed_corpus/status_line.bin b/src/websocket/fuzz_websocket_seed_corpus/status_line.bin new file mode 100644 index 000000000000..b51b9a1c9bd3 --- /dev/null +++ b/src/websocket/fuzz_websocket_seed_corpus/status_line.bin @@ -0,0 +1,2 @@ +HTTP/1.0 101 Switching Protocols + \ No newline at end of file