Skip to content

Commit 7fd8470

Browse files
🧷 Fuzz testing (#98)
* 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
1 parent 3d50121 commit 7fd8470

17 files changed

+163
-69
lines changed

.github/workflows/coverage.yml

Lines changed: 0 additions & 37 deletions
This file was deleted.

.github/workflows/jwt.yml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
name: JWT CI
2+
3+
on:
4+
push:
5+
branches: [master]
6+
pull_request:
7+
branches: [master]
8+
9+
jobs:
10+
coverage:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v2
14+
- uses: lukka/get-cmake@latest
15+
- uses: ./.github/actions/install/gtest
16+
17+
- name: configure
18+
run: |
19+
mkdir build
20+
cd build
21+
cmake .. -DJWT_BUILD_EXAMPLES=OFF -DJWT_BUILD_TESTS=ON -DJWT_ENABLE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug
22+
23+
- name: run
24+
working-directory: build
25+
run: make jwt-cpp-test coverage
26+
27+
- uses: coverallsapp/[email protected]
28+
with:
29+
github-token: ${{ secrets.GITHUB_TOKEN }}
30+
path-to-lcov: build/coverage.info
31+
32+
fuzzing:
33+
runs-on: ubuntu-latest
34+
steps:
35+
- uses: actions/checkout@v2
36+
- uses: lukka/get-cmake@latest
37+
- uses: ./.github/actions/install/gtest
38+
39+
- name: configure
40+
run: |
41+
mkdir build
42+
cd build
43+
cmake .. -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DJWT_ENABLE_FUZZING=ON
44+
45+
- name: run
46+
working-directory: build
47+
run: |
48+
make jwt-cpp-fuzz-BaseEncodeFuzz jwt-cpp-fuzz-BaseDecodeFuzz jwt-cpp-fuzz-TokenDecodeFuzz
49+
./tests/fuzz/jwt-cpp-fuzz-BaseEncodeFuzz -runs=100000
50+
./tests/fuzz/jwt-cpp-fuzz-BaseDecodeFuzz -runs=100000 ../tests/fuzz/decode-corpus
51+
./tests/fuzz/jwt-cpp-fuzz-TokenDecodeFuzz -runs=100000 ../tests/fuzz/token-corpus
52+
53+
asan: ## Based on https://gist.github.com/jlblancoc/44be9d4d466f0a973b1f3808a8e56782
54+
runs-on: ubuntu-20.04
55+
steps:
56+
- uses: actions/checkout@v2
57+
- uses: lukka/get-cmake@latest
58+
- uses: ./.github/actions/install/gtest
59+
60+
- name: configure
61+
run: |
62+
mkdir build
63+
cd build
64+
cmake .. -DJWT_BUILD_TESTS=ON -DCMAKE_CXX_FLAGS="-fsanitize=address -fsanitize=leak -g" \
65+
-DCMAKE_C_FLAGS="-fsanitize=address -fsanitize=leak -g" \
66+
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address -fsanitize=leak" \
67+
-DCMAKE_MODULE_LINKER_FLAGS="-fsanitize=address -fsanitize=leak"
68+
69+
- name: run
70+
working-directory: build
71+
run: |
72+
make
73+
export ASAN_OPTIONS=fast_unwind_on_malloc=0
74+
./example/rsa-create
75+
./example/rsa-verify
76+
./tests/jwt-cpp-test

.github/workflows/lint.yml

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -51,32 +51,3 @@ jobs:
5151
cd build
5252
make
5353
- run: exit $(git status -s | wc -l)
54-
55-
asan: ## Based on https://gist.github.com/jlblancoc/44be9d4d466f0a973b1f3808a8e56782
56-
runs-on: ubuntu-20.04
57-
steps:
58-
- uses: actions/checkout@v2
59-
- uses: lukka/get-cmake@latest
60-
- uses: ./.github/actions/install/gtest
61-
62-
- name: configure
63-
run: |
64-
mkdir build
65-
cd build
66-
cmake .. -DJWT_BUILD_TESTS=ON -DCMAKE_CXX_FLAGS="-fsanitize=address -fsanitize=leak -g" \
67-
-DCMAKE_C_FLAGS="-fsanitize=address -fsanitize=leak -g" \
68-
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address -fsanitize=leak" \
69-
-DCMAKE_MODULE_LINKER_FLAGS="-fsanitize=address -fsanitize=leak"
70-
71-
- name: build
72-
run: |
73-
cd build
74-
make
75-
76-
- name: run
77-
run: |
78-
export ASAN_OPTIONS=fast_unwind_on_malloc=0
79-
cd build
80-
./example/rsa-create
81-
./example/rsa-verify
82-
./tests/jwt-cpp-test

CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ project(jwt-cpp)
1414
option(JWT_BUILD_EXAMPLES "Configure CMake to build examples (or not)" ON)
1515
option(JWT_BUILD_TESTS "Configure CMake to build tests (or not)" OFF)
1616
option(JWT_ENABLE_COVERAGE "Enable code coverage testing" OFF)
17+
option(JWT_ENABLE_FUZZING "Enable fuzz testing" OFF)
1718

1819
option(JWT_EXTERNAL_PICOJSON "Use find_package() to locate picojson, provided to integrate with package managers" OFF)
1920
option(JWT_DISABLE_BASE64 "Do not include the base64 implementation from this library" OFF)
@@ -110,3 +111,7 @@ endif()
110111
if(JWT_BUILD_TESTS)
111112
add_subdirectory(tests)
112113
endif()
114+
115+
if(JWT_ENABLE_FUZZING)
116+
add_subdirectory(tests/fuzz)
117+
endif()

include/jwt-cpp/base.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,12 @@ namespace jwt {
137137
if (base.substr(size - fill.size(), fill.size()) == fill) {
138138
fill_cnt++;
139139
size -= fill.size();
140-
if (fill_cnt > 2) throw std::runtime_error("Invalid input");
140+
if (fill_cnt > 2) throw std::runtime_error("Invalid input: too much fill");
141141
} else
142142
break;
143143
}
144144

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

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

158158
size_t fast_size = size - size % 4;

tests/fuzz/BaseDecodeFuzz.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#include <jwt-cpp/base.h>
2+
3+
extern "C" {
4+
5+
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
6+
try {
7+
const auto bin = jwt::base::decode<jwt::alphabet::base64>(
8+
std::string{(char *)Data, Size});
9+
} catch (const std::runtime_error &) {
10+
// parse errors are ok, because input may be random bytes
11+
}
12+
return 0; // Non-zero return values are reserved for future use.
13+
}
14+
}

tests/fuzz/BaseEncodeFuzz.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#include <jwt-cpp/base.h>
2+
3+
extern "C" {
4+
5+
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
6+
jwt::base::encode<jwt::alphabet::base64>(std::string{(char *)Data, Size});
7+
return 0; // Non-zero return values are reserved for future use.
8+
}
9+
}

tests/fuzz/CMakeLists.txt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
if(NOT ${CMAKE_CXX_COMPILER_ID} STREQUAL Clang)
2+
message(FATAL_ERROR "Fuzzing is only available on Clang")
3+
endif()
4+
5+
function(ADD_FUZZING_EXECUTABLE TARGET)
6+
add_executable(jwt-cpp-fuzz-${TARGET} "${TARGET}.cpp")
7+
target_compile_options(
8+
jwt-cpp-fuzz-${TARGET}
9+
PRIVATE -g -O1 -fsanitize=fuzzer,address,signed-integer-overflow,undefined
10+
-fno-omit-frame-pointer)
11+
target_link_options(
12+
jwt-cpp-fuzz-${TARGET} PRIVATE
13+
-fsanitize=fuzzer,address,signed-integer-overflow,undefined
14+
-fno-omit-frame-pointer)
15+
target_link_libraries(jwt-cpp-fuzz-${TARGET} PRIVATE jwt-cpp::jwt-cpp)
16+
endfunction()
17+
18+
add_fuzzing_executable(BaseEncodeFuzz)
19+
add_fuzzing_executable(BaseDecodeFuzz)
20+
add_fuzzing_executable(TokenDecodeFuzz)

tests/fuzz/TokenDecodeFuzz.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#include <jwt-cpp/jwt.h>
2+
3+
extern "C" {
4+
5+
int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
6+
try {
7+
// step 1: parse input
8+
const auto jwt1 = jwt::decode(std::string{(char *)Data, Size});
9+
10+
try {
11+
// step 2: round trip
12+
std::string s1 = jwt1.get_token();
13+
const auto jwt2 = jwt::decode(s1);
14+
15+
// tokens must match
16+
if (s1 != jwt2.get_token())
17+
abort();
18+
} catch (...) {
19+
// parsing raw data twice must not fail
20+
abort();
21+
}
22+
} catch (...) {
23+
// parse errors are ok, because input may be random bytes
24+
}
25+
26+
return 0; // Non-zero return values are reserved for future use.
27+
}
28+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
FMF=
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
eCy
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
FF==
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
eCyIcHzyc2RQHa1EchsP11BhieWRIdm2MToLRpVLKFGNKFvfXIEinoFpLv��
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
eCyI
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
..
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
eyJhbGci..

tests/fuzz/token-corpus/valid-sample

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE

0 commit comments

Comments
 (0)