Skip to content

Commit

Permalink
Add OSSFuzz Integration (#352)
Browse files Browse the repository at this point in the history
* Moved fuzzing source into pyvex

* Added CI-Fuzz

* Attempt to resolve syntax error in cifuzz

* Force workflow

* Set language to python in cifuzz

* Added a more intelligent catch for value error exceptions

* Resolved inaccurate crashes

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Appeasing the linter a bit

* Resolved another pylint complaint

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Moved atheris requirement to testing in setup.cfg

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* The CI server only has Apple Clang and you can't use atheris on Windows, so make it an optional install

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Moved build-script into pyvex source

* Updated CIFuzz job with a more specific name and to run on pushes to main

* Updated submodule to reflect master

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
capuanob and pre-commit-ci[bot] authored Oct 6, 2023
1 parent b822251 commit d9e3ab6
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 0 deletions.
39 changes: 39 additions & 0 deletions .github/workflows/cifuzz.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: OSS-Fuzz
on:
push:
branches:
- master
pull_request:
permissions: {}
jobs:
Fuzzing:
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- name: Build Fuzzers
id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
with:
oss-fuzz-project-name: 'pyvex'
language: python
- name: Run Fuzzers
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
with:
oss-fuzz-project-name: 'pyvex'
language: python
fuzz-seconds: 600
output-sarif: true
- name: Upload Crash
uses: actions/upload-artifact@v3
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts
- name: Upload Sarif
if: always() && steps.build.outcome == 'success'
uses: github/codeql-action/upload-sarif@v2
with:
# Path to SARIF file relative to the root of the repository
sarif_file: cifuzz-sarif/results.sarif
checkout_path: cifuzz-sarif
33 changes: 33 additions & 0 deletions fuzzing/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/bash -eu
# Copyright 2023 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.
#
################################################################################

# Since pyvex requires a specific developer build of archinfo, install it from source
cd "$SRC"/archinfo
pip3 install .

cd "$SRC"/pyvex
pip3 install .[testing]

# Generate a simple binary for the corpus
echo -ne "start:\n\txor %edi, %edi\nmov \$60, %eax\nsyscall" > /tmp/corpus.s
clang -Os -s /tmp/corpus.s -nostdlib -nostartfiles -m32 -o corpus
zip -r "$OUT"/irsb_fuzzer_seed_corpus.zip corpus

# Build fuzzers in $OUT
for fuzzer in $(find $SRC -name 'fuzzing/*_fuzzer.py'); do
compile_python_fuzzer "$fuzzer" --add-binary="pyvex/lib/libpyvex.so:pyvex/lib"
done
60 changes: 60 additions & 0 deletions fuzzing/enhanced_fdp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# 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.
#
################################################################################
"""
Defines the EnhancedFuzzedDataProvider
"""
from atheris import FuzzedDataProvider


class EnhancedFuzzedDataProvider(FuzzedDataProvider):
"""
Extends the functionality of FuzzedDataProvider
"""

def _consume_random_count(self) -> int:
"""
:return: A count of bytes that is strictly in range 0<=n<=remaining_bytes
"""
return self.ConsumeIntInRange(0, self.remaining_bytes())

def ConsumeRandomBytes(self) -> bytes:
"""
Consume a 'random' count of the remaining bytes
:return: 0<=n<=remaining_bytes bytes
"""
return self.ConsumeBytes(self._consume_random_count())

def ConsumeRemainingBytes(self) -> bytes:
"""
:return: The remaining buffer
"""
return self.ConsumeBytes(self.remaining_bytes())

def ConsumeRandomString(self) -> str:
"""
Consume a 'random' length string, excluding surrogates
:return: The string
"""
return self.ConsumeUnicodeNoSurrogates(self._consume_random_count())

def ConsumeRemainingString(self) -> str:
"""
:return: The remaining buffer, as a string without surrogates
"""
return self.ConsumeUnicodeNoSurrogates(self.remaining_bytes())

def PickValueInEnum(self, enum):
return self.PickValueInList([e.value for e in enum])
101 changes: 101 additions & 0 deletions fuzzing/irsb_fuzzer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/usr/bin/python3
# Copyright 2023 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.
#
################################################################################
import re
import sys
from contextlib import contextmanager
from enum import IntEnum
from io import StringIO

import atheris

with atheris.instrument_imports(include=["pyvex"]):
import pyvex

# Additional imports
import archinfo
from enhanced_fdp import EnhancedFuzzedDataProvider

register_error_msg = re.compile("Register .*? does not exist!")


@contextmanager
def nostdout():
saved_stdout = sys.stdout
saved_stderr = sys.stderr
sys.stdout = StringIO()
sys.stderr = StringIO()
yield
sys.stdout = saved_stdout
sys.stderr = saved_stderr


# Save all available architectures off
available_archs = [tup[3]() for tup in archinfo.arch.arch_id_map if len(tup) >= 3]


class SupportedOptLevels(IntEnum):
"""
Enumerates the supported optimization levels within pyvex, as derived from the documentation
"""

StrictUnopt = -1
Unopt = 0
Opt = 1
StrictOpt = 2


def consume_random_arch(fdp: atheris.FuzzedDataProvider) -> archinfo.Arch:
return fdp.PickValueInList(available_archs)


def TestOneInput(data: bytes):
fdp = EnhancedFuzzedDataProvider(data)

arch = consume_random_arch(fdp)

try:
with nostdout():
data = fdp.ConsumeRandomBytes()
max_bytes = fdp.ConsumeIntInRange(0, len(data))
irsb = pyvex.lift(
data,
fdp.ConsumeInt(arch.bits),
arch,
max_bytes=fdp.ConsumeIntInRange(0, len(data)),
max_inst=fdp.ConsumeInt(16),
bytes_offset=fdp.ConsumeIntInRange(0, max_bytes),
opt_level=fdp.PickValueInEnum(SupportedOptLevels),
)
irsb.pp()
return 0
except pyvex.PyVEXError:
return -1
except ValueError as e:
if re.match(register_error_msg, str(e)):
return -1
raise e
except OverflowError:
return -1


def main():
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()


if __name__ == "__main__":
main()
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ docs =
myst-parser
sphinx
sphinx-autodoc-typehints
fuzzing =
atheris>=2.3.0
testing =
pytest
pytest-xdist
Expand Down

0 comments on commit d9e3ab6

Please sign in to comment.