🧷 Fuzz testing (#98) · Thalhammer/jwt-cpp@7fd8470

17 files changed

lines changed

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/github-action@v1.1.1

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

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

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()

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;

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+

}

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+

}

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)

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+

}

Original file line numberDiff line numberDiff line change

@@ -0,0 +1 @@

1+

FMF=