Skip to content

Commit

Permalink
🧷 Fuzz testing (#98)
Browse files Browse the repository at this point in the history
* Update coverage.yml

* adding a fuzz test

* adding more fuzzing

TBA how to handle input requirement of decode

* base64 encoding input to avoid "obvious" exceptions

trying out the EVP_ interface from #89

* fixing decode fuzz

* accepting exceptions are normal

After comparing with https://github.com/nlohmann/json/blob/v3.9.0/test/src/fuzzer-parse_json.cpp I must agree data can be random so it should be accepted

* decoding twice should produce the same result

again based on https://github.com/nlohmann/json/blob/v3.9.0/test/src/fuzzer-parse_json.cpp

* fixing token decode fuzzer

* adding corpus for fuzz tests + adding them to ci

* removing numbers with more meaning descriptions

* Update BaseEncodeFuzz.cpp

* Update coverage.yml

* Update coverage.yml

* shrink interations

* cleaning cmake

* Update and rename coverage.yml to jwt.yml

* Update lint.yml

* Update jwt.yml

* Update jwt.yml
  • Loading branch information
prince-chrismc authored Sep 18, 2021
1 parent 3d50121 commit 7fd8470
Show file tree
Hide file tree
Showing 17 changed files with 163 additions and 69 deletions.
37 changes: 0 additions & 37 deletions .github/workflows/coverage.yml

This file was deleted.

76 changes: 76 additions & 0 deletions .github/workflows/jwt.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: JWT CI

on:
push:
branches: [master]
pull_request:
branches: [master]

jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: lukka/get-cmake@latest
- uses: ./.github/actions/install/gtest

- name: configure
run: |
mkdir build
cd build
cmake .. -DJWT_BUILD_EXAMPLES=OFF -DJWT_BUILD_TESTS=ON -DJWT_ENABLE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug
- name: run
working-directory: build
run: make jwt-cpp-test coverage

- uses: coverallsapp/[email protected]
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: build/coverage.info

fuzzing:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: lukka/get-cmake@latest
- uses: ./.github/actions/install/gtest

- name: configure
run: |
mkdir build
cd build
cmake .. -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DJWT_ENABLE_FUZZING=ON
- name: run
working-directory: build
run: |
make jwt-cpp-fuzz-BaseEncodeFuzz jwt-cpp-fuzz-BaseDecodeFuzz jwt-cpp-fuzz-TokenDecodeFuzz
./tests/fuzz/jwt-cpp-fuzz-BaseEncodeFuzz -runs=100000
./tests/fuzz/jwt-cpp-fuzz-BaseDecodeFuzz -runs=100000 ../tests/fuzz/decode-corpus
./tests/fuzz/jwt-cpp-fuzz-TokenDecodeFuzz -runs=100000 ../tests/fuzz/token-corpus
asan: ## Based on https://gist.github.com/jlblancoc/44be9d4d466f0a973b1f3808a8e56782
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: lukka/get-cmake@latest
- uses: ./.github/actions/install/gtest

- name: configure
run: |
mkdir build
cd build
cmake .. -DJWT_BUILD_TESTS=ON -DCMAKE_CXX_FLAGS="-fsanitize=address -fsanitize=leak -g" \
-DCMAKE_C_FLAGS="-fsanitize=address -fsanitize=leak -g" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address -fsanitize=leak" \
-DCMAKE_MODULE_LINKER_FLAGS="-fsanitize=address -fsanitize=leak"
- name: run
working-directory: build
run: |
make
export ASAN_OPTIONS=fast_unwind_on_malloc=0
./example/rsa-create
./example/rsa-verify
./tests/jwt-cpp-test
29 changes: 0 additions & 29 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,32 +51,3 @@ jobs:
cd build
make
- run: exit $(git status -s | wc -l)

asan: ## Based on https://gist.github.com/jlblancoc/44be9d4d466f0a973b1f3808a8e56782
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: lukka/get-cmake@latest
- uses: ./.github/actions/install/gtest

- name: configure
run: |
mkdir build
cd build
cmake .. -DJWT_BUILD_TESTS=ON -DCMAKE_CXX_FLAGS="-fsanitize=address -fsanitize=leak -g" \
-DCMAKE_C_FLAGS="-fsanitize=address -fsanitize=leak -g" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address -fsanitize=leak" \
-DCMAKE_MODULE_LINKER_FLAGS="-fsanitize=address -fsanitize=leak"
- name: build
run: |
cd build
make
- name: run
run: |
export ASAN_OPTIONS=fast_unwind_on_malloc=0
cd build
./example/rsa-create
./example/rsa-verify
./tests/jwt-cpp-test
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ project(jwt-cpp)
option(JWT_BUILD_EXAMPLES "Configure CMake to build examples (or not)" ON)
option(JWT_BUILD_TESTS "Configure CMake to build tests (or not)" OFF)
option(JWT_ENABLE_COVERAGE "Enable code coverage testing" OFF)
option(JWT_ENABLE_FUZZING "Enable fuzz testing" OFF)

option(JWT_EXTERNAL_PICOJSON "Use find_package() to locate picojson, provided to integrate with package managers" OFF)
option(JWT_DISABLE_BASE64 "Do not include the base64 implementation from this library" OFF)
Expand Down Expand Up @@ -110,3 +111,7 @@ endif()
if(JWT_BUILD_TESTS)
add_subdirectory(tests)
endif()

if(JWT_ENABLE_FUZZING)
add_subdirectory(tests/fuzz)
endif()
6 changes: 3 additions & 3 deletions include/jwt-cpp/base.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,12 @@ namespace jwt {
if (base.substr(size - fill.size(), fill.size()) == fill) {
fill_cnt++;
size -= fill.size();
if (fill_cnt > 2) throw std::runtime_error("Invalid input");
if (fill_cnt > 2) throw std::runtime_error("Invalid input: too much fill");
} else
break;
}

if ((size + fill_cnt) % 4 != 0) throw std::runtime_error("Invalid input");
if ((size + fill_cnt) % 4 != 0) throw std::runtime_error("Invalid input: incorrect total size");

size_t out_size = size / 4 * 3;
std::string res;
Expand All @@ -152,7 +152,7 @@ namespace jwt {
for (size_t i = 0; i < alphabet.size(); i++) {
if (alphabet[i] == base[offset]) return static_cast<uint32_t>(i);
}
throw std::runtime_error("Invalid input");
throw std::runtime_error("Invalid input: not within alphabet");
};

size_t fast_size = size - size % 4;
Expand Down
14 changes: 14 additions & 0 deletions tests/fuzz/BaseDecodeFuzz.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#include <jwt-cpp/base.h>

extern "C" {

int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
try {
const auto bin = jwt::base::decode<jwt::alphabet::base64>(
std::string{(char *)Data, Size});
} catch (const std::runtime_error &) {
// parse errors are ok, because input may be random bytes
}
return 0; // Non-zero return values are reserved for future use.
}
}
9 changes: 9 additions & 0 deletions tests/fuzz/BaseEncodeFuzz.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include <jwt-cpp/base.h>

extern "C" {

int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
jwt::base::encode<jwt::alphabet::base64>(std::string{(char *)Data, Size});
return 0; // Non-zero return values are reserved for future use.
}
}
20 changes: 20 additions & 0 deletions tests/fuzz/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
if(NOT ${CMAKE_CXX_COMPILER_ID} STREQUAL Clang)
message(FATAL_ERROR "Fuzzing is only available on Clang")
endif()

function(ADD_FUZZING_EXECUTABLE TARGET)
add_executable(jwt-cpp-fuzz-${TARGET} "${TARGET}.cpp")
target_compile_options(
jwt-cpp-fuzz-${TARGET}
PRIVATE -g -O1 -fsanitize=fuzzer,address,signed-integer-overflow,undefined
-fno-omit-frame-pointer)
target_link_options(
jwt-cpp-fuzz-${TARGET} PRIVATE
-fsanitize=fuzzer,address,signed-integer-overflow,undefined
-fno-omit-frame-pointer)
target_link_libraries(jwt-cpp-fuzz-${TARGET} PRIVATE jwt-cpp::jwt-cpp)
endfunction()

add_fuzzing_executable(BaseEncodeFuzz)
add_fuzzing_executable(BaseDecodeFuzz)
add_fuzzing_executable(TokenDecodeFuzz)
28 changes: 28 additions & 0 deletions tests/fuzz/TokenDecodeFuzz.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include <jwt-cpp/jwt.h>

extern "C" {

int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
try {
// step 1: parse input
const auto jwt1 = jwt::decode(std::string{(char *)Data, Size});

try {
// step 2: round trip
std::string s1 = jwt1.get_token();
const auto jwt2 = jwt::decode(s1);

// tokens must match
if (s1 != jwt2.get_token())
abort();
} catch (...) {
// parsing raw data twice must not fail
abort();
}
} catch (...) {
// parse errors are ok, because input may be random bytes
}

return 0; // Non-zero return values are reserved for future use.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FMF=
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eCy
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FF==
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eCyIcHzyc2RQHa1EchsP11BhieWRIdm2MToLRpVLKFGNKFvfXIEinoFpLv��
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eCyI
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
..
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eyJhbGci..
1 change: 1 addition & 0 deletions tests/fuzz/token-corpus/valid-sample
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE

0 comments on commit 7fd8470

Please sign in to comment.