-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
33 changed files
with
3,162 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
build | ||
corpus |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# OpenVPN Fuzzing | ||
|
||
## Setup | ||
The fuzzing setup is handled by Nix inside a `nix-shell` and works both on | ||
Linux and macOS. Nix is the only dependency (https://nixos.org/download.html). | ||
|
||
## Usage | ||
|
||
```sh | ||
$ nix-shell fuzz/shell.nix | ||
$ autoreconf -i -v -f | ||
$ ./configure --disable-lz4 | ||
$ cd fuzz | ||
$ ./openvpn-fuzz.py fuzz base64 | ||
$ ./openvpn-fuzz.py fuzz parse_argv -- -fork=4 -ignore_crashes=1 | ||
$ ./openvpn-fuzz.py coverage base64 parse_argv # specified targets | ||
$ ./openvpn-fuzz.py coverage # all targets | ||
$ ./openvpn-fuzz.py coverage --clean # do make clean before and after, use if previously built for fuzzing | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
#!/usr/bin/env python | ||
|
||
import argparse | ||
import os | ||
import platform | ||
import subprocess | ||
import sys | ||
|
||
TARGETS = [ | ||
'base64', | ||
'buffer', | ||
'dhcp', | ||
'forward', | ||
'list', | ||
'misc', | ||
'mroute', | ||
'mss', | ||
'packet_id', | ||
'parse_argv', | ||
'proxy', | ||
'route', | ||
'verify_cert', | ||
] | ||
|
||
BASE_DIR = os.path.dirname(os.path.realpath(__file__)) | ||
|
||
def fuzz_target(target, args=[]): | ||
build_targets([target]) | ||
os.makedirs(corpus_dir(target), exist_ok=True) | ||
os.chdir(target_dir(target, "fuzzer")) | ||
os.execv(target_bin_path(target, "fuzzer"), | ||
[target_bin_path(target, "fuzzer"), corpus_dir(target)] + args) | ||
|
||
def generate_coverage_report(targets=TARGETS): | ||
""" | ||
If OpenVPN was previously built for fuzzing run `make -C ../ clean` before and after generating coverage. | ||
""" | ||
wd = os.getcwd() | ||
build_targets(targets, for_coverage=True) | ||
profraws = [] | ||
object_args = [] | ||
for target in targets: | ||
os.chdir(target_dir(target, "coverage")) | ||
profraws.append(target_dir(target, "coverage", "default.profraw")) | ||
object_args.append("-object") | ||
object_args.append(target_bin_path(target, "coverage")) | ||
subprocess.run([target_bin_path(target, "coverage"), corpus_dir(target), "-runs=0"]) | ||
|
||
profdata = build_dir("coverage", "combined.profdata") | ||
subprocess.run(["llvm-profdata", "merge", "-o", profdata, "-sparse"] + profraws) | ||
subprocess.run(["llvm-cov", "show", "--format", "html", f"-instr-profile={profdata}", | ||
"-output-dir", build_dir("coverage", "report")] + object_args) | ||
os.chdir(wd) | ||
|
||
def triage_parse_argv_crashes(): | ||
""" | ||
Filters out false positives that are caused by calling exit. | ||
""" | ||
import pwn | ||
target = "parse_argv" | ||
for filename in os.listdir(target_dir(target, "fuzzer")): | ||
if "crash-" in filename: | ||
print("Triaging", filename) | ||
with open(target_dir(target, "fuzzer", filename), "rb") as f: | ||
argv_raw = f.read() | ||
p = pwn.process(executable="../src/openvpn/openvpn", argv=argv_raw.split(b'\x00')) | ||
out = p.readall() | ||
if b"SIGSEGV" in out or b"smashing" or b"AddressSanitizer" in out: | ||
print(pwn.hexdump(argv_raw)) | ||
print(out) | ||
exit(1) | ||
p.close() | ||
|
||
def build_openvpn(cflags): | ||
""" | ||
Build OpenVPN as usual, assumes `autoconf -f -v -f` and `./configure --disable-lz4` already run. | ||
""" | ||
subprocess.run(["make", "-j", "-C", "../", f"CFLAGS={cflags}"]) | ||
|
||
def build_targets(targets, for_coverage=False): | ||
fuzzer_flags = '-g -fsanitize=address,fuzzer-no-link' | ||
coverage_flags = '-g -fprofile-instr-generate -fcoverage-mapping' | ||
|
||
build_subdir = 'coverage' if for_coverage else 'fuzzer' | ||
os.makedirs(build_dir(build_subdir), exist_ok=True) | ||
|
||
cflags = coverage_flags if for_coverage else fuzzer_flags | ||
cflags += " -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION" | ||
build_openvpn(cflags) | ||
|
||
o_files = [] | ||
for file in os.listdir("../src/openvpn"): | ||
if file.endswith(".o") and file != 'openvpn.o': | ||
o_files.append("../src/openvpn/" + file) | ||
subprocess.run(["ar", "r", build_dir(build_subdir, "libopenvpn.a")] + o_files) | ||
|
||
subprocess.run(["clang++", "-c", "src/fuzz_randomizer.cpp", | ||
"-o", build_dir(build_subdir, "fuzz_randomizer.o")] + | ||
cflags.split(' ')) | ||
|
||
extra_libs = ["-lc++abi", "-lresolv"] if platform.system() == 'Darwin' else ['-lcap-ng'] | ||
|
||
for target in targets: | ||
os.makedirs(target_dir(target, build_subdir), exist_ok=True) | ||
subprocess.run(["clang", "-I../src/openvpn", "-I..", "-I../src/compat", "-I../include", | ||
"-lssl", "-lcrypto", "-llzo2", f"src/fuzz_{target}.c", | ||
build_dir(build_subdir, "libopenvpn.a"), | ||
build_dir(build_subdir, "fuzz_randomizer.o"), | ||
"-o", target_bin_path(target, build_subdir), | ||
"-g", "-fsanitize=address,fuzzer"] + | ||
(coverage_flags.split(' ') if for_coverage else []) + | ||
extra_libs) | ||
|
||
def build_dir(subdir, file=''): | ||
""" | ||
There are two build flavors that live in their own subdirs: coverage and fuzzer. | ||
""" | ||
return os.path.join(BASE_DIR, "build", subdir, file) | ||
|
||
def target_dir(target, subdir, file=''): | ||
return os.path.join(build_dir(subdir), f"fuzz_{target}", file) | ||
|
||
def corpus_dir(target): | ||
return os.path.join(BASE_DIR, "corpus", f"fuzz_{target}") | ||
|
||
def target_bin(target): | ||
return f"fuzz_{target}" | ||
|
||
def target_bin_path(target, subdir): | ||
return target_dir(target, subdir, target_bin(target)) | ||
|
||
if __name__ == "__main__": | ||
parser = argparse.ArgumentParser() | ||
subparsers = parser.add_subparsers(dest="subcommand") | ||
fuzz_parser = subparsers.add_parser('fuzz') | ||
fuzz_parser.add_argument('target', type=str) | ||
fuzz_parser.add_argument('libfuzzer_args', type=str, nargs='*') | ||
coverage_parser = subparsers.add_parser('coverage') | ||
coverage_parser.add_argument('targets', type=str, nargs='*') | ||
coverage_parser.add_argument('--clean', action=argparse.BooleanOptionalAction) | ||
|
||
args = parser.parse_args() | ||
if args.subcommand == 'fuzz': | ||
# ./openvpn-fuzz.py fuzz proxy -- -fork=4 -ignore_crashes=1 | ||
fuzz_target(args.target, args.libfuzzer_args) | ||
elif args.subcommand == 'coverage': | ||
if args.clean: | ||
subprocess.run(["make", "-C", "../", "clean"]) | ||
|
||
if args.targets: | ||
generate_coverage_report(args.targets) | ||
else: | ||
generate_coverage_report() | ||
|
||
if args.clean: | ||
subprocess.run(["make", "-C", "../", "clean"]) | ||
else: | ||
parser.print_help() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
with import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/e58a7747db96c23b8a977e7c1bbfc5753b81b6fa.tar.gz") {}; | ||
|
||
let llvmPackages = llvmPackages_14; | ||
in llvmPackages.stdenv.mkDerivation { | ||
name = "openvpn-fuzz"; | ||
buildInputs = [ | ||
autoconf | ||
automake | ||
m4 | ||
libtool | ||
pkg-config | ||
openssl_1_1 | ||
lz4 | ||
lzo | ||
pam | ||
llvmPackages.llvm | ||
python3Packages.pwntools | ||
] ++ lib.optional (!stdenv.isDarwin) libcap_ng; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* Copyright 2021 Google LLC | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
#include <stdint.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
|
||
#include "base64.h" | ||
|
||
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { | ||
if (size > 500) { | ||
return 0; | ||
} | ||
|
||
char *new_str = (char *)malloc(size + 1); | ||
if (new_str == NULL) { | ||
return 0; | ||
} | ||
memcpy(new_str, data, size); | ||
new_str[size] = '\0'; | ||
|
||
char *str = NULL; | ||
openvpn_base64_encode(data, size, &str); | ||
if(str != NULL) { | ||
free(str); | ||
} | ||
|
||
uint16_t outsize = 10000; | ||
char *output_buf = (char *)malloc(outsize); | ||
openvpn_base64_decode(new_str, output_buf, outsize); | ||
free(output_buf); | ||
|
||
free(new_str); | ||
return 0; | ||
} |
Oops, something went wrong.