diff --git a/.dockerignore b/.dockerignore index 92d352e6..f99c7853 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,12 +1,12 @@ -.dockerignore -docker/Dockerfile -docker/base -__pycache__ -*.pyc -*.pyo -*.pyd -.Python -pip-log.txt -pip-delete-this-directory.txt -*.log -.git +.dockerignore +docker/Dockerfile +docker/base +__pycache__ +*.pyc +*.pyo +*.pyd +.Python +pip-log.txt +pip-delete-this-directory.txt +*.log +.git diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe943edc..4e5a5650 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,65 +1,65 @@ -name: CI - -on: - push: - branches: - - master - pull_request: - schedule: - # run CI every day even if no PRs/merges occur - - cron: '0 12 * * *' - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v1 - with: - python-version: "3.7" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install pyflakes mypy - - name: Lint - run: | - find ./bin -name '*.py' -exec pyflakes "{}" \; - find ./tests -name '*.py' -exec pyflakes "{}" \; - mypy --strict-optional --config-file mypy.ini bin/deepstate - build: - strategy: - matrix: - env: - - TEST: sanity_check - # - TEST: crash - # - TEST: fixture - # - TEST: klee - # - TEST: lists - # - TEST: oneof - # - TEST: runlen - # - TEST: overflow - # - TEST: primes - # - TEST: takeover - # - TEST: streamingandformatting - # - TEST: boringdisabled - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v1 - with: - python-version: "3.7" - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y build-essential gcc-multilib cmake libffi-dev - python -m pip install --upgrade pip - pip install z3-solver angr nose - pip install git+git://github.com/trailofbits/manticore.git - - name: Build - run: | - mkdir build && cd build - cmake .. - sudo make install - - name: Test - run: | - nosetests tests/test_${{ matrix.env.TEST }}.py +name: CI + +on: + push: + branches: + - master + pull_request: + schedule: + # run CI every day even if no PRs/merges occur + - cron: '0 12 * * *' + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v1 + with: + python-version: "3.7" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pyflakes mypy + - name: Lint + run: | + find ./bin -name '*.py' -exec pyflakes "{}" \; + find ./tests -name '*.py' -exec pyflakes "{}" \; + mypy --strict-optional --config-file mypy.ini bin/deepstate + build: + strategy: + matrix: + env: + - TEST: sanity_check + # - TEST: crash + # - TEST: fixture + # - TEST: klee + # - TEST: lists + # - TEST: oneof + # - TEST: runlen + # - TEST: overflow + # - TEST: primes + # - TEST: takeover + # - TEST: streamingandformatting + # - TEST: boringdisabled + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v1 + with: + python-version: "3.7" + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential gcc-multilib cmake libffi-dev + python -m pip install --upgrade pip + pip install z3-solver angr nose + pip install git+git://github.com/trailofbits/manticore.git + - name: Build + run: | + mkdir build && cd build + cmake .. + sudo make install + - name: Test + run: | + nosetests tests/test_${{ matrix.env.TEST }}.py diff --git a/.gitignore b/.gitignore index 11ad2d2f..f1514638 100644 --- a/.gitignore +++ b/.gitignore @@ -1,153 +1,153 @@ - -# Created by https://www.gitignore.io/api/c++,cmake,python - -### C++ ### -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - -### CMake ### -CMakeCache.txt -CMakeFiles -CMakeScripts -Testing -Makefile -cmake_install.cmake -install_manifest.txt -compile_commands.json -CTestTestfile.cmake -build - -### Python ### -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -.pytest_cache/ -nosetests.xml -coverage.xml -*.cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule.* - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ - -# DeepState-specific ignores -deepstate.out -out/ -mcore_*/ - -# End of https://www.gitignore.io/api/c++,cmake,python + +# Created by https://www.gitignore.io/api/c++,cmake,python + +### C++ ### +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +### CMake ### +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +build + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +# DeepState-specific ignores +deepstate.out +out/ +mcore_*/ + +# End of https://www.gitignore.io/api/c++,cmake,python diff --git a/CMakeLists.txt b/CMakeLists.txt index f91fb5e8..9ebf43e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,315 +1,315 @@ -# Copyright (c) 2019 Trail of Bits, Inc. -# -# 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. - -project(deepstate) -cmake_minimum_required(VERSION 2.8) - -# --- options -option(DEEPSTATE_LIBFUZZER, OFF) -if(DEFINED ENV{DEEPSTATE_LIBFUZZER}) - set(DEEPSTATE_LIBFUZZER ON) -endif() - -option(DEEPSTATE_HONGGFUZZ, OFF) -if(DEFINED ENV{DEEPSTATE_HONGGFUZZ}) - set(DEEPSTATE_HONGGFUZZ ON) -endif() - -option(DEEPSTATE_AFL, OFF) -if(DEFINED ENV{DEEPSTATE_AFL}) - set(DEEPSTATE_AFL ON) -endif() - -option(DEEPSTATE_ANGORA, OFF) -if(DEFINED ENV{DEEPSTATE_ANGORA}) - set(DEEPSTATE_ANGORA ON) -endif() - -option(DEEPSTATE_NOSTATIC, OFF) -if(DEFINED ENV{DEEPSTATE_NOSTATIC}) - set(DEEPSTATE_NOSTATIC ON) -endif() - -option(EXAMPLES_ONLY, OFF) -if(DEFINED ENV{EXAMPLES_ONLY}) - set(EXAMPLES_ONLY ON) -endif() - -# --- compilers -if (DEEPSTATE_LIBFUZZER) - if(NOT DEFINED CMAKE_C_COMPILER) - set(CMAKE_C_COMPILER clang) - endif() - - if(NOT DEFINED CMAKE_CXX_COMPILER) - set(CMAKE_CXX_COMPILER clang++) - endif() - - if (NOT "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" AND NOT "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang") - message(FATAL_ERROR "DeepState's libFuzzer mode requires the Clang C compiler.") - endif() - - if (NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" AND NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") - message(FATAL_ERROR "DeepState's libFuzzer mode requires the Clang C++ compiler.") - endif() -endif() - -if (DEEPSTATE_HONGGFUZZ) - string(FIND "${CMAKE_C_COMPILER}" "hfuzz-clang" _hfuzz_found) - if(_hfuzz_found EQUAL -1) - message(FATAL_ERROR "DeepState's HonggFuzz mode requires the hfuzz-clang C compiler.") - endif() - - string(FIND "${CMAKE_CXX_COMPILER}" "hfuzz-clang++" _hfuzz_found) - if(_hfuzz_found EQUAL -1) - message(FATAL_ERROR "DeepState's HonggFuzz mode requires the hfuzz-clang++ C++ compiler.") - endif() -endif() - -if (DEEPSTATE_AFL) - string(REGEX MATCH ".*(afl-gcc|afl-clang|afl-clang-fast)" _afl_found "${CMAKE_C_COMPILER}") - if(NOT _afl_found) - message(FATAL_ERROR "DeepState's AFL mode requires the afl-gcc or afl-clang C compiler.") - endif() - - string(REGEX MATCH ".*(afl-g\\+\\+|afl-clang\\+\\+|afl-clang-fast\\+\\+)" _afl_found "${CMAKE_CXX_COMPILER}") - if(NOT _afl_found) - message(FATAL_ERROR "DeepState's AFL mode requires the afl-g++ or afl-clang++ C++ compiler.") - endif() -endif() - -if (DEEPSTATE_ANGORA) - string(FIND "${CMAKE_C_COMPILER}" "angora-clang" _angora_found) - if(_angora_found EQUAL -1) - message(FATAL_ERROR "DeepState's Angora mode requires the angora-clang C compiler.") - elseif(CMAKE_C_COMPILER_VERSION VERSION_LESS "4.0.0" OR CMAKE_C_COMPILER_VERSION VERSION_GREATER "7.1.0") - message("X " ${CMAKE_C_COMPILER}) - message(FATAL_ERROR "DeepState's Angora mode requires the main compiler to be clang 4.0.0-7.1.0\n" - "export PATH to \"$ANGORA_HOME/clang+llvm/bin:$PATH\"") - endif() - - string(FIND "${CMAKE_CXX_COMPILER}" "angora-clang++" _angora_found) - if(_angora_found EQUAL -1) - message(FATAL_ERROR "DeepState's Angora mode requires the angora-clang++ C++ compiler.") - elseif(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.0.0" OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "7.1.0") - message(FATAL_ERROR "DeepState's Angora mode requires the main compiler to be clang++ 4.0.0-7.1.0\n" - "export PATH to \"$ANGORA_HOME/clang+llvm/bin:$PATH\"") - endif() -endif() - -# --- settings and flags -enable_language(C) -enable_language(CXX) - -set(CMAKE_POSITION_INDEPENDENT_CODE ON) - -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Release) -endif() - -if (WIN32) - set(DEEPSTATE_PLATFORM_LIB src/lib/DeepState_Win32.c) -endif (WIN32) - -if (UNIX) - set(DEEPSTATE_PLATFORM_LIB src/lib/DeepState_UNIX.c) -endif (UNIX) - -# compile only examples -if(EXAMPLES_ONLY) - add_subdirectory(examples) - return() -endif() - -find_program(PYTHON "python3") - -# Enable the GNU extensions -set(CMAKE_CXX_EXTENSIONS ON) - -# Visual Studio already defaults to c++11 -if (NOT WIN32) - set(CMAKE_C_STANDARD 99) - set(CMAKE_CXX_STANDARD 11) -endif() - -add_library(${PROJECT_NAME} STATIC - ${DEEPSTATE_PLATFORM_LIB} - src/lib/DeepState.c - src/lib/Log.c - src/lib/Option.c - src/lib/Stream.c -) - -add_library(${PROJECT_NAME}32 STATIC - ${DEEPSTATE_PLATFORM_LIB} - src/lib/DeepState.c - src/lib/Log.c - src/lib/Option.c - src/lib/Stream.c -) - -target_compile_options(${PROJECT_NAME} PUBLIC -mno-avx) - -target_compile_options(${PROJECT_NAME}32 PUBLIC -m32 -g3 -mno-avx) - -if (NOT APPLE OR DEEPSTATE_NOSTATIC) - target_link_libraries(${PROJECT_NAME} -static "-Wl,--allow-multiple-definition,--no-export-dynamic") - target_link_libraries(${PROJECT_NAME}32 -static "-Wl,--allow-multiple-definition,--no-export-dynamic") -endif() - -target_include_directories(${PROJECT_NAME} - PUBLIC SYSTEM "${CMAKE_SOURCE_DIR}/src/include" -) - -target_include_directories(${PROJECT_NAME}32 - PUBLIC SYSTEM "${CMAKE_SOURCE_DIR}/src/include" -) - -# Install the library -install( - DIRECTORY "${CMAKE_SOURCE_DIR}/src/include/deepstate" - DESTINATION include -) - -# Install the library -install( - TARGETS ${PROJECT_NAME} ${PROJECT_NAME}32 - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib -) - -# --- build with fuzzer -if (DEEPSTATE_LIBFUZZER) - if(CMAKE_CXX_COMPILE_VERSION LESS 6.0) - message(WARNING "libFuzzer is not built-in for your Clang version. If fails, recompile with clang-6.0 or above") - endif() - - add_library(${PROJECT_NAME}_LF STATIC - ${DEEPSTATE_PLATFORM_LIB} - src/lib/DeepState.c - src/lib/Log.c - src/lib/Option.c - src/lib/Stream.c - ) - - target_compile_options(${PROJECT_NAME}_LF PUBLIC -DLIBFUZZER -mno-avx -fsanitize=fuzzer-no-link,undefined) - - target_include_directories(${PROJECT_NAME}_LF - PUBLIC SYSTEM "${CMAKE_SOURCE_DIR}/src/include" - ) - - install( - TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_LF - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - ) -endif() - -if (DEEPSTATE_HONGGFUZZ) - add_library(${PROJECT_NAME}_HFUZZ STATIC - ${DEEPSTATE_PLATFORM_LIB} - src/lib/DeepState.c - src/lib/Log.c - src/lib/Option.c - src/lib/Stream.c - ) - - target_compile_options(${PROJECT_NAME}_HFUZZ PUBLIC) - - target_include_directories(${PROJECT_NAME}_HFUZZ - PUBLIC SYSTEM "${CMAKE_SOURCE_DIR}/src/include" - ) - - install( - TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_HFUZZ - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - ) -endif() - -if (DEEPSTATE_AFL) - add_library(${PROJECT_NAME}_AFL STATIC - ${DEEPSTATE_PLATFORM_LIB} - src/lib/DeepState.c - src/lib/Log.c - src/lib/Option.c - src/lib/Stream.c - ) - - target_compile_options(${PROJECT_NAME}_AFL PUBLIC -mno-avx) - - target_include_directories(${PROJECT_NAME}_AFL - PUBLIC SYSTEM "${CMAKE_SOURCE_DIR}/src/include" - ) - - install( - TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_AFL - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - ) -endif() - -if (DEEPSTATE_ANGORA) - if(DEFINED ENV{USE_TRACK}) - set(PROJECT_NAME ${PROJECT_NAME}_taint) - else() - set(PROJECT_NAME ${PROJECT_NAME}_fast) - endif() - - add_library(${PROJECT_NAME} STATIC - ${DEEPSTATE_PLATFORM_LIB} - src/lib/DeepState.c - src/lib/Log.c - src/lib/Option.c - src/lib/Stream.c - ) - - target_compile_options(${PROJECT_NAME} PUBLIC -mno-avx) - - target_include_directories(${PROJECT_NAME} - PUBLIC SYSTEM "${CMAKE_SOURCE_DIR}/src/include" - ) - - install( - TARGETS ${PROJECT_NAME} - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - ) -endif() - -set(SETUP_PY_IN "${CMAKE_SOURCE_DIR}/bin/setup.py.in") -set(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py") -configure_file(${SETUP_PY_IN} ${SETUP_PY}) - -set(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/timestamp") -add_custom_command( - OUTPUT ${OUTPUT} - COMMAND ${PYTHON} ${SETUP_PY} build - COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT} -) - -# Install the Manticore harness. -add_custom_target(target ALL DEPENDS ${OUTPUT}) - -# Install DeepState via PIP. -if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - #if an install prefix is not set, assume a global install - install(CODE "execute_process(COMMAND ${PYTHON} ${SETUP_PY} install)") -else() - # and install prefix is set; assume a user install - # the "prefix=" is intentional - install(CODE "execute_process(COMMAND ${PYTHON} ${SETUP_PY} install --user --prefix=)") -endif() - -add_subdirectory(examples) +# Copyright (c) 2019 Trail of Bits, Inc. +# +# 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. + +project(deepstate) +cmake_minimum_required(VERSION 2.8) + +# --- options +option(DEEPSTATE_LIBFUZZER, OFF) +if(DEFINED ENV{DEEPSTATE_LIBFUZZER}) + set(DEEPSTATE_LIBFUZZER ON) +endif() + +option(DEEPSTATE_HONGGFUZZ, OFF) +if(DEFINED ENV{DEEPSTATE_HONGGFUZZ}) + set(DEEPSTATE_HONGGFUZZ ON) +endif() + +option(DEEPSTATE_AFL, OFF) +if(DEFINED ENV{DEEPSTATE_AFL}) + set(DEEPSTATE_AFL ON) +endif() + +option(DEEPSTATE_ANGORA, OFF) +if(DEFINED ENV{DEEPSTATE_ANGORA}) + set(DEEPSTATE_ANGORA ON) +endif() + +option(DEEPSTATE_NOSTATIC, OFF) +if(DEFINED ENV{DEEPSTATE_NOSTATIC}) + set(DEEPSTATE_NOSTATIC ON) +endif() + +option(EXAMPLES_ONLY, OFF) +if(DEFINED ENV{EXAMPLES_ONLY}) + set(EXAMPLES_ONLY ON) +endif() + +# --- compilers +if (DEEPSTATE_LIBFUZZER) + if(NOT DEFINED CMAKE_C_COMPILER) + set(CMAKE_C_COMPILER clang) + endif() + + if(NOT DEFINED CMAKE_CXX_COMPILER) + set(CMAKE_CXX_COMPILER clang++) + endif() + + if (NOT "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" AND NOT "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang") + message(FATAL_ERROR "DeepState's libFuzzer mode requires the Clang C compiler.") + endif() + + if (NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" AND NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") + message(FATAL_ERROR "DeepState's libFuzzer mode requires the Clang C++ compiler.") + endif() +endif() + +if (DEEPSTATE_HONGGFUZZ) + string(FIND "${CMAKE_C_COMPILER}" "hfuzz-clang" _hfuzz_found) + if(_hfuzz_found EQUAL -1) + message(FATAL_ERROR "DeepState's HonggFuzz mode requires the hfuzz-clang C compiler.") + endif() + + string(FIND "${CMAKE_CXX_COMPILER}" "hfuzz-clang++" _hfuzz_found) + if(_hfuzz_found EQUAL -1) + message(FATAL_ERROR "DeepState's HonggFuzz mode requires the hfuzz-clang++ C++ compiler.") + endif() +endif() + +if (DEEPSTATE_AFL) + string(REGEX MATCH ".*(afl-gcc|afl-clang|afl-clang-fast)" _afl_found "${CMAKE_C_COMPILER}") + if(NOT _afl_found) + message(FATAL_ERROR "DeepState's AFL mode requires the afl-gcc or afl-clang C compiler.") + endif() + + string(REGEX MATCH ".*(afl-g\\+\\+|afl-clang\\+\\+|afl-clang-fast\\+\\+)" _afl_found "${CMAKE_CXX_COMPILER}") + if(NOT _afl_found) + message(FATAL_ERROR "DeepState's AFL mode requires the afl-g++ or afl-clang++ C++ compiler.") + endif() +endif() + +if (DEEPSTATE_ANGORA) + string(FIND "${CMAKE_C_COMPILER}" "angora-clang" _angora_found) + if(_angora_found EQUAL -1) + message(FATAL_ERROR "DeepState's Angora mode requires the angora-clang C compiler.") + elseif(CMAKE_C_COMPILER_VERSION VERSION_LESS "4.0.0" OR CMAKE_C_COMPILER_VERSION VERSION_GREATER "7.1.0") + message("X " ${CMAKE_C_COMPILER}) + message(FATAL_ERROR "DeepState's Angora mode requires the main compiler to be clang 4.0.0-7.1.0\n" + "export PATH to \"$ANGORA_HOME/clang+llvm/bin:$PATH\"") + endif() + + string(FIND "${CMAKE_CXX_COMPILER}" "angora-clang++" _angora_found) + if(_angora_found EQUAL -1) + message(FATAL_ERROR "DeepState's Angora mode requires the angora-clang++ C++ compiler.") + elseif(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.0.0" OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "7.1.0") + message(FATAL_ERROR "DeepState's Angora mode requires the main compiler to be clang++ 4.0.0-7.1.0\n" + "export PATH to \"$ANGORA_HOME/clang+llvm/bin:$PATH\"") + endif() +endif() + +# --- settings and flags +enable_language(C) +enable_language(CXX) + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +if (WIN32) + set(DEEPSTATE_PLATFORM_LIB src/lib/DeepState_Win32.c) +endif (WIN32) + +if (UNIX) + set(DEEPSTATE_PLATFORM_LIB src/lib/DeepState_UNIX.c) +endif (UNIX) + +# compile only examples +if(EXAMPLES_ONLY) + add_subdirectory(examples) + return() +endif() + +find_program(PYTHON "python3") + +# Enable the GNU extensions +set(CMAKE_CXX_EXTENSIONS ON) + +# Visual Studio already defaults to c++11 +if (NOT WIN32) + set(CMAKE_C_STANDARD 99) + set(CMAKE_CXX_STANDARD 11) +endif() + +add_library(${PROJECT_NAME} STATIC + ${DEEPSTATE_PLATFORM_LIB} + src/lib/DeepState.c + src/lib/Log.c + src/lib/Option.c + src/lib/Stream.c +) + +add_library(${PROJECT_NAME}32 STATIC + ${DEEPSTATE_PLATFORM_LIB} + src/lib/DeepState.c + src/lib/Log.c + src/lib/Option.c + src/lib/Stream.c +) + +target_compile_options(${PROJECT_NAME} PUBLIC -mno-avx) + +target_compile_options(${PROJECT_NAME}32 PUBLIC -m32 -g3 -mno-avx) + +if (NOT APPLE OR DEEPSTATE_NOSTATIC) + target_link_libraries(${PROJECT_NAME} -static "-Wl,--allow-multiple-definition,--no-export-dynamic") + target_link_libraries(${PROJECT_NAME}32 -static "-Wl,--allow-multiple-definition,--no-export-dynamic") +endif() + +target_include_directories(${PROJECT_NAME} + PUBLIC SYSTEM "${CMAKE_SOURCE_DIR}/src/include" +) + +target_include_directories(${PROJECT_NAME}32 + PUBLIC SYSTEM "${CMAKE_SOURCE_DIR}/src/include" +) + +# Install the library +install( + DIRECTORY "${CMAKE_SOURCE_DIR}/src/include/deepstate" + DESTINATION include +) + +# Install the library +install( + TARGETS ${PROJECT_NAME} ${PROJECT_NAME}32 + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) + +# --- build with fuzzer +if (DEEPSTATE_LIBFUZZER) + if(CMAKE_CXX_COMPILE_VERSION LESS 6.0) + message(WARNING "libFuzzer is not built-in for your Clang version. If fails, recompile with clang-6.0 or above") + endif() + + add_library(${PROJECT_NAME}_LF STATIC + ${DEEPSTATE_PLATFORM_LIB} + src/lib/DeepState.c + src/lib/Log.c + src/lib/Option.c + src/lib/Stream.c + ) + + target_compile_options(${PROJECT_NAME}_LF PUBLIC -DLIBFUZZER -mno-avx -fsanitize=fuzzer-no-link,undefined) + + target_include_directories(${PROJECT_NAME}_LF + PUBLIC SYSTEM "${CMAKE_SOURCE_DIR}/src/include" + ) + + install( + TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_LF + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + ) +endif() + +if (DEEPSTATE_HONGGFUZZ) + add_library(${PROJECT_NAME}_HFUZZ STATIC + ${DEEPSTATE_PLATFORM_LIB} + src/lib/DeepState.c + src/lib/Log.c + src/lib/Option.c + src/lib/Stream.c + ) + + target_compile_options(${PROJECT_NAME}_HFUZZ PUBLIC) + + target_include_directories(${PROJECT_NAME}_HFUZZ + PUBLIC SYSTEM "${CMAKE_SOURCE_DIR}/src/include" + ) + + install( + TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_HFUZZ + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + ) +endif() + +if (DEEPSTATE_AFL) + add_library(${PROJECT_NAME}_AFL STATIC + ${DEEPSTATE_PLATFORM_LIB} + src/lib/DeepState.c + src/lib/Log.c + src/lib/Option.c + src/lib/Stream.c + ) + + target_compile_options(${PROJECT_NAME}_AFL PUBLIC -mno-avx) + + target_include_directories(${PROJECT_NAME}_AFL + PUBLIC SYSTEM "${CMAKE_SOURCE_DIR}/src/include" + ) + + install( + TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_AFL + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + ) +endif() + +if (DEEPSTATE_ANGORA) + if(DEFINED ENV{USE_TRACK}) + set(PROJECT_NAME ${PROJECT_NAME}_taint) + else() + set(PROJECT_NAME ${PROJECT_NAME}_fast) + endif() + + add_library(${PROJECT_NAME} STATIC + ${DEEPSTATE_PLATFORM_LIB} + src/lib/DeepState.c + src/lib/Log.c + src/lib/Option.c + src/lib/Stream.c + ) + + target_compile_options(${PROJECT_NAME} PUBLIC -mno-avx) + + target_include_directories(${PROJECT_NAME} + PUBLIC SYSTEM "${CMAKE_SOURCE_DIR}/src/include" + ) + + install( + TARGETS ${PROJECT_NAME} + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + ) +endif() + +set(SETUP_PY_IN "${CMAKE_SOURCE_DIR}/bin/setup.py.in") +set(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py") +configure_file(${SETUP_PY_IN} ${SETUP_PY}) + +set(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/timestamp") +add_custom_command( + OUTPUT ${OUTPUT} + COMMAND ${PYTHON} ${SETUP_PY} build + COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT} +) + +# Install the Manticore harness. +add_custom_target(target ALL DEPENDS ${OUTPUT}) + +# Install DeepState via PIP. +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + #if an install prefix is not set, assume a global install + install(CODE "execute_process(COMMAND ${PYTHON} ${SETUP_PY} install)") +else() + # and install prefix is set; assume a user install + # the "prefix=" is intentional + install(CODE "execute_process(COMMAND ${PYTHON} ${SETUP_PY} install --user --prefix=)") +endif() + +add_subdirectory(examples) diff --git a/CODEOWNERS b/CODEOWNERS index 8b4c9086..b98db429 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @pgoodman +* @pgoodman diff --git a/LICENSE b/LICENSE index 665789a2..a36d7c91 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,201 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2019 Trail of Bits, Inc. - - 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. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 Trail of Bits, Inc. + + 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. diff --git a/README.md b/README.md index dbae22d8..3157eba0 100644 --- a/README.md +++ b/README.md @@ -1,340 +1,340 @@ -# DeepState - -[![Slack Chat](http://slack.empirehacking.nyc/badge.svg)](https://slack.empirehacking.nyc/) - -DeepState is a framework that provides C and C++ developers with a common interface to various symbolic execution and fuzzing engines. Users can write one test harness using a Google Test-like API, then execute it using multiple backends without having to learn the complexities of the underlying engines. It supports writing unit tests and API sequence tests, as well as automatic test generation. Read more about the goals and design of DeepState in our [paper](https://agroce.github.io/bar18.pdf). - -The -[2018 IEEE Cybersecurity Development Conference](https://secdev.ieee.org/2018/home) -included a -[full tutorial](https://github.com/trailofbits/publications/tree/master/workshops/DeepState:%20Bringing%20vulnerability%20detection%20tools%20into%20the%20development%20lifecycle%20-%20SecDev%202018) -on effective use of DeepState. - - -Table of Contents -================= - * [DeepState in a Nutshell](#deepstate-in-a-nutshell) - * [Articles describing DeepState](#articles-describing-deepstate) - * [Overview of Features](#overview-of-features) - * [Build'n'run](#buildnrun) - * [Supported Platforms](#supported-platforms) - * [Dependencies](#dependencies) - * [Building on Ubuntu 18.04 (Bionic)](#building-on-ubuntu-1804-bionic) - * [Building on Windows 10](#building-on-windows-10) - * [Installing](#installing) - * [Installation testing](#installation-testing) - * [Docker](#docker) - * [Documentation](#documentation) - * [Contributing](#contributing) - * [Trophy case](#trophy-case) - * [License](#license) - - -## DeepState in a Nutshell - -If you want to jump right in, or are having trouble with building DeepState, you can just use a Docker that is pre-built and has compiled versions of several of the easiest examples of DeepState in use: - -```shell -docker pull agroce/deepstate_examples_aflpp -docker run -it agroce/deepstate_examples_aflpp -``` - -Then within the DeepState docker container, go to an example: -```shell -cd ~/examples/fuzz_tcas -deepstate-afl ./TCAS_AFL -o fuzz_afl --fuzzer_out --timeout 120 -./TCAS_cov --input_test_files_dir fuzz_afl/the_fuzzer/queue/ -llvm-cov-9 gcov TCAS_driver.cpp -b -``` - -This runs the AFL++ fuzzer on the TCAS code (https://en.wikipedia.org/wiki/Traffic_collision_avoidance_system), a long-used example program in software testing. After two minutes of fuzzing, we run a version of the test driver that collects code coverage, and see how much of the code AFL has managed to cover in two minutes. - -NOTE 1: The above docker is built using AFL++ instead of AFL for "AFL" fuzzing. You can use agroce/deepstate_examples instead if for some reason you prefer "classic" AFL. - -NOTE 2: You may need to modify `/proc/sys/kernel/core_pattern` on your host for AFL to run properly, e.g.: - -```shell -echo core | sudo tee /proc/sys/kernel/core_pattern -``` - -Finally, we can look at the failing tests AFL produces: - -```shell -./TCAS --input_test_files_dir fuzz_afl/the_fuzzer/crashes/ --min_log_level=0 -``` - -Inspecting `TCAS_driver.cpp` and `Makefile` will give a good idea of how DeepState can be used. The other examples, including the one in the original DeepState blog post, are similar in structure and usage. - -## Articles describing DeepState - -* [Fuzzing an API with DeepState (Part 1)](https://blog.trailofbits.com/2019/01/22/fuzzing-an-api-with-deepstate-part-1) -* [Fuzzing an API with DeepState (Part 2)](https://blog.trailofbits.com/2019/01/23/fuzzing-an-api-with-deepstate-part-2) -* [Fuzzing Unit Tests with DeepState and Eclipser](https://blog.trailofbits.com/2019/05/31/fuzzing-unit-tests-with-deepstate-and-eclipser) -* [DeepState Now Supports Ensemble Fuzzing](https://blog.trailofbits.com/2019/09/03/deepstate-now-supports-ensemble-fuzzing/) -* [Everything You Ever Wanted To Know About Test-Case Reduction, But Didn’t Know to Ask](https://blog.trailofbits.com/2019/11/11/test-case-reduction/) - -## Overview of Features - -* Tests look like Google Test, but can use symbolic execution/fuzzing to generate data (parameterized unit testing) - * Easier to learn than binary analysis tools/fuzzers, but provides similar functionality -* Already supports Manticore, Angr, libFuzzer, file-based fuzzing with - e.g., AFL or Eclipser; more back-ends likely in future - * Switch test generation tool without re-writing test harness - * Work around show-stopper bugs - * Find out which tool works best for your code under test - * Different tools find different bugs/vulnerabilities - * Fair way to benchmark/bakeoff tools -* Provides test replay for regression [plus effective automatic test case reduction to aid debugging](https://blog.trailofbits.com/2019/11/11/test-case-reduction/) -* Supports API-sequence generation with extensions to Google Test interface - * Concise readable way (OneOf) to say "run one of these blocks of code" - * Same construct supports fixed value set non-determinism - * E.g., writing a POSIX file system tester is pleasant, not painful as in pure Google Test idioms -* Provides high-level strategies for improving symbolic execution/fuzzing effectiveness - * Pumping (novel to DeepState) to pick concrete values when symbolic execution is too expensive - * Automatic decomposition of integer compares to guide coverage-driven fuzzers - * Strong support for automated [swarm testing](https://agroce.github.io/issta12.pdf) - -To put it another way, DeepState sits at the intersection of -*property-based testing*, *traditional unit testing*, *fuzzing*, and -*symbolic execution*. It lets you perform property-based unit testing -using fuzzing or symbolic execution as a back end to generate data, and saves the -results so that what DeepState finds can easily be used in -deterministic settings such as regression testing or CI. - -## Build'n'run - -### Supported Platforms - -DeepState currently targets Linux, Windows, with macOS support in progress -(the fuzzers work fine, but symbolic execution is not well-supported -yet, without a painful cross-compilation process). No current support for ARM64 architecture, - this includes Apple Silicon processors, as there is no support for multilib compilers. - -### Dependencies - -Build: - -- CMake -- GCC and G++ with multilib support -- Python 3.6 (or newer) -- Setuptools - -Runtime: - -- Python 3.6 (or newer) -- Z3 (for the Manticore backend) - -### Building on Ubuntu 18.04 (Bionic) - -First make sure you install [Python 3.6 or greater](https://askubuntu.com/a/865569). Then use this command line to install additional requirements and compile DeepState: - -**Building Deepstate** -```shell -sudo apt update && sudo apt-get install build-essential gcc-multilib g++-multilib cmake python3-setuptools python3-dev libffi-dev z3 -sudo apt-add-repository ppa:sri-csl/formal-methods -sudo apt-get update -sudo apt-get install yices2 -git clone https://github.com/trailofbits/deepstate deepstate -mkdir deepstate/build && cd deepstate/build -cmake ../ -make -sudo make install -``` - -### Changing file permissions -```shell -cd deepstate -sudo chmod -R 755 . -sudo chown -R username:groupname . -``` - -### Installing Dependencies on Ubuntu if issues arise -**CMake:** -```shell -sudo apt install cmake -``` - -**GCC & G++ with multilib support:** -```shell -sudo apt-get install gcc-multilib -sudo apt-get install g++-multilib -``` - -**Installing the latest version of Python via CLI:** -```shell -sudo apt-get update && sudo apt upgrade -sudo apt install wget build-essential libncursesw5-dev libssl-dev \ -libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev libffi-dev zlib1g-dev -sudo add-apt-repository ppa:deadsnakes/ppa -sudo apt install python3.11 -``` - -**Setup Tools:** - -Skip the first command below if pip is already installed. -```shell -sudo apt install python3-pip -pip3 install setuptools -``` - -**Z3:** -```shell -sudo apt install z3 -``` - -### Building on Windows 10 -If you want to compile DeepState on Windows make sure to install MinGW with MSYS2 by following the [installation instructions](https://www.msys2.org/#installation). After the installation is finished, select an environment and launch that version of the environment from the Windows programs menu(if in doubt, choose MINGW64 or UCRT64). Then, use the command below to install all of your environment's dependencies and compile DeepState: -```shell -pacman -Syyu -pacman -S mingw-w64-x86_64-python3 mingw-w64-x86_64-python-setuptools mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-libffi mingw-w64-x86_64-make -pacman -S make git - -git clone https://github.com/trailofbits/deepstate deepstate -mkdir deepstate/build && cd deepstate/build -cmake -G "Unix Makefiles" ../ -make -make install -``` - -NOTE: If you decide to use UCRT64, keep in mind to change `x86_64` to `ucrt-x86_64` in the second pacman command, i.e. `mingw-w64-x86_64-python3` gets replaced with `mingw-w64-ucrt-x86_64-python3`. - -### Installing - -Assuming the DeepState build resides in `$DEEPSTATE`, run the following commands to install the DeepState python package: - -```shell -virtualenv venv -. venv/bin/activate -python $DEEPSTATE/build/setup.py install -``` - -The `virtualenv`-enabled `$PATH` should now include two executables: `deepstate` and `deepstate-angr`. These are _executors_, which are used to run DeepState test binaries with specific backends (automatically installed as Python dependencies). The `deepstate` or `deepstate-manticore` executor uses the Manticore backend while `deepstate-angr` uses angr. They share a common interface where you may specify a number of workers and an output directory for saving backend-generated test cases. - -If you try using Manticore, and it doesn't work, but you definitely have the latest Manticore installed, check the `.travis.yml` file. If that grabs a Manticore other than the master version, you can try using the version of Manticore we use in our CI tests. Sometimes Manticore makes a breaking change, and we are behind for a short time. - - -### Installation testing - -You can check your build using the test binaries that were (by default) built and emitted to `deepstate/build/examples`. For example, to use angr to symbolically execute the `IntegerOverflow` test harness with 4 workers, saving generated test cases in a directory called `out`, you would invoke: - -```shell -deepstate-angr --num_workers 4 --output_test_dir out $DEEPSTATE/build/examples/IntegerOverflow -``` - - The resulting `out` directory should look something like: - - ``` - out -└── IntegerOverflow.cpp - ├── SignedInteger_AdditionOverflow - │   ├── a512f8ffb2c1bb775a9779ec60b699cb.fail - │   └── f1d3ff8443297732862df21dc4e57262.pass - └── SignedInteger_MultiplicationOverflow - ├── 6a1a90442b4d898cb3fac2800fef5baf.fail - └── f1d3ff8443297732862df21dc4e57262.pass -``` - -To run these tests, you can just use the native executable, e.g.: - -```shell -$DEEPSTATE/build/examples/IntegerOverflow --input_test_dir out -``` - -to run all the generated tests, or - -```shell -$DEEPSTATE/build/examples/IntegerOverflow --input_test_files_dir out/IntegerOverflow.cpp/SignedInteger_AdditionOverflow --input_which_test SignedInteger_AdditionOverflow -``` - -to run the tests in one directory (in this case, you want to specify -which test to run, also). You can also run a single test, e.g.: - -```shell -$DEEPSTATE/build/examples/IntegerOverflow --input_test_file out/IntegerOverflow.cpp/SignedInteger_AdditionOverflow/a512f8ffb2c1bb775a9779ec60b699cb.fail--input_which_test SignedInteger_AdditionOverflow -``` - -In the absence of an `--input_which_test` argument, DeepState defaults -to the first-defined test. Run the native executable with the `--help` -argument to see all DeepState options. - - -### Docker - -You can also try out Deepstate with Docker, which is the easiest way -to get all the fuzzers and tools up and running on any system. - -The build may take about 40 minutes, because some fuzzers require us -building huge projects like QEMU or LLVM. - -**Ensure that docker is installed:** - -- Check out the docker website [here](https://docs.docker.com/engine/install/) for installation instructions. - -**Check if docker is installed correctly:** -```bash -docker run hello-world -``` - -**Run these commands to install deepstate via docker:** -```bash -git clone https://github.com/trailofbits/deepstate deepstate -$ cd deepstate -$ docker build -t deepstate-base -f docker/base/Dockerfile docker/base -$ docker build -t deepstate --build-arg make_j=6 -f ./docker/Dockerfile . -$ docker run -it deepstate bash -user@a17bc44fd259:~/deepstate$ cd build/examples -user@a17bc44fd259:~/deepstate/build/examples$ deepstate-angr ./Runlen -user@a17bc44fd259:~/deepstate/build/examples$ mkdir tmp && deepstate-eclipser ./Runlen -o tmp --timeout 30 -user@a17bc44fd259:~/deepstate/build/examples$ cd ../../build_libfuzzer/examples -user@a17bc44fd259:~/deepstate/build_libfuzzer/examples$ ./Runlen_LF -max_total_time=30 -user@a17bc44fd259:~/deepstate/build_libfuzzer/examples$ cd ../../build_afl/examples -user@a17bc44fd259:~/deepstate/build_afl/examples$ mkdir foo && echo x > foo/x && mkdir afl_Runlen2 -user@a17bc44fd259:~/deepstate/build_afl/examples$ $AFL_HOME/afl-fuzz -i foo -o afl_Runlen -- ./Runlen_AFL --input_test_file @@ --no_fork --abort_on_fail -user@a17bc44fd259:~/deepstate/build_afl/examples$ deepstate-afl -o afl_Runlen2 ./Runlen_AFL --fuzzer_out -``` - -**How to use docker without sudo:** -```bash -sudo groupadd docker -sudo gpasswd -a $USER docker -newgrp docker -``` - -**If this error occurs:** -```bash -ERROR: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? -``` -**Run this command:** -```bash -systemctl start docker -``` - -## Documentation - -Check out [docs](/docs) folder: - -* [Basic usage](/docs/basic_usage.md) -* [Writing a test harness](/docs/test_harness.md) -* [Fuzzing](/docs/fuzzing.md) -* [Swarm testing](/docs/swarm_testing.md) - -## External Tools - -DeepState can be used to test R packages written using the popular Rcpp package. The [Rcppdeepstate tool](https://github.com/akhikolla/RcppDeepState) is described in a [paper presented at the 2021 IEEE International Symposium on Software Reliability Engineering](https://agroce.github.io/issre21.pdf). - -## Contributing - -All accepted PRs are awarded bounties by Trail of Bits. Join the #deepstate channel on the [Empire Hacking Slack](https://slack.empirehacking.nyc/) to discuss ongoing development and claim bounties. Check the [good first issue](https://github.com/trailofbits/deepstate/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) label for suggested contributions. - -## Trophy case - -We have not yet applied DeepState to many targets, but it was responsible for finding the following confirmed bugs (serious faults are in bold): - -- https://github.com/Blosc/c-blosc2/issues/93 -- https://github.com/Blosc/c-blosc2/issues/94 -- **https://github.com/Blosc/c-blosc2/issues/95** (bug causing compression engine to return incorrect uncompressed data) **FIXED** -- **https://github.com/FooBarWidget/boyer-moore-horspool/issues/4** (implementation of Turbo version of Boyer-Moore string search can fail to find present string) - -## License - -DeepState is released under [The Apache License 2.0](LICENSE). +# DeepState + +[![Slack Chat](http://slack.empirehacking.nyc/badge.svg)](https://slack.empirehacking.nyc/) + +DeepState is a framework that provides C and C++ developers with a common interface to various symbolic execution and fuzzing engines. Users can write one test harness using a Google Test-like API, then execute it using multiple backends without having to learn the complexities of the underlying engines. It supports writing unit tests and API sequence tests, as well as automatic test generation. Read more about the goals and design of DeepState in our [paper](https://agroce.github.io/bar18.pdf). + +The +[2018 IEEE Cybersecurity Development Conference](https://secdev.ieee.org/2018/home) +included a +[full tutorial](https://github.com/trailofbits/publications/tree/master/workshops/DeepState:%20Bringing%20vulnerability%20detection%20tools%20into%20the%20development%20lifecycle%20-%20SecDev%202018) +on effective use of DeepState. + + +Table of Contents +================= + * [DeepState in a Nutshell](#deepstate-in-a-nutshell) + * [Articles describing DeepState](#articles-describing-deepstate) + * [Overview of Features](#overview-of-features) + * [Build'n'run](#buildnrun) + * [Supported Platforms](#supported-platforms) + * [Dependencies](#dependencies) + * [Building on Ubuntu 18.04 (Bionic)](#building-on-ubuntu-1804-bionic) + * [Building on Windows 10](#building-on-windows-10) + * [Installing](#installing) + * [Installation testing](#installation-testing) + * [Docker](#docker) + * [Documentation](#documentation) + * [Contributing](#contributing) + * [Trophy case](#trophy-case) + * [License](#license) + + +## DeepState in a Nutshell + +If you want to jump right in, or are having trouble with building DeepState, you can just use a Docker that is pre-built and has compiled versions of several of the easiest examples of DeepState in use: + +```shell +docker pull agroce/deepstate_examples_aflpp +docker run -it agroce/deepstate_examples_aflpp +``` + +Then within the DeepState docker container, go to an example: +```shell +cd ~/examples/fuzz_tcas +deepstate-afl ./TCAS_AFL -o fuzz_afl --fuzzer_out --timeout 120 +./TCAS_cov --input_test_files_dir fuzz_afl/the_fuzzer/queue/ +llvm-cov-9 gcov TCAS_driver.cpp -b +``` + +This runs the AFL++ fuzzer on the TCAS code (https://en.wikipedia.org/wiki/Traffic_collision_avoidance_system), a long-used example program in software testing. After two minutes of fuzzing, we run a version of the test driver that collects code coverage, and see how much of the code AFL has managed to cover in two minutes. + +NOTE 1: The above docker is built using AFL++ instead of AFL for "AFL" fuzzing. You can use agroce/deepstate_examples instead if for some reason you prefer "classic" AFL. + +NOTE 2: You may need to modify `/proc/sys/kernel/core_pattern` on your host for AFL to run properly, e.g.: + +```shell +echo core | sudo tee /proc/sys/kernel/core_pattern +``` + +Finally, we can look at the failing tests AFL produces: + +```shell +./TCAS --input_test_files_dir fuzz_afl/the_fuzzer/crashes/ --min_log_level=0 +``` + +Inspecting `TCAS_driver.cpp` and `Makefile` will give a good idea of how DeepState can be used. The other examples, including the one in the original DeepState blog post, are similar in structure and usage. + +## Articles describing DeepState + +* [Fuzzing an API with DeepState (Part 1)](https://blog.trailofbits.com/2019/01/22/fuzzing-an-api-with-deepstate-part-1) +* [Fuzzing an API with DeepState (Part 2)](https://blog.trailofbits.com/2019/01/23/fuzzing-an-api-with-deepstate-part-2) +* [Fuzzing Unit Tests with DeepState and Eclipser](https://blog.trailofbits.com/2019/05/31/fuzzing-unit-tests-with-deepstate-and-eclipser) +* [DeepState Now Supports Ensemble Fuzzing](https://blog.trailofbits.com/2019/09/03/deepstate-now-supports-ensemble-fuzzing/) +* [Everything You Ever Wanted To Know About Test-Case Reduction, But Didn’t Know to Ask](https://blog.trailofbits.com/2019/11/11/test-case-reduction/) + +## Overview of Features + +* Tests look like Google Test, but can use symbolic execution/fuzzing to generate data (parameterized unit testing) + * Easier to learn than binary analysis tools/fuzzers, but provides similar functionality +* Already supports Manticore, Angr, libFuzzer, file-based fuzzing with + e.g., AFL or Eclipser; more back-ends likely in future + * Switch test generation tool without re-writing test harness + * Work around show-stopper bugs + * Find out which tool works best for your code under test + * Different tools find different bugs/vulnerabilities + * Fair way to benchmark/bakeoff tools +* Provides test replay for regression [plus effective automatic test case reduction to aid debugging](https://blog.trailofbits.com/2019/11/11/test-case-reduction/) +* Supports API-sequence generation with extensions to Google Test interface + * Concise readable way (OneOf) to say "run one of these blocks of code" + * Same construct supports fixed value set non-determinism + * E.g., writing a POSIX file system tester is pleasant, not painful as in pure Google Test idioms +* Provides high-level strategies for improving symbolic execution/fuzzing effectiveness + * Pumping (novel to DeepState) to pick concrete values when symbolic execution is too expensive + * Automatic decomposition of integer compares to guide coverage-driven fuzzers + * Strong support for automated [swarm testing](https://agroce.github.io/issta12.pdf) + +To put it another way, DeepState sits at the intersection of +*property-based testing*, *traditional unit testing*, *fuzzing*, and +*symbolic execution*. It lets you perform property-based unit testing +using fuzzing or symbolic execution as a back end to generate data, and saves the +results so that what DeepState finds can easily be used in +deterministic settings such as regression testing or CI. + +## Build'n'run + +### Supported Platforms + +DeepState currently targets Linux, Windows, with macOS support in progress +(the fuzzers work fine, but symbolic execution is not well-supported +yet, without a painful cross-compilation process). No current support for ARM64 architecture, + this includes Apple Silicon processors, as there is no support for multilib compilers. + +### Dependencies + +Build: + +- CMake +- GCC and G++ with multilib support +- Python 3.6 (or newer) +- Setuptools + +Runtime: + +- Python 3.6 (or newer) +- Z3 (for the Manticore backend) + +### Building on Ubuntu 18.04 (Bionic) + +First make sure you install [Python 3.6 or greater](https://askubuntu.com/a/865569). Then use this command line to install additional requirements and compile DeepState: + +**Building Deepstate** +```shell +sudo apt update && sudo apt-get install build-essential gcc-multilib g++-multilib cmake python3-setuptools python3-dev libffi-dev z3 +sudo apt-add-repository ppa:sri-csl/formal-methods +sudo apt-get update +sudo apt-get install yices2 +git clone https://github.com/trailofbits/deepstate deepstate +mkdir deepstate/build && cd deepstate/build +cmake ../ +make +sudo make install +``` + +### Changing file permissions +```shell +cd deepstate +sudo chmod -R 755 . +sudo chown -R username:groupname . +``` + +### Installing Dependencies on Ubuntu if issues arise +**CMake:** +```shell +sudo apt install cmake +``` + +**GCC & G++ with multilib support:** +```shell +sudo apt-get install gcc-multilib +sudo apt-get install g++-multilib +``` + +**Installing the latest version of Python via CLI:** +```shell +sudo apt-get update && sudo apt upgrade +sudo apt install wget build-essential libncursesw5-dev libssl-dev \ +libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev libffi-dev zlib1g-dev +sudo add-apt-repository ppa:deadsnakes/ppa +sudo apt install python3.11 +``` + +**Setup Tools:** + +Skip the first command below if pip is already installed. +```shell +sudo apt install python3-pip +pip3 install setuptools +``` + +**Z3:** +```shell +sudo apt install z3 +``` + +### Building on Windows 10 +If you want to compile DeepState on Windows make sure to install MinGW with MSYS2 by following the [installation instructions](https://www.msys2.org/#installation). After the installation is finished, select an environment and launch that version of the environment from the Windows programs menu(if in doubt, choose MINGW64 or UCRT64). Then, use the command below to install all of your environment's dependencies and compile DeepState: +```shell +pacman -Syyu +pacman -S mingw-w64-x86_64-python3 mingw-w64-x86_64-python-setuptools mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-libffi mingw-w64-x86_64-make +pacman -S make git + +git clone https://github.com/trailofbits/deepstate deepstate +mkdir deepstate/build && cd deepstate/build +cmake -G "Unix Makefiles" ../ +make +make install +``` + +NOTE: If you decide to use UCRT64, keep in mind to change `x86_64` to `ucrt-x86_64` in the second pacman command, i.e. `mingw-w64-x86_64-python3` gets replaced with `mingw-w64-ucrt-x86_64-python3`. + +### Installing + +Assuming the DeepState build resides in `$DEEPSTATE`, run the following commands to install the DeepState python package: + +```shell +virtualenv venv +. venv/bin/activate +python $DEEPSTATE/build/setup.py install +``` + +The `virtualenv`-enabled `$PATH` should now include two executables: `deepstate` and `deepstate-angr`. These are _executors_, which are used to run DeepState test binaries with specific backends (automatically installed as Python dependencies). The `deepstate` or `deepstate-manticore` executor uses the Manticore backend while `deepstate-angr` uses angr. They share a common interface where you may specify a number of workers and an output directory for saving backend-generated test cases. + +If you try using Manticore, and it doesn't work, but you definitely have the latest Manticore installed, check the `.travis.yml` file. If that grabs a Manticore other than the master version, you can try using the version of Manticore we use in our CI tests. Sometimes Manticore makes a breaking change, and we are behind for a short time. + + +### Installation testing + +You can check your build using the test binaries that were (by default) built and emitted to `deepstate/build/examples`. For example, to use angr to symbolically execute the `IntegerOverflow` test harness with 4 workers, saving generated test cases in a directory called `out`, you would invoke: + +```shell +deepstate-angr --num_workers 4 --output_test_dir out $DEEPSTATE/build/examples/IntegerOverflow +``` + + The resulting `out` directory should look something like: + + ``` + out +└── IntegerOverflow.cpp + ├── SignedInteger_AdditionOverflow + │   ├── a512f8ffb2c1bb775a9779ec60b699cb.fail + │   └── f1d3ff8443297732862df21dc4e57262.pass + └── SignedInteger_MultiplicationOverflow + ├── 6a1a90442b4d898cb3fac2800fef5baf.fail + └── f1d3ff8443297732862df21dc4e57262.pass +``` + +To run these tests, you can just use the native executable, e.g.: + +```shell +$DEEPSTATE/build/examples/IntegerOverflow --input_test_dir out +``` + +to run all the generated tests, or + +```shell +$DEEPSTATE/build/examples/IntegerOverflow --input_test_files_dir out/IntegerOverflow.cpp/SignedInteger_AdditionOverflow --input_which_test SignedInteger_AdditionOverflow +``` + +to run the tests in one directory (in this case, you want to specify +which test to run, also). You can also run a single test, e.g.: + +```shell +$DEEPSTATE/build/examples/IntegerOverflow --input_test_file out/IntegerOverflow.cpp/SignedInteger_AdditionOverflow/a512f8ffb2c1bb775a9779ec60b699cb.fail--input_which_test SignedInteger_AdditionOverflow +``` + +In the absence of an `--input_which_test` argument, DeepState defaults +to the first-defined test. Run the native executable with the `--help` +argument to see all DeepState options. + + +### Docker + +You can also try out Deepstate with Docker, which is the easiest way +to get all the fuzzers and tools up and running on any system. + +The build may take about 40 minutes, because some fuzzers require us +building huge projects like QEMU or LLVM. + +**Ensure that docker is installed:** + +- Check out the docker website [here](https://docs.docker.com/engine/install/) for installation instructions. + +**Check if docker is installed correctly:** +```bash +docker run hello-world +``` + +**Run these commands to install deepstate via docker:** +```bash +git clone https://github.com/trailofbits/deepstate deepstate +$ cd deepstate +$ docker build -t deepstate-base -f docker/base/Dockerfile docker/base +$ docker build -t deepstate --build-arg make_j=6 -f ./docker/Dockerfile . +$ docker run -it deepstate bash +user@a17bc44fd259:~/deepstate$ cd build/examples +user@a17bc44fd259:~/deepstate/build/examples$ deepstate-angr ./Runlen +user@a17bc44fd259:~/deepstate/build/examples$ mkdir tmp && deepstate-eclipser ./Runlen -o tmp --timeout 30 +user@a17bc44fd259:~/deepstate/build/examples$ cd ../../build_libfuzzer/examples +user@a17bc44fd259:~/deepstate/build_libfuzzer/examples$ ./Runlen_LF -max_total_time=30 +user@a17bc44fd259:~/deepstate/build_libfuzzer/examples$ cd ../../build_afl/examples +user@a17bc44fd259:~/deepstate/build_afl/examples$ mkdir foo && echo x > foo/x && mkdir afl_Runlen2 +user@a17bc44fd259:~/deepstate/build_afl/examples$ $AFL_HOME/afl-fuzz -i foo -o afl_Runlen -- ./Runlen_AFL --input_test_file @@ --no_fork --abort_on_fail +user@a17bc44fd259:~/deepstate/build_afl/examples$ deepstate-afl -o afl_Runlen2 ./Runlen_AFL --fuzzer_out +``` + +**How to use docker without sudo:** +```bash +sudo groupadd docker +sudo gpasswd -a $USER docker +newgrp docker +``` + +**If this error occurs:** +```bash +ERROR: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? +``` +**Run this command:** +```bash +systemctl start docker +``` + +## Documentation + +Check out [docs](/docs) folder: + +* [Basic usage](/docs/basic_usage.md) +* [Writing a test harness](/docs/test_harness.md) +* [Fuzzing](/docs/fuzzing.md) +* [Swarm testing](/docs/swarm_testing.md) + +## External Tools + +DeepState can be used to test R packages written using the popular Rcpp package. The [Rcppdeepstate tool](https://github.com/akhikolla/RcppDeepState) is described in a [paper presented at the 2021 IEEE International Symposium on Software Reliability Engineering](https://agroce.github.io/issre21.pdf). + +## Contributing + +All accepted PRs are awarded bounties by Trail of Bits. Join the #deepstate channel on the [Empire Hacking Slack](https://slack.empirehacking.nyc/) to discuss ongoing development and claim bounties. Check the [good first issue](https://github.com/trailofbits/deepstate/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) label for suggested contributions. + +## Trophy case + +We have not yet applied DeepState to many targets, but it was responsible for finding the following confirmed bugs (serious faults are in bold): + +- https://github.com/Blosc/c-blosc2/issues/93 +- https://github.com/Blosc/c-blosc2/issues/94 +- **https://github.com/Blosc/c-blosc2/issues/95** (bug causing compression engine to return incorrect uncompressed data) **FIXED** +- **https://github.com/FooBarWidget/boyer-moore-horspool/issues/4** (implementation of Turbo version of Boyer-Moore string search can fail to find present string) + +## License + +DeepState is released under [The Apache License 2.0](LICENSE). diff --git a/Vagrantfile b/Vagrantfile index 108a9996..a44b0fa3 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -1,26 +1,26 @@ -# encoding: utf-8 -# -*- mode: ruby -*- -# vi: set ft=ruby : -VAGRANT_BOX = 'bento/ubuntu-18.10' - -VM_NAME = "deepstate" -GUEST_PATH = "/home/" + VM_NAME - -# main configuration -Vagrant.configure(2) do |config| - config.vm.box = VAGRANT_BOX - config.vm.hostname = VM_NAME - config.vm.provider "virtualbox" do |v| - v.name = VM_NAME - v.memory = 2048 - v.cpus = 4 - end - - config.vm.network "private_network", type: "dhcp" - config.vm.synced_folder ".", GUEST_PATH - - # TODO: allow users to configure a dockerized vagrant build or a local one - # configure scripts to run to provision environment - config.vm.provision "shell", path: "./docker/deps", privileged: true - config.vm.provision "shell", path: "./docker/install", privileged: false -end +# encoding: utf-8 +# -*- mode: ruby -*- +# vi: set ft=ruby : +VAGRANT_BOX = 'bento/ubuntu-18.10' + +VM_NAME = "deepstate" +GUEST_PATH = "/home/" + VM_NAME + +# main configuration +Vagrant.configure(2) do |config| + config.vm.box = VAGRANT_BOX + config.vm.hostname = VM_NAME + config.vm.provider "virtualbox" do |v| + v.name = VM_NAME + v.memory = 2048 + v.cpus = 4 + end + + config.vm.network "private_network", type: "dhcp" + config.vm.synced_folder ".", GUEST_PATH + + # TODO: allow users to configure a dockerized vagrant build or a local one + # configure scripts to run to provision environment + config.vm.provision "shell", path: "./docker/deps", privileged: true + config.vm.provision "shell", path: "./docker/install", privileged: false +end diff --git a/bin/deepstate/__init__.py b/bin/deepstate/__init__.py index 7016b47d..275e7756 100644 --- a/bin/deepstate/__init__.py +++ b/bin/deepstate/__init__.py @@ -1,68 +1,68 @@ -import os -import functools -import logging -from sys import exit - - -class DeepStateLogger(logging.getLoggerClass()): # type: ignore - def __init__(self, name: str) -> None: - logging.Logger.__init__(self, name=name) - self.trace = functools.partial(self.log, 15) # type: ignore - self.external = functools.partial(self.log, 45) # type: ignore - self.fuzz_stats = functools.partial(self.log, 46) # type: ignore - - -logging.basicConfig() -logging.addLevelName(15, "TRACE") -logging.addLevelName(45, "EXTERNAL") -logging.addLevelName(46, "FUZZ_STATS") -logging.setLoggerClass(DeepStateLogger) - -logger = logging.getLogger(__name__) - -LOG_LEVEL_DEBUG = 0 -LOG_LEVEL_TRACE = 1 -LOG_LEVEL_INFO = 2 -LOG_LEVEL_WARNING = 3 -LOG_LEVEL_ERROR = 4 -LOG_LEVEL_EXTERNAL = 5 -LOG_LEVEL_CRITICAL = 6 - -LOG_LEVEL_INT_TO_STR = { - LOG_LEVEL_DEBUG: logging.DEBUG, - LOG_LEVEL_TRACE: logging.getLevelName(15), - LOG_LEVEL_INFO: logging.INFO, - LOG_LEVEL_WARNING: logging.WARNING, - LOG_LEVEL_ERROR: logging.ERROR, - LOG_LEVEL_EXTERNAL: logging.getLevelName(45), - LOG_LEVEL_CRITICAL: logging.CRITICAL -} - -LOG_LEVEL_INT_TO_LOGGER = { - LOG_LEVEL_DEBUG: logger.debug, - LOG_LEVEL_TRACE: logger.trace, # type: ignore - LOG_LEVEL_INFO: logger.info, - LOG_LEVEL_WARNING: logger.warning, - LOG_LEVEL_ERROR: logger.error, - LOG_LEVEL_EXTERNAL: logger.external, # type: ignore - LOG_LEVEL_CRITICAL: logger.critical -} - -log_level_from_env: str = os.environ.get("DEEPSTATE_LOG", "2") -try: - log_level_from_env_int: int = int(log_level_from_env) - logger.setLevel(LOG_LEVEL_INT_TO_STR[log_level_from_env_int]) - logger.info("Setting log level from DEEPSTATE_LOG: %d", log_level_from_env_int) -except ValueError: - print("$DEEPSTATE_LOG contains invalid value `%s`, " - "should be int in 0-6 (debug, trace, info, warning, error, external, critical).", - log_level_from_env) - exit(1) -except KeyError: - print("$DEEPSTATE_LOG is in invalid range, should be in 0-6 " - "(debug, trace, info, warning, error, external, critical).") - exit(1) - -__all__ = ["DeepStateLogger", "LOG_LEVEL_INT_TO_STR", "LOG_LEVEL_INT_TO_LOGGER", "LOG_LEVEL_DEBUG", - "LOG_LEVEL_TRACE", "LOG_LEVEL_INFO", "LOG_LEVEL_WARNING", - "LOG_LEVEL_ERROR", "LOG_LEVEL_EXTERNAL", "LOG_LEVEL_CRITICAL"] +import os +import functools +import logging +from sys import exit + + +class DeepStateLogger(logging.getLoggerClass()): # type: ignore + def __init__(self, name: str) -> None: + logging.Logger.__init__(self, name=name) + self.trace = functools.partial(self.log, 15) # type: ignore + self.external = functools.partial(self.log, 45) # type: ignore + self.fuzz_stats = functools.partial(self.log, 46) # type: ignore + + +logging.basicConfig() +logging.addLevelName(15, "TRACE") +logging.addLevelName(45, "EXTERNAL") +logging.addLevelName(46, "FUZZ_STATS") +logging.setLoggerClass(DeepStateLogger) + +logger = logging.getLogger(__name__) + +LOG_LEVEL_DEBUG = 0 +LOG_LEVEL_TRACE = 1 +LOG_LEVEL_INFO = 2 +LOG_LEVEL_WARNING = 3 +LOG_LEVEL_ERROR = 4 +LOG_LEVEL_EXTERNAL = 5 +LOG_LEVEL_CRITICAL = 6 + +LOG_LEVEL_INT_TO_STR = { + LOG_LEVEL_DEBUG: logging.DEBUG, + LOG_LEVEL_TRACE: logging.getLevelName(15), + LOG_LEVEL_INFO: logging.INFO, + LOG_LEVEL_WARNING: logging.WARNING, + LOG_LEVEL_ERROR: logging.ERROR, + LOG_LEVEL_EXTERNAL: logging.getLevelName(45), + LOG_LEVEL_CRITICAL: logging.CRITICAL +} + +LOG_LEVEL_INT_TO_LOGGER = { + LOG_LEVEL_DEBUG: logger.debug, + LOG_LEVEL_TRACE: logger.trace, # type: ignore + LOG_LEVEL_INFO: logger.info, + LOG_LEVEL_WARNING: logger.warning, + LOG_LEVEL_ERROR: logger.error, + LOG_LEVEL_EXTERNAL: logger.external, # type: ignore + LOG_LEVEL_CRITICAL: logger.critical +} + +log_level_from_env: str = os.environ.get("DEEPSTATE_LOG", "2") +try: + log_level_from_env_int: int = int(log_level_from_env) + logger.setLevel(LOG_LEVEL_INT_TO_STR[log_level_from_env_int]) + logger.info("Setting log level from DEEPSTATE_LOG: %d", log_level_from_env_int) +except ValueError: + print("$DEEPSTATE_LOG contains invalid value `%s`, " + "should be int in 0-6 (debug, trace, info, warning, error, external, critical).", + log_level_from_env) + exit(1) +except KeyError: + print("$DEEPSTATE_LOG is in invalid range, should be in 0-6 " + "(debug, trace, info, warning, error, external, critical).") + exit(1) + +__all__ = ["DeepStateLogger", "LOG_LEVEL_INT_TO_STR", "LOG_LEVEL_INT_TO_LOGGER", "LOG_LEVEL_DEBUG", + "LOG_LEVEL_TRACE", "LOG_LEVEL_INFO", "LOG_LEVEL_WARNING", + "LOG_LEVEL_ERROR", "LOG_LEVEL_EXTERNAL", "LOG_LEVEL_CRITICAL"] diff --git a/bin/deepstate/core/__init__.py b/bin/deepstate/core/__init__.py index 836ab90e..1793ec61 100644 --- a/bin/deepstate/core/__init__.py +++ b/bin/deepstate/core/__init__.py @@ -1,3 +1,3 @@ -from .symex import SymexFrontend, TestInfo -from .fuzz import FuzzerFrontend, FuzzFrontendError +from .symex import SymexFrontend, TestInfo +from .fuzz import FuzzerFrontend, FuzzFrontendError __all__ = ["SymexFrontend", "TestInfo", "FuzzerFrontend", "FuzzFrontendError"] \ No newline at end of file diff --git a/bin/deepstate/core/base.py b/bin/deepstate/core/base.py index 4c3aa5e8..c36b1dd5 100644 --- a/bin/deepstate/core/base.py +++ b/bin/deepstate/core/base.py @@ -1,281 +1,281 @@ -#!/usr/bin/env python3.6 -# Copyright (c) 2019 Trail of Bits, Inc. -# -# 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 logging -import os -import argparse -import configparser - -from typing import Dict, ClassVar, Optional, Union, List, Any, Tuple - -from deepstate import LOG_LEVEL_INT_TO_STR - - -L = logging.getLogger(__name__) - - -class AnalysisBackendError(Exception): - """ - Defines our custom exception class for AnalysisBackend - """ - pass - - -class AnalysisBackend(object): - """ - Defines the root base object to inherit attributes and methods for any frontends that - enable us to build any DeepState-backing executor or auxiliary tool using a builder pattern - for instantiation. - """ - - # name of tool executable, should be implemented by subclass - NAME: ClassVar[str] = '' - - # dict of executable files, should be implemented by subclass - EXECUTABLES: ClassVar[Dict[str,str]] = {} - - # compiler executable - compiler_exe: ClassVar[Optional[str]] = None - - # temporary attribute for argparsing, and should be used to build up object attributes - _ARGS: ClassVar[Optional[argparse.Namespace]] = None - - # temporary attribute for parser instantiation, should be used to check if user parsed args - parser: ClassVar[Optional[argparse.ArgumentParser]] = None - - - def __init__(self): - """ - Create and store variables: - - name (name for pretty printing) - - compiler_exe (compiler executable, optional) - - User must define NAME members in inherited class. - """ - - # in case name supplied as `bin/fuzzer`, strip executable name - self.name: str = self.NAME - if not self.name: - raise AnalysisBackendError("AnalysisBackend.NAME not set") - L.debug("Analysis backend name: %s", self.name) - - self.compiler_exe = self.EXECUTABLES.pop("COMPILER", None) - - # parsed argument attributes - self.binary: Optional[str] = None - self.output_test_dir: str - self.timeout: int = 0 - self.mem_limit: int = 50 - self.min_log_level: int = 2 - - self.compile_test: Optional[str] = None - self.compiler_args: Optional[str] = None - self.out_test_name: str = "out" - - self.no_exit_compile: bool = False - self.which_test: Optional[str] = None - self.target_args: List[Any] = [] - - - @classmethod - def parse_args(cls) -> Optional[argparse.Namespace]: - """ - Base root-level argument parser. After the executors initializes its application-specific arguments, and the frontend - builds up further with analysis-specific arguments, this base parse_args finalizes with all other required args every - executor should consume. - """ - - if cls._ARGS: - L.debug("Returning already-parsed arguments") - return cls._ARGS - - # checks if frontend executor already implements an argparser, since we want to extend on that. - if cls.parser is not None: - parser: argparse.ArgumentParser = cls.parser - else: - parser = argparse.ArgumentParser(description="Use {} as a backend for DeepState".format(cls.NAME)) - - # Compilation/instrumentation support, only if COMPILER is set in EXECUTABLES - # TODO: extends compilation interface for symex engines that "compile" source to - # binary, IR format, or boolean expressions for symbolic VM to reason with - if cls.compiler_exe: - L.debug("Adding compilation support since a compiler was specified") - - # type: ignore - compile_group = parser.add_argument_group("Compilation and Instrumentation") - compile_group.add_argument("--compile_test", type=str, - help="Path to DeepState test source for compilation and instrumentation by analysis tool.") - - # TODO: instead of parsing out arguments, we should consume JSON compilation databases instead - compile_group.add_argument("--compiler_args", type=str, - help="Linker flags (space separated) to include for external libraries.") - - compile_group.add_argument("--out_test_name", type=str, - help=("Set name of generated instrumented binary. Default is `out`. " - "Automatically adds `.frontend_name_lowercase` suffix.")) - - compile_group.add_argument("--no_exit_compile", action="store_true", - help="Continue execution after compiling a harness (set as default if `--config` is set).") - - # Target binary (not required, since user may pass in source for compilation) - parser.add_argument("binary", nargs="?", type=str, - help="Path to the test binary compiled with DeepState to run under analysis tool.") - - # Analysis-related configurations - parser.add_argument( - "-o", "--output_test_dir", type=str, - help="Output directory where tests will be saved. Required. If not empty, will try to resume.") - - parser.add_argument( - "-c", "--config", type=str, - help="Configuration file to be consumed instead of arguments.") - - parser.add_argument( - "-t", "--timeout", default=0, type=int, - help="Time to kill analysis worker processes, in seconds (default is 0 for none).") - - parser.add_argument("--mem_limit", type=int, default=50, - help="Child process memory limit in MiB (default is 50). 0 for unlimited.") - - parser.add_argument( - "--min_log_level", default=2, type=int, - help="Minimum DeepState log level to print (default: 2), 0-6 (debug, trace, info, warning, error, external, critical).") - - # DeepState-related options - exec_group = parser.add_argument_group("DeepState Test Configuration") - exec_group.add_argument( - "--which_test", type=str, - help="DeepState unit test to run (equivalent to `--input_which_test`).") - - exec_group.add_argument( - "--target_args", default=[], nargs='*', - help="Other DeepState flags to pass to harness before execution. Format: `a arg=val` -> `-a --arg1 val`.") - - args = parser.parse_args() - - # from parsed arguments, modify dict copy if configuration is specified - _args: Dict[str, Any] = vars(args) - - # parse target_args - target_args_parsed: List[Tuple[str, Optional[str]]] = [] - for arg in _args['target_args']: - vals = arg.split("=", 1) - key = vals[0] - val = None - if len(vals) == 2: - val = vals[1] - target_args_parsed.append((key, val)) - _args['target_args'] = target_args_parsed - - - # if configuration is specified, parse and replace argument instantiations - if args.config: - _args.update(cls.build_from_config(args.config)) # type: ignore - - # Cleanup: force --no_exit_compile to be on, meaning if user specifies a `[test]` section, - # execution will continue. Delete config as well - _args["no_exit_compile"] = True # type: ignore - del _args["config"] - - # log level fixing - if not os.environ.get("DEEPSTATE_LOG"): - if _args["min_log_level"] < 0 or _args["min_log_level"] > 6: - raise AnalysisBackendError("`--min_log_level` is in invalid range, should be in 0-6 " - "(debug, trace, info, warning, error, external, critical).") - - L.info("Setting log level from --min_log_level: %d", _args["min_log_level"]) - logger = logging.getLogger("deepstate") - logger.setLevel(LOG_LEVEL_INT_TO_STR[_args["min_log_level"]]) - else: - L.debug("Using log level from $DEEPSTATE_LOG.") - - cls._ARGS = args - return cls._ARGS - - - @staticmethod - def build_from_config(config: str, allowed_keys: Optional[List[str]] = None, include_sections: bool = False) -> Union[Dict[str, Dict[str, Any]], Dict[str, Any]]: - """ - Simple auxiliary helper that does safe and correct parsing of DeepState configurations. This can be used - in the following manners: - - * Baked-in usage with AnalysisBackend, allowing us to take input user configurations and initialize attributes for - our frontend executors. - * Used externally as API for reasoning with configurations as part of auxiliary tools or test runners. - - :param config: path to configuration file - :param allowed_keys: contains allowed keys that should be parsed - :param include_sections: if true, parse all sections, and return a Dict[str, Dict[str, Any]] where keys are section names - """ - - context: Dict[str, Dict[str, Any]] = dict() # type: ignore - - # reserved sections are ignored by executors, but can be used by other auxiliary tools - # to reason about with. - reserved_sections: List[str] = [ - "manifest", # contains "metadata" for a configuration - "internal" # write-only by auxiliary tools, and should store anything not used by DeepState - ] - - # define tokens that are allowed for a configuration. This way users will not be able to - # populate an executor with unnecessary attributes that do not contribute to execution. - allowed_sections: List[str] = [ - "compile", # specifies configuration for compiling a test - "test" # configurations for harness execution under analysis tool - ] - - parser = configparser.SafeConfigParser() - parser.read(config) - - for section, kv in parser._sections.items(): # type: ignore - - # if `include_sections` is not set, parse only from allowed_sections - if not include_sections: - if section not in allowed_sections: - continue - elif section in reserved_sections: - continue - - # if `include_sections`, keys are now all section names - if include_sections: - _context = context[section] = dict() - else: - _context = context - - for key, val in kv.items(): - - # check if key should be parsed - if allowed_keys is not None: - if key not in allowed_keys: - continue - - if isinstance(val, list): - _context[key].append(val) - else: - _context[key] = val - - return context # type: ignore - - - def init_from_dict(self, _args: Optional[Dict[str, str]] = None) -> None: - """ - Builder initialization routine used to instantiate the attributes of the frontend object, either from the stored - _ARGS namespace, or manual arguments passed in (not ideal, but useful for ensembler orchestration). - - :param _args: optional dictionary with parsed arguments to set as attributes. - """ - args: Dict[str, str] = vars(self._ARGS) if _args is None else _args - for key, value in args.items(): - setattr(self, key, value) +#!/usr/bin/env python3.6 +# Copyright (c) 2019 Trail of Bits, Inc. +# +# 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 logging +import os +import argparse +import configparser + +from typing import Dict, ClassVar, Optional, Union, List, Any, Tuple + +from deepstate import LOG_LEVEL_INT_TO_STR + + +L = logging.getLogger(__name__) + + +class AnalysisBackendError(Exception): + """ + Defines our custom exception class for AnalysisBackend + """ + pass + + +class AnalysisBackend(object): + """ + Defines the root base object to inherit attributes and methods for any frontends that + enable us to build any DeepState-backing executor or auxiliary tool using a builder pattern + for instantiation. + """ + + # name of tool executable, should be implemented by subclass + NAME: ClassVar[str] = '' + + # dict of executable files, should be implemented by subclass + EXECUTABLES: ClassVar[Dict[str,str]] = {} + + # compiler executable + compiler_exe: ClassVar[Optional[str]] = None + + # temporary attribute for argparsing, and should be used to build up object attributes + _ARGS: ClassVar[Optional[argparse.Namespace]] = None + + # temporary attribute for parser instantiation, should be used to check if user parsed args + parser: ClassVar[Optional[argparse.ArgumentParser]] = None + + + def __init__(self): + """ + Create and store variables: + - name (name for pretty printing) + - compiler_exe (compiler executable, optional) + + User must define NAME members in inherited class. + """ + + # in case name supplied as `bin/fuzzer`, strip executable name + self.name: str = self.NAME + if not self.name: + raise AnalysisBackendError("AnalysisBackend.NAME not set") + L.debug("Analysis backend name: %s", self.name) + + self.compiler_exe = self.EXECUTABLES.pop("COMPILER", None) + + # parsed argument attributes + self.binary: Optional[str] = None + self.output_test_dir: str + self.timeout: int = 0 + self.mem_limit: int = 50 + self.min_log_level: int = 2 + + self.compile_test: Optional[str] = None + self.compiler_args: Optional[str] = None + self.out_test_name: str = "out" + + self.no_exit_compile: bool = False + self.which_test: Optional[str] = None + self.target_args: List[Any] = [] + + + @classmethod + def parse_args(cls) -> Optional[argparse.Namespace]: + """ + Base root-level argument parser. After the executors initializes its application-specific arguments, and the frontend + builds up further with analysis-specific arguments, this base parse_args finalizes with all other required args every + executor should consume. + """ + + if cls._ARGS: + L.debug("Returning already-parsed arguments") + return cls._ARGS + + # checks if frontend executor already implements an argparser, since we want to extend on that. + if cls.parser is not None: + parser: argparse.ArgumentParser = cls.parser + else: + parser = argparse.ArgumentParser(description="Use {} as a backend for DeepState".format(cls.NAME)) + + # Compilation/instrumentation support, only if COMPILER is set in EXECUTABLES + # TODO: extends compilation interface for symex engines that "compile" source to + # binary, IR format, or boolean expressions for symbolic VM to reason with + if cls.compiler_exe: + L.debug("Adding compilation support since a compiler was specified") + + # type: ignore + compile_group = parser.add_argument_group("Compilation and Instrumentation") + compile_group.add_argument("--compile_test", type=str, + help="Path to DeepState test source for compilation and instrumentation by analysis tool.") + + # TODO: instead of parsing out arguments, we should consume JSON compilation databases instead + compile_group.add_argument("--compiler_args", type=str, + help="Linker flags (space separated) to include for external libraries.") + + compile_group.add_argument("--out_test_name", type=str, + help=("Set name of generated instrumented binary. Default is `out`. " + "Automatically adds `.frontend_name_lowercase` suffix.")) + + compile_group.add_argument("--no_exit_compile", action="store_true", + help="Continue execution after compiling a harness (set as default if `--config` is set).") + + # Target binary (not required, since user may pass in source for compilation) + parser.add_argument("binary", nargs="?", type=str, + help="Path to the test binary compiled with DeepState to run under analysis tool.") + + # Analysis-related configurations + parser.add_argument( + "-o", "--output_test_dir", type=str, + help="Output directory where tests will be saved. Required. If not empty, will try to resume.") + + parser.add_argument( + "-c", "--config", type=str, + help="Configuration file to be consumed instead of arguments.") + + parser.add_argument( + "-t", "--timeout", default=0, type=int, + help="Time to kill analysis worker processes, in seconds (default is 0 for none).") + + parser.add_argument("--mem_limit", type=int, default=50, + help="Child process memory limit in MiB (default is 50). 0 for unlimited.") + + parser.add_argument( + "--min_log_level", default=2, type=int, + help="Minimum DeepState log level to print (default: 2), 0-6 (debug, trace, info, warning, error, external, critical).") + + # DeepState-related options + exec_group = parser.add_argument_group("DeepState Test Configuration") + exec_group.add_argument( + "--which_test", type=str, + help="DeepState unit test to run (equivalent to `--input_which_test`).") + + exec_group.add_argument( + "--target_args", default=[], nargs='*', + help="Other DeepState flags to pass to harness before execution. Format: `a arg=val` -> `-a --arg1 val`.") + + args = parser.parse_args() + + # from parsed arguments, modify dict copy if configuration is specified + _args: Dict[str, Any] = vars(args) + + # parse target_args + target_args_parsed: List[Tuple[str, Optional[str]]] = [] + for arg in _args['target_args']: + vals = arg.split("=", 1) + key = vals[0] + val = None + if len(vals) == 2: + val = vals[1] + target_args_parsed.append((key, val)) + _args['target_args'] = target_args_parsed + + + # if configuration is specified, parse and replace argument instantiations + if args.config: + _args.update(cls.build_from_config(args.config)) # type: ignore + + # Cleanup: force --no_exit_compile to be on, meaning if user specifies a `[test]` section, + # execution will continue. Delete config as well + _args["no_exit_compile"] = True # type: ignore + del _args["config"] + + # log level fixing + if not os.environ.get("DEEPSTATE_LOG"): + if _args["min_log_level"] < 0 or _args["min_log_level"] > 6: + raise AnalysisBackendError("`--min_log_level` is in invalid range, should be in 0-6 " + "(debug, trace, info, warning, error, external, critical).") + + L.info("Setting log level from --min_log_level: %d", _args["min_log_level"]) + logger = logging.getLogger("deepstate") + logger.setLevel(LOG_LEVEL_INT_TO_STR[_args["min_log_level"]]) + else: + L.debug("Using log level from $DEEPSTATE_LOG.") + + cls._ARGS = args + return cls._ARGS + + + @staticmethod + def build_from_config(config: str, allowed_keys: Optional[List[str]] = None, include_sections: bool = False) -> Union[Dict[str, Dict[str, Any]], Dict[str, Any]]: + """ + Simple auxiliary helper that does safe and correct parsing of DeepState configurations. This can be used + in the following manners: + + * Baked-in usage with AnalysisBackend, allowing us to take input user configurations and initialize attributes for + our frontend executors. + * Used externally as API for reasoning with configurations as part of auxiliary tools or test runners. + + :param config: path to configuration file + :param allowed_keys: contains allowed keys that should be parsed + :param include_sections: if true, parse all sections, and return a Dict[str, Dict[str, Any]] where keys are section names + """ + + context: Dict[str, Dict[str, Any]] = dict() # type: ignore + + # reserved sections are ignored by executors, but can be used by other auxiliary tools + # to reason about with. + reserved_sections: List[str] = [ + "manifest", # contains "metadata" for a configuration + "internal" # write-only by auxiliary tools, and should store anything not used by DeepState + ] + + # define tokens that are allowed for a configuration. This way users will not be able to + # populate an executor with unnecessary attributes that do not contribute to execution. + allowed_sections: List[str] = [ + "compile", # specifies configuration for compiling a test + "test" # configurations for harness execution under analysis tool + ] + + parser = configparser.SafeConfigParser() + parser.read(config) + + for section, kv in parser._sections.items(): # type: ignore + + # if `include_sections` is not set, parse only from allowed_sections + if not include_sections: + if section not in allowed_sections: + continue + elif section in reserved_sections: + continue + + # if `include_sections`, keys are now all section names + if include_sections: + _context = context[section] = dict() + else: + _context = context + + for key, val in kv.items(): + + # check if key should be parsed + if allowed_keys is not None: + if key not in allowed_keys: + continue + + if isinstance(val, list): + _context[key].append(val) + else: + _context[key] = val + + return context # type: ignore + + + def init_from_dict(self, _args: Optional[Dict[str, str]] = None) -> None: + """ + Builder initialization routine used to instantiate the attributes of the frontend object, either from the stored + _ARGS namespace, or manual arguments passed in (not ideal, but useful for ensembler orchestration). + + :param _args: optional dictionary with parsed arguments to set as attributes. + """ + args: Dict[str, str] = vars(self._ARGS) if _args is None else _args + for key, value in args.items(): + setattr(self, key, value) diff --git a/bin/deepstate/core/fuzz.py b/bin/deepstate/core/fuzz.py index fc0f85ac..856f0885 100644 --- a/bin/deepstate/core/fuzz.py +++ b/bin/deepstate/core/fuzz.py @@ -1,945 +1,945 @@ -#!/usr/bin/env python3.6 -# Copyright (c) 2019 Trail of Bits, Inc. -# -# 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 logging - -import os -import time -import sys -import subprocess -import psutil # type: ignore -import argparse -import shutil -import traceback - -from tempfile import mkdtemp -from time import sleep -from pathlib import Path -from typing import Optional, Dict, List, Any, Tuple - -from deepstate.core.base import AnalysisBackend, AnalysisBackendError - - -L = logging.getLogger(__name__) - - -class FuzzFrontendError(AnalysisBackendError): - """ - Defines our custom exception class for FuzzerFrontend - """ - pass - - -class FuzzerFrontend(AnalysisBackend): - """ - Defines a base front-end object for using DeepState to interact with fuzzers. - """ - - # fuzzer-specific configurations - ENVVAR: str = "PATH" - REQUIRE_SEEDS: bool = False - - # configurations for seed synchronization - PUSH_DIR: str - PULL_DIR: str - CRASH_DIR: str - - def __init__(self) -> None: - """ - Create and store variables: - - fuzzer_exe (fuzzer executable file) - - env (environment variable name) - - search_dirs (directories inside fuzzer home dir where to look for executables) - - require_seeds - - stats (dict that frontend should populate in populate_stats method) - - stats_file (file where to put stats from fuzzer in common format) - - output_file (file where stdout of fuzzer will be redirected) - - proc (handler to fuzzer process) - - fuzzer_return_code (the exit status from the fuzzer process AND the return status of main) - - - push_dir (push testcases from external sources here) - - pull_dir (pull new testcases from this dir) - - crash_dir (crashes will be in this dir) - - Inherits: - - name (name for pretty printing) - - compiler_exe (fuzzer compiler file, optional) - - User must define in inherited fuzzer class: - - NAME str - - EXECUTABLES dict with keys: - FUZZER, COMPILER (if compiling) and any executable it will use - - """ - super(FuzzerFrontend, self).__init__() - - if "FUZZER" not in self.EXECUTABLES: - raise FuzzFrontendError("FuzzerFrontend.EXECUTABLES[\"FUZZER\"] not set.") - self.fuzzer_exe: str = self.EXECUTABLES.pop("FUZZER") - - self.env: Optional[str] = os.environ.get(self.ENVVAR, None) - - self.search_dirs: List[str] = getattr(self, "SEARCH_DIRS", []) - - # flag to ensure fuzzer processes do not persist - self._on: bool = False - - self.proc: subprocess.Popen[bytes] - self.fuzzer_return_code = 0 - self.require_seeds: bool = False - self.stats_file: str = "deepstate-stats.txt" - self.output_file: str = "fuzzer-output.txt" - - # same as AFL's (https://github.com/google/AFL/blob/master/docs/status_screen.txt) - self.stats: Dict[str, Optional[str]] = { - # guaranteed - "unique_crashes": None, - "fuzzer_pid": None, - "start_time": None, - "sync_dir_size": None, - - # not guaranteed - "execs_done": None, - "execs_per_sec": None, - "last_update": None, - "cycles_done": None, - "paths_total": None, - "paths_favored": None, - "paths_found": None, - "paths_imported": None, - "max_depth": None, - "cur_path": None, - "pending_favs": None, - "pending_total": None, - "variable_paths": None, - "stability": None, - "bitmap_cvg": None, - "unique_hangs": None, - "last_path": None, - "last_crash": None, - "last_hang": None, - "execs_since_crash": None, - "slowest_exec_ms": None, - "peak_rss_mb": None, - } - - # parsed argument attributes - self.input_seeds: Optional[str] = None - self.max_input_size: int = 8192 - self.dictionary: Optional[str] = None - self.exec_timeout: Optional[int] = None - self.blackbox: Optional[bool] = None - self.fuzzer_args: List[Any] = [] - self.fuzzer_out: bool = False - - self.sync_cycle: int = 5 - self.sync_out: bool = True - self.sync_dir: Optional[str] = None - - self.push_dir: str = '' - self.pull_dir: str = '' - self.crash_dir: str = '' - - self.home_path: Optional[str] = None - - - def __repr__(self) -> str: - return "{}".format(self.__class__.__name__) - - - @classmethod - def parse_args(cls) -> Optional[argparse.Namespace]: - """ - Default base argument parser for DeepState frontends. Comprises of default arguments all - frontends must implement to maintain consistency in executables. Users can inherit this - method to extend and add own arguments or override for outstanding deviations in fuzzer CLIs. - - Arguments provided by this method and usable in fuzzers' functions. - Guaranteed arguments (have default value): - - output_test_dir (default: out) - - mem_limit (default: 50MiB) - - max_input_size (default: 8192B) - - fuzzer_args (default: {}) - - blackbox (default: False) - - Optional arguments (may be None): - - input_seeds - - dictionary - - exec_timeout - - Arguments that should not be used by child class: - - timeout (default: 0) - - num_workers (default: 1) - - target_args (default: {}) - """ - - L.debug("Parsing arguments with internal base class routine") - - if cls._ARGS: - L.debug("Returning already-parsed arguments") - return cls._ARGS - - # use existing argparser if defined in fuzzer object, or initialize new one, both with default arguments - if cls.parser: - L.debug("Using previously initialized parser") - parser = cls.parser - else: - L.debug("Instantiating new ArgumentParser") - parser = argparse.ArgumentParser(description="Use {} fuzzer as a backend for DeepState".format(str(cls))) - - # Fuzzer execution options - parser.add_argument( - "-i", "--input_seeds", type=str, - help="Directory with seed inputs for fuzzers to queue and mutate.") - - parser.add_argument( - "-s", "--max_input_size", type=int, default=8192, - help="Maximum input size for input generator in bytes (default is 8192). 0 for unlimited.") - - parser.add_argument("--dictionary", type=str, - help="Optional fuzzer dictionary.") - - parser.add_argument( - "--exec_timeout", type=int, - help="Timeout for one test-case (fuzz run) in milliseconds.") - - parser.add_argument( - "--blackbox", action="store_true", - help="Black-box fuzzing without compile-time instrumentation.") - - parser.add_argument( - "--fuzzer_out", action="store_true", - help="Show fuzzer-specific output (graphical interface) instead of deepstate one.") - - parser.add_argument( - "--fuzzer_args", default=[], nargs='*', - help="Flags to pass to the fuzzer. Format: `a arg1=val` -> `-a --arg val`.") - - - # Parallel / Ensemble Fuzzing - ensemble_group = parser.add_argument_group("Parallel/Ensemble Fuzzing") - ensemble_group.add_argument( - "--sync_dir", type=str, - help="Directory representing seed queue for synchronization between fuzzers.") - - ensemble_group.add_argument( - "--sync_cycle", type=int, default=5, - help="Time in seconds the executor should sync to sync directory (default is 5 seconds).") - - # Miscellaneous options - parser.add_argument( - "--fuzzer_help", action="store_true", - help="Show fuzzer command line interface's help options.") - - parser.add_argument( - "--home_path", type=str, - help="Path to fuzzer home directory. Will search executables there and in PATH.") - - # finalize building up parser by passing to superclass, and instantiate object attributes - # the base `parse_args` sets state with _ARGS, so we do need to return a namespace - cls.parser = parser - super(FuzzerFrontend, cls).parse_args() - - # parse fuzzer_args - _args: Dict[str, Any] = vars(cls._ARGS) - - fuzzer_args_parsed: List[Tuple[str, Optional[str]]] = [] - for arg in _args['fuzzer_args']: - vals = arg.split("=", 1) - key = vals[0] - val = None - if len(vals) == 2: - val = vals[1] - fuzzer_args_parsed.append((key, val)) - _args['fuzzer_args'] = fuzzer_args_parsed - - return None - - - def print_help(self) -> None: - """ - Calls fuzzer to print executable help menu. - """ - subprocess.call([self.fuzzer_exe, "--help"]) - - - ############################################## - # Fuzzer pre-execution methods - ############################################## - - - def _search_for_executable(self, exe_name): - # exe as absolute name - if os.path.isabs(exe_name): - if not os.path.isfile(exe_name): - raise FuzzFrontendError(f"File `{exe_name}` doesn't exists.") - if not os.access(exe_name, os.X_OK): - raise FuzzFrontendError(f"File `{exe_name}` is not executable.") - return exe_name - - # search in env, add search_dirs - if self.env: - for one_env_path in self.env.split(":"): - for search_dir in [""] + self.search_dirs: - exe_path: Optional[str] = shutil.which(exe_name, mode=os.F_OK, path=os.path.join(one_env_path, search_dir)) - if exe_path is not None: - return exe_path - - # search in current dir and $PATH - where_to_search = ['.', None] - for search_env in where_to_search: - exe_path: Optional[str] = shutil.which(exe_name, mode=os.F_OK, path=search_env) - if exe_path is not None: - return exe_path - - raise FuzzFrontendError(f"Executable file `{exe_name}` not found in neither `{self.env}` nor $PATH.\n" - f"Please add path to {self.name} in {self.ENVVAR} env var or in --home_path argument.") - - - def _set_executables(self): - """ - Search for required executables in ${env} (envvar from child class), - then in --home_path CLI argument, then in current dir and then in $PATH. - - Except if executable is given as an absolute path, then just check if it exists - and if permissions are correct. - - Update variables: - - fuzzer_exe - - compiler_exe - - Throws exception if some executable couldn't be found - """ - - # add path from argument to env - if self.home_path: - if self.env: - self.env += f":{self.home_path}" - else: - self.env = self.home_path - - # set fuzzer_exe - self.fuzzer_exe = self._search_for_executable(self.fuzzer_exe) - L.debug("Will use %s as fuzzer executable.", self.fuzzer_exe) - - # set compiler_exe - if self.compiler_exe: - self.compiler_exe = self._search_for_executable(self.compiler_exe) - L.debug("Will use %s as fuzzer compiler.", self.compiler_exe) - - # set additional executables - for exe_name, exe_file in self.EXECUTABLES.items(): - self.EXECUTABLES[exe_name] = self._search_for_executable(exe_file) - - - def compile(self, lib_path: str, flags: List[str], _out_bin: str, env = os.environ.copy()) -> None: - """ - Provides a simple interface that allows the user to compile a test harness - with instrumentation using the specified compiler. Users should implement an - inherited method that constructs the arguments necessary, and then pass it to the - base object. - - `compile()` also supports compiling arbitrary harnesses without instrumentation if a compiler - isn't set. - - :param lib_path: path to DeepState static library for linking - :param flags: list of compiler flags (TODO: support parsing from compilation database path) - :param _out_bin: name of linked test harness binary - :param env: optional envvars to set during compilation - """ - - _out_bin += f".{self.NAME.lower()}" - - if self.compiler_exe is None: - raise FuzzFrontendError("No compiler specified for compile-time instrumentation.") - - if self.binary is not None: - raise FuzzFrontendError("User-specified test binary conflicts with compiling from source.") - - if not os.path.isfile(lib_path): - raise FuzzFrontendError("No {}-instrumented DeepState static library found in {}".format(self, lib_path)) - L.debug("Static library path: %s", lib_path) - - # initialize compiler envvars - env["CC"] = self.compiler_exe.replace('++', '') - env["CXX"] = self.compiler_exe - L.debug("CC=%s and CXX=%s", env['CC'], env['CXX']) - - # initialize command with prepended compiler - compiler_args: List[str] = ["-std=c++11", self.compile_test] + flags + ["-o", _out_bin] # type: ignore - compile_cmd = [self.compiler_exe] + compiler_args - L.debug("Compilation command: %s", compile_cmd) - - # call compiler, and deal with exceptions accordingly - L.info("Compiling test harness `%s`", compile_cmd) - subprocess.Popen(compile_cmd, env=env).communicate() - - # extra check if target binary was successfully compiled, and set that as target binary - out_bin = os.path.join(os.getcwd(), _out_bin) - if os.path.exists(out_bin): - self.binary = out_bin - - - def create_fake_seeds(self): - if not self.input_seeds: - self.input_seeds = mkdtemp(prefix="deepstate_fake_seed") - with open(os.path.join(self.input_seeds, "fake_seed"), 'wb') as f: - f.write(b'X') - L.info("Creating fake input seed file in directory `%s`", self.input_seeds) - - - def check_required_directories(self, required_dirs): - for required_dir in required_dirs: - if not os.path.isdir(required_dir): - raise FuzzFrontendError(f"Can't resume with output directory `{self.output_test_dir}`. " - f"No `{required_dir}` directory inside.") - - - def setup_new_session(self, dirs_to_create=[]): - for dir_to_create in dirs_to_create: - Path(dir_to_create).mkdir(parents=True, exist_ok=True) - L.debug(f"Creating directory {dir_to_create}.") - - if self.require_seeds is True and not self.input_seeds: - self.create_fake_seeds() - - - def pre_exec(self): - """ - Called before fuzzer execution in order to perform sanity checks. Base method contains - default argument checks. Users should implement inherited method for any other environment - checks or initializations before execution. - - Do: - - search for executables (update self.EXECUTABLES) - - may print fuzzer help (and exit) - - may compile - - check for targets (self.binary) - - may check for input_seeds - - check for output directory - - check for sync_dir - - update stats_file path - """ - - if self.parser is None: - raise FuzzFrontendError("No arguments parsed yet. Call parse_args() before pre_exec().") - - # search for executables and set proper variables - self._set_executables() - - if self.fuzzer_help: - self.print_help() - sys.exit(0) - - # if compile_test is set, call compile for user - if self.compile_test: - self.compile() - - if self.binary is None: - print("\nError: Could not compile binary for execution.") - sys.exit(1) - - if not self.no_exit_compile: - print(f"\nDone compiling target binary `{self.binary}`.") - sys.exit(0) - - # manually check if binary positional argument was passed - if self.binary is None: - self.parser.print_help() - print("\nError: Target binary not specified.") - sys.exit(1) - - # check if binary exists and contains an absolute path - self.binary = os.path.abspath(self.binary) - if not os.path.isfile(self.binary): - raise FuzzFrontendError(f"Binary {self.binary} doesn't exists.") - L.debug("Target binary: %s", self.binary) - - # if input_seeds is provided run checks - if self.input_seeds: - L.debug("Input seeds directory: %s", self.input_seeds) - - if not os.path.exists(self.input_seeds): - raise FuzzFrontendError(f"Input seeds dir (`{self.input_seeds}`) doesn't exist.") - - if not os.path.isdir(self.input_seeds): - raise FuzzFrontendError(f"Input seeds dir (`{self.input_seeds}`) is not a directory.") - - if len(os.listdir(self.input_seeds)) == 0: - raise FuzzFrontendError(f"No seeds present in directory `{self.input_seeds}`.") - - # require output directory - L.debug("Output directory: %s", self.output_test_dir) - if not self.output_test_dir: - raise FuzzFrontendError("Must provide -o/--output_test_dir.") - - if not os.path.exists(self.output_test_dir): - try: - os.mkdir(self.output_test_dir) - except: - raise FuzzFrontendError(f"Output test dir (`{self.output_test_dir}`) doesn't exist, and could not be created.") - - if not os.path.isdir(self.output_test_dir): - raise FuzzFrontendError(f"Output test dir (`{self.output_test_dir}`) is not a directory.") - - # update stats and output file - self.stats_file = os.path.join(self.output_test_dir, self.stats_file) - self.output_file = os.path.join(self.output_test_dir, self.output_file) - - # require seeds flag - self.require_seeds = self.REQUIRE_SEEDS - - # push/pull/crash paths - self.push_dir = os.path.join(self.output_test_dir, self.PUSH_DIR) - self.pull_dir = os.path.join(self.output_test_dir, self.PULL_DIR) - self.crash_dir = os.path.join(self.output_test_dir, self.CRASH_DIR) - - # check if we enabled seed synchronization, and initialize directory - if self.sync_dir: - if not os.path.exists(self.sync_dir): - raise FuzzFrontendError(f"Seed synchronization dir (`{self.sync_dir}`) doesn't exist.") - - if not os.path.isdir(self.sync_dir): - raise FuzzFrontendError(f"Seed synchronization dir (`{self.sync_dir}`) is not a directory.") - - L.info("Will synchronize seed using `%s` directory.", self.sync_dir) - - - ################################## - # Fuzzer command builder methods - ################################## - - - @property - def cmd(self) -> List[str]: - """ - Property method that implements the logic for constructing a valid command - for fuzzing, which is then bootstrapped and consumed by subprocess. User must - implement functionality that can construct a valid list of flags (key-value tuples), - and then returns it to build_cmd(). - """ - raise NotImplementedError("Must implement in frontend subclass.") - - - def build_cmd(self, cmd_list: List[Optional[str]], input_symbol: str = "@@") -> List[Optional[str]]: - """ - Helper method to be invoked by child fuzzer class's cmd() property method in order - to finalize command called by the fuzzer executable with appropriate arguments for the - test harness. Should NOT be called if a fuzzer gets invoked differently (ie arguments necessary - that deviate from how standard fuzzers invoke binaries). - - :param cmd_list: incomplete dict to complete with harness argument information - :param input_symbol: symbol recognized by fuzzer to replace when conducting file-based fuzzing - """ - - # initialize command with harness binary and DeepState flags to pass to it - cmd_list.extend([ - "--", self.binary, - "--input_test_file", input_symbol, - "--abort_on_fail", - "--no_fork", - "--min_log_level", str(self.min_log_level) - ]) - - # append any other DeepState flags - for key, val in self.target_args: - if len(key) == 1: - cmd_list.append('-{}'.format(key)) - else: - cmd_list.append('--{}'.format(key)) - if val is not None: - cmd_list.append(val) - - # test selection - if self.which_test: - cmd_list.extend(["--input_which_test", self.which_test]) - - return cmd_list - - - def main(self): - """ - Helper method for calling fuzzer methods in correct order - """ - try: - self.parse_args() - self.run() - return self.fuzzer_return_code - except AnalysisBackendError as e: - L.error(e) - return 1 - - - ############################################## - # Fuzzer process execution methods - ############################################## - - - def manage(self): - # print and save statistics - self.populate_stats() - self.save_stats() - if not self.fuzzer_out: - self.print_stats() - - # invoke ensemble if sync_dir is provided - if self.sync_dir: - L.info("%s - Performing sync cycle %s", self.name, self.sync_count) - self.ensemble() - self.sync_count += 1 - - - def cleanup(self): - if not self.proc: - return - - L.info(f"Killing process {self.proc.pid} and childs.") - - # terminate - try: - for some_proc in psutil.Process(self.proc.pid).children(recursive=True) + [self.proc]: - some_proc.terminate() - except psutil.NoSuchProcess: - self.proc = None - return - - # hard kill - for some_proc in psutil.Process(self.proc.pid).children(recursive=True) + [self.proc]: - L.warning("Subprocess (PID %d) could not terminate in time, killing.", some_proc.pid) - try: - some_proc.kill() - except psutil.NoSuchProcess: - self.proc = None - - self.proc = None - - - def run(self, runner: Optional[str] = None, no_exec: bool = False, skip_argparse: bool = False): - """ - Interface for spawning and executing fuzzer job. - - :param runner: if necessary, a runner that is invoked before fuzzer executable (ie `dotnet`) - :param no_exec: skips pre- and post-processing steps during execution - :param skip_argparse: if set, skip argparsing, if already done in any auxiliary executor. - - """ - - # NOTE(alan): we don't use namespace param so we "build up" object attributes when we execute run() - if not skip_argparse: - super(FuzzerFrontend, self).init_from_dict() - - # call pre_exec for any checks/inits before execution. - if not no_exec: - L.info("Calling pre_exec before fuzzing") - self.pre_exec() - - # initialize cmd from property - command = [self.fuzzer_exe] + self.cmd # type: ignore - - # prepend runner that invokes fuzzer - if runner: - command.insert(0, runner) - - L.info("Executing command `%s`", command) - self.start_time: int = int(time.time()) - self.command: str = ' '.join(command) - self.sync_count = 0 - - total_execution_time: int = 0 - wait_time: int = self.sync_cycle - run_fuzzer: bool = True - prev_log_level = L.level - - # for fuzzer output - if not self.fuzzer_out: - fuzzer_out_file = open(self.output_file, "wb") - - # run or resume fuzzer process as long as it is needed - # may create new processes continuously - while run_fuzzer: - run_one_fuzzer_process: bool = False - try: - if self.fuzzer_out: - # disable deepstate output - L.info("Using fuzzer output.") - L.setLevel("ERROR") - self.proc = subprocess.Popen(command) - - else: - L.info("Using DeepState output.") - # TODO: frontends uses blocking read in `populate_stats`, - # we may replace PIPE with normal file and do reads non-blocking - self.proc = subprocess.Popen(command, stdout=fuzzer_out_file, stderr=fuzzer_out_file) - - run_one_fuzzer_process = True - L.info("Started fuzzer process with PID %d.", self.proc.pid) - - except (OSError, ValueError): - L.setLevel(prev_log_level) - L.error(traceback.format_exc()) - raise FuzzFrontendError("Exception during fuzzer startup.") - - # run-manage loop, until somethings happens (error, interrupt, fuzzer exits) - # use only one process - while run_one_fuzzer_process: - # general timeout - time_left = float('inf') - total_execution_time = int(time.time() - self.start_time) - if self.timeout != 0: - time_left = self.timeout - total_execution_time - if time_left < 0: - run_one_fuzzer_process = False - run_fuzzer = False - wait_time = 0 - L.info("Timeout") - - try: - # sleep/communicate for `self.sync_cycle` time - timeout_one_cycle: int = wait_time - if wait_time > time_left: - timeout_one_cycle = int(time_left) - - L.debug("One cycle `communicate` with timeout %d.", timeout_one_cycle) - stdout, stderr = self.proc.communicate(timeout=timeout_one_cycle) - - # fuzzer process exited - # it's fine if returncode is 0 or 1 for libfuzzer - if self.proc.returncode == 0 or \ - (self.proc.returncode == 1 and self.name == "libFuzzer"): - L.info("Fuzzer %s (PID %d) exited with return code %d.", - self.name, self.proc.pid, self.proc.returncode) - self.fuzzer_return_code = 0 - run_one_fuzzer_process = False - - else: - if stdout: - L.error(stdout.decode('utf8')) - if stderr: - L.error(stderr.decode('utf8')) - self.fuzzer_return_code = self.proc.returncode - raise FuzzFrontendError(f"Fuzzer {self.name} (PID {self.proc.pid}) exited " - f"with return code {self.proc.returncode}.") - - # Timeout, just continue to management step - except subprocess.TimeoutExpired: - L.debug("One cycle timeout.") - - # Any OS-specific errors encountered - except OSError as e: - L.error("%s run interrupted due to OSError: %s.", self.name, e) - run_one_fuzzer_process = False - - # SIGINT stops fuzzer, but continues frontend execution - except KeyboardInterrupt: - L.info("Stopped the %s fuzzer.", self.name) - run_one_fuzzer_process = False - run_fuzzer = False - - # bad things happened, inform user and exit - except Exception: - L.error(traceback.format_exc()) - L.error("Exception during fuzzer %s run.", self.name) - run_one_fuzzer_process = False - run_fuzzer = False - - # manage - try: - L.debug("Management cycle starts after %ss.", total_execution_time) - self.manage() - - # error in management, exit - except Exception: - L.error(traceback.format_exc()) - L.error("Exception during fuzzer %s run.", self.name) - run_one_fuzzer_process = False - run_fuzzer = False - - if self.do_restart(): - L.info(f"Restarting fuzzer {self.name}.") - run_one_fuzzer_process = False - - # cleanup - try: - self.cleanup() - sleep(1) # wait so all fuzzer processes are killed - except: - pass - - if run_fuzzer: - self.post_exec() - - # and... maybe loop again! - - if not self.fuzzer_out: - fuzzer_out_file.close() - - L.setLevel(prev_log_level) - # calculate total execution time - exec_time: float = round(time.time() - self.start_time, 2) - L.info("Fuzzer exec time: %ss", exec_time) - - # do post-fuzz operations - if not no_exec: - L.info("Calling post-exec for fuzzer post-processing") - self.post_exec() - - - ############################################ - # Auxiliary reporting and processing methods - ############################################ - - - def reporter(self): - """ - Provides an interface for fuzzers to output important statistics during an ensemble - cycle. This ensure that fuzzer outputs don't clobber STDOUT, and that users can gain - insight during ensemble run. - """ - return NotImplementedError("Must implement in frontend subclass.") - - - def do_restart(self): - """ - Some fuzzers need restart to use seeds from external sources - (can't pull seeds in runtime). - This function should determine if the fuzzer should be restarted too look - for new seeds. - This may be based on time of last new path discovered or whatever. - - Should return False if self.sync_dir is None. - """ - if not self.sync_dir: - return False - - # if time.time() - self.start_time > 20: - # return True - return False - - - def populate_stats(self): - """ - Parses out stats generated by fuzzer output. Should be implemented by user, and can return custom - feedback. - """ - crashes: int = len(os.listdir(self.crash_dir)) - if os.path.isfile(os.path.join(self.crash_dir, "README.txt")): - crashes -= 1 - self.stats["unique_crashes"] = str(crashes) - self.stats["start_time"] = str(int(self.start_time)) - if self.proc: - self.stats["fuzzer_pid"] = str(self.proc.pid) - if self.sync_dir: - self.stats["sync_dir_size"] = str(len(os.listdir(self.sync_dir))) - - - def print_stats(self): - for key, value in self.stats.items(): - if value: - L.fuzz_stats("%s:%s", key, value) - L.fuzz_stats("-"*30) - - - def save_stats(self): - with open(self.stats_file, 'w') as f: - for key, value in self.stats.items(): - if value: - f.write(f"{key}:{value}\n") - - - def post_exec(self): - """ - Performs user-specified post-processing execution logic. Should be implemented by user, and can implement - things like crash triaging, testcase minimization (ie with `deepstate-reduce`), or any other manipulations - with produced testcases. - """ - # make sure that child processes are killed - self.cleanup() - - - ################################### - # Ensemble/Parallel Fuzzing methods - ################################### - - - def _sync_seeds(self, src: str, dest: str, excludes: List[str] = []) -> None: - """ - Helper that invokes rsync for convenient file syncing between two files. - - TODO(alan): implement functionality for syncing across servers. - TODO(alan): consider implementing "native" syncing alongside current "rsync mode". - - :param src: path to source queue - :param dest: path to destination queue - :param excludes: list of string patterns for paths to ignore when rsync-ing - """ - - rsync_cmd: List[str] = [ - "rsync", - "--recursive", - "--archive", - "--checksum", - "--compress", - "--ignore-existing" - ] - - # subclass should invoke with list of pattern ignores - if len(excludes) > 0: - rsync_cmd += [f"--exclude={e}" for e in excludes] - - rsync_cmd += [ - os.path.join(src, ""), # append trailing / - dest - ] - - # L.debug("rsync command: %s", rsync_cmd) - L.debug("rsync %s: from `%s` to `%s`.", self.name, src, dest) - try: - subprocess.Popen(rsync_cmd) - except subprocess.CalledProcessError as e: - raise FuzzFrontendError(f"{self.name} rsync interrupted due to exception {e}.") - - - def ensemble(self, local_queue: Optional[str] = None, global_queue: Optional[str] = None): - """ - Base method for implementing ensemble fuzzing with seed synchronization. User should - implement any additional logic for determining whether to sync/get seeds as if in event loop. - """ - - if not self.sync_dir: - L.warning("Called `ensemble`, but `--sync_dir` not provided.") - return - - global_queue = os.path.join(self.sync_dir, "queue") - global_crashes = os.path.join(self.sync_dir, "crashes") - local_queue = self.push_dir - local_crashes = self.crash_dir - - # check global queue - global_len: int = len(os.listdir(self.crash_dir)) - L.debug("Global seed queue: `%s` with %d files", global_queue, global_len) - - # update local queue with new findings - self._sync_seeds(src=self.pull_dir, dest=self.push_dir) - - # check local queue - local_len: int = len(os.listdir(self.push_dir)) - L.debug("Fuzzer local seed queue: `%s` with %d files", local_queue, local_len) - - # get seeds from local to global queue, rsync will deal with duplicates - self._sync_seeds(src=local_queue, dest=global_queue) - self._sync_seeds(src=local_crashes, dest=global_crashes) - - # push seeds from global queue to local, rsync will deal with duplicates - self._sync_seeds(src=global_queue, dest=local_queue) +#!/usr/bin/env python3.6 +# Copyright (c) 2019 Trail of Bits, Inc. +# +# 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 logging + +import os +import time +import sys +import subprocess +import psutil # type: ignore +import argparse +import shutil +import traceback + +from tempfile import mkdtemp +from time import sleep +from pathlib import Path +from typing import Optional, Dict, List, Any, Tuple + +from deepstate.core.base import AnalysisBackend, AnalysisBackendError + + +L = logging.getLogger(__name__) + + +class FuzzFrontendError(AnalysisBackendError): + """ + Defines our custom exception class for FuzzerFrontend + """ + pass + + +class FuzzerFrontend(AnalysisBackend): + """ + Defines a base front-end object for using DeepState to interact with fuzzers. + """ + + # fuzzer-specific configurations + ENVVAR: str = "PATH" + REQUIRE_SEEDS: bool = False + + # configurations for seed synchronization + PUSH_DIR: str + PULL_DIR: str + CRASH_DIR: str + + def __init__(self) -> None: + """ + Create and store variables: + - fuzzer_exe (fuzzer executable file) + - env (environment variable name) + - search_dirs (directories inside fuzzer home dir where to look for executables) + - require_seeds + - stats (dict that frontend should populate in populate_stats method) + - stats_file (file where to put stats from fuzzer in common format) + - output_file (file where stdout of fuzzer will be redirected) + - proc (handler to fuzzer process) + - fuzzer_return_code (the exit status from the fuzzer process AND the return status of main) + + - push_dir (push testcases from external sources here) + - pull_dir (pull new testcases from this dir) + - crash_dir (crashes will be in this dir) + + Inherits: + - name (name for pretty printing) + - compiler_exe (fuzzer compiler file, optional) + + User must define in inherited fuzzer class: + - NAME str + - EXECUTABLES dict with keys: + FUZZER, COMPILER (if compiling) and any executable it will use + + """ + super(FuzzerFrontend, self).__init__() + + if "FUZZER" not in self.EXECUTABLES: + raise FuzzFrontendError("FuzzerFrontend.EXECUTABLES[\"FUZZER\"] not set.") + self.fuzzer_exe: str = self.EXECUTABLES.pop("FUZZER") + + self.env: Optional[str] = os.environ.get(self.ENVVAR, None) + + self.search_dirs: List[str] = getattr(self, "SEARCH_DIRS", []) + + # flag to ensure fuzzer processes do not persist + self._on: bool = False + + self.proc: subprocess.Popen[bytes] + self.fuzzer_return_code = 0 + self.require_seeds: bool = False + self.stats_file: str = "deepstate-stats.txt" + self.output_file: str = "fuzzer-output.txt" + + # same as AFL's (https://github.com/google/AFL/blob/master/docs/status_screen.txt) + self.stats: Dict[str, Optional[str]] = { + # guaranteed + "unique_crashes": None, + "fuzzer_pid": None, + "start_time": None, + "sync_dir_size": None, + + # not guaranteed + "execs_done": None, + "execs_per_sec": None, + "last_update": None, + "cycles_done": None, + "paths_total": None, + "paths_favored": None, + "paths_found": None, + "paths_imported": None, + "max_depth": None, + "cur_path": None, + "pending_favs": None, + "pending_total": None, + "variable_paths": None, + "stability": None, + "bitmap_cvg": None, + "unique_hangs": None, + "last_path": None, + "last_crash": None, + "last_hang": None, + "execs_since_crash": None, + "slowest_exec_ms": None, + "peak_rss_mb": None, + } + + # parsed argument attributes + self.input_seeds: Optional[str] = None + self.max_input_size: int = 8192 + self.dictionary: Optional[str] = None + self.exec_timeout: Optional[int] = None + self.blackbox: Optional[bool] = None + self.fuzzer_args: List[Any] = [] + self.fuzzer_out: bool = False + + self.sync_cycle: int = 5 + self.sync_out: bool = True + self.sync_dir: Optional[str] = None + + self.push_dir: str = '' + self.pull_dir: str = '' + self.crash_dir: str = '' + + self.home_path: Optional[str] = None + + + def __repr__(self) -> str: + return "{}".format(self.__class__.__name__) + + + @classmethod + def parse_args(cls) -> Optional[argparse.Namespace]: + """ + Default base argument parser for DeepState frontends. Comprises of default arguments all + frontends must implement to maintain consistency in executables. Users can inherit this + method to extend and add own arguments or override for outstanding deviations in fuzzer CLIs. + + Arguments provided by this method and usable in fuzzers' functions. + Guaranteed arguments (have default value): + - output_test_dir (default: out) + - mem_limit (default: 50MiB) + - max_input_size (default: 8192B) + - fuzzer_args (default: {}) + - blackbox (default: False) + + Optional arguments (may be None): + - input_seeds + - dictionary + - exec_timeout + + Arguments that should not be used by child class: + - timeout (default: 0) + - num_workers (default: 1) + - target_args (default: {}) + """ + + L.debug("Parsing arguments with internal base class routine") + + if cls._ARGS: + L.debug("Returning already-parsed arguments") + return cls._ARGS + + # use existing argparser if defined in fuzzer object, or initialize new one, both with default arguments + if cls.parser: + L.debug("Using previously initialized parser") + parser = cls.parser + else: + L.debug("Instantiating new ArgumentParser") + parser = argparse.ArgumentParser(description="Use {} fuzzer as a backend for DeepState".format(str(cls))) + + # Fuzzer execution options + parser.add_argument( + "-i", "--input_seeds", type=str, + help="Directory with seed inputs for fuzzers to queue and mutate.") + + parser.add_argument( + "-s", "--max_input_size", type=int, default=8192, + help="Maximum input size for input generator in bytes (default is 8192). 0 for unlimited.") + + parser.add_argument("--dictionary", type=str, + help="Optional fuzzer dictionary.") + + parser.add_argument( + "--exec_timeout", type=int, + help="Timeout for one test-case (fuzz run) in milliseconds.") + + parser.add_argument( + "--blackbox", action="store_true", + help="Black-box fuzzing without compile-time instrumentation.") + + parser.add_argument( + "--fuzzer_out", action="store_true", + help="Show fuzzer-specific output (graphical interface) instead of deepstate one.") + + parser.add_argument( + "--fuzzer_args", default=[], nargs='*', + help="Flags to pass to the fuzzer. Format: `a arg1=val` -> `-a --arg val`.") + + + # Parallel / Ensemble Fuzzing + ensemble_group = parser.add_argument_group("Parallel/Ensemble Fuzzing") + ensemble_group.add_argument( + "--sync_dir", type=str, + help="Directory representing seed queue for synchronization between fuzzers.") + + ensemble_group.add_argument( + "--sync_cycle", type=int, default=5, + help="Time in seconds the executor should sync to sync directory (default is 5 seconds).") + + # Miscellaneous options + parser.add_argument( + "--fuzzer_help", action="store_true", + help="Show fuzzer command line interface's help options.") + + parser.add_argument( + "--home_path", type=str, + help="Path to fuzzer home directory. Will search executables there and in PATH.") + + # finalize building up parser by passing to superclass, and instantiate object attributes + # the base `parse_args` sets state with _ARGS, so we do need to return a namespace + cls.parser = parser + super(FuzzerFrontend, cls).parse_args() + + # parse fuzzer_args + _args: Dict[str, Any] = vars(cls._ARGS) + + fuzzer_args_parsed: List[Tuple[str, Optional[str]]] = [] + for arg in _args['fuzzer_args']: + vals = arg.split("=", 1) + key = vals[0] + val = None + if len(vals) == 2: + val = vals[1] + fuzzer_args_parsed.append((key, val)) + _args['fuzzer_args'] = fuzzer_args_parsed + + return None + + + def print_help(self) -> None: + """ + Calls fuzzer to print executable help menu. + """ + subprocess.call([self.fuzzer_exe, "--help"]) + + + ############################################## + # Fuzzer pre-execution methods + ############################################## + + + def _search_for_executable(self, exe_name): + # exe as absolute name + if os.path.isabs(exe_name): + if not os.path.isfile(exe_name): + raise FuzzFrontendError(f"File `{exe_name}` doesn't exists.") + if not os.access(exe_name, os.X_OK): + raise FuzzFrontendError(f"File `{exe_name}` is not executable.") + return exe_name + + # search in env, add search_dirs + if self.env: + for one_env_path in self.env.split(":"): + for search_dir in [""] + self.search_dirs: + exe_path: Optional[str] = shutil.which(exe_name, mode=os.F_OK, path=os.path.join(one_env_path, search_dir)) + if exe_path is not None: + return exe_path + + # search in current dir and $PATH + where_to_search = ['.', None] + for search_env in where_to_search: + exe_path: Optional[str] = shutil.which(exe_name, mode=os.F_OK, path=search_env) + if exe_path is not None: + return exe_path + + raise FuzzFrontendError(f"Executable file `{exe_name}` not found in neither `{self.env}` nor $PATH.\n" + f"Please add path to {self.name} in {self.ENVVAR} env var or in --home_path argument.") + + + def _set_executables(self): + """ + Search for required executables in ${env} (envvar from child class), + then in --home_path CLI argument, then in current dir and then in $PATH. + + Except if executable is given as an absolute path, then just check if it exists + and if permissions are correct. + + Update variables: + - fuzzer_exe + - compiler_exe + + Throws exception if some executable couldn't be found + """ + + # add path from argument to env + if self.home_path: + if self.env: + self.env += f":{self.home_path}" + else: + self.env = self.home_path + + # set fuzzer_exe + self.fuzzer_exe = self._search_for_executable(self.fuzzer_exe) + L.debug("Will use %s as fuzzer executable.", self.fuzzer_exe) + + # set compiler_exe + if self.compiler_exe: + self.compiler_exe = self._search_for_executable(self.compiler_exe) + L.debug("Will use %s as fuzzer compiler.", self.compiler_exe) + + # set additional executables + for exe_name, exe_file in self.EXECUTABLES.items(): + self.EXECUTABLES[exe_name] = self._search_for_executable(exe_file) + + + def compile(self, lib_path: str, flags: List[str], _out_bin: str, env = os.environ.copy()) -> None: + """ + Provides a simple interface that allows the user to compile a test harness + with instrumentation using the specified compiler. Users should implement an + inherited method that constructs the arguments necessary, and then pass it to the + base object. + + `compile()` also supports compiling arbitrary harnesses without instrumentation if a compiler + isn't set. + + :param lib_path: path to DeepState static library for linking + :param flags: list of compiler flags (TODO: support parsing from compilation database path) + :param _out_bin: name of linked test harness binary + :param env: optional envvars to set during compilation + """ + + _out_bin += f".{self.NAME.lower()}" + + if self.compiler_exe is None: + raise FuzzFrontendError("No compiler specified for compile-time instrumentation.") + + if self.binary is not None: + raise FuzzFrontendError("User-specified test binary conflicts with compiling from source.") + + if not os.path.isfile(lib_path): + raise FuzzFrontendError("No {}-instrumented DeepState static library found in {}".format(self, lib_path)) + L.debug("Static library path: %s", lib_path) + + # initialize compiler envvars + env["CC"] = self.compiler_exe.replace('++', '') + env["CXX"] = self.compiler_exe + L.debug("CC=%s and CXX=%s", env['CC'], env['CXX']) + + # initialize command with prepended compiler + compiler_args: List[str] = ["-std=c++11", self.compile_test] + flags + ["-o", _out_bin] # type: ignore + compile_cmd = [self.compiler_exe] + compiler_args + L.debug("Compilation command: %s", compile_cmd) + + # call compiler, and deal with exceptions accordingly + L.info("Compiling test harness `%s`", compile_cmd) + subprocess.Popen(compile_cmd, env=env).communicate() + + # extra check if target binary was successfully compiled, and set that as target binary + out_bin = os.path.join(os.getcwd(), _out_bin) + if os.path.exists(out_bin): + self.binary = out_bin + + + def create_fake_seeds(self): + if not self.input_seeds: + self.input_seeds = mkdtemp(prefix="deepstate_fake_seed") + with open(os.path.join(self.input_seeds, "fake_seed"), 'wb') as f: + f.write(b'X') + L.info("Creating fake input seed file in directory `%s`", self.input_seeds) + + + def check_required_directories(self, required_dirs): + for required_dir in required_dirs: + if not os.path.isdir(required_dir): + raise FuzzFrontendError(f"Can't resume with output directory `{self.output_test_dir}`. " + f"No `{required_dir}` directory inside.") + + + def setup_new_session(self, dirs_to_create=[]): + for dir_to_create in dirs_to_create: + Path(dir_to_create).mkdir(parents=True, exist_ok=True) + L.debug(f"Creating directory {dir_to_create}.") + + if self.require_seeds is True and not self.input_seeds: + self.create_fake_seeds() + + + def pre_exec(self): + """ + Called before fuzzer execution in order to perform sanity checks. Base method contains + default argument checks. Users should implement inherited method for any other environment + checks or initializations before execution. + + Do: + - search for executables (update self.EXECUTABLES) + - may print fuzzer help (and exit) + - may compile + - check for targets (self.binary) + - may check for input_seeds + - check for output directory + - check for sync_dir + - update stats_file path + """ + + if self.parser is None: + raise FuzzFrontendError("No arguments parsed yet. Call parse_args() before pre_exec().") + + # search for executables and set proper variables + self._set_executables() + + if self.fuzzer_help: + self.print_help() + sys.exit(0) + + # if compile_test is set, call compile for user + if self.compile_test: + self.compile() + + if self.binary is None: + print("\nError: Could not compile binary for execution.") + sys.exit(1) + + if not self.no_exit_compile: + print(f"\nDone compiling target binary `{self.binary}`.") + sys.exit(0) + + # manually check if binary positional argument was passed + if self.binary is None: + self.parser.print_help() + print("\nError: Target binary not specified.") + sys.exit(1) + + # check if binary exists and contains an absolute path + self.binary = os.path.abspath(self.binary) + if not os.path.isfile(self.binary): + raise FuzzFrontendError(f"Binary {self.binary} doesn't exists.") + L.debug("Target binary: %s", self.binary) + + # if input_seeds is provided run checks + if self.input_seeds: + L.debug("Input seeds directory: %s", self.input_seeds) + + if not os.path.exists(self.input_seeds): + raise FuzzFrontendError(f"Input seeds dir (`{self.input_seeds}`) doesn't exist.") + + if not os.path.isdir(self.input_seeds): + raise FuzzFrontendError(f"Input seeds dir (`{self.input_seeds}`) is not a directory.") + + if len(os.listdir(self.input_seeds)) == 0: + raise FuzzFrontendError(f"No seeds present in directory `{self.input_seeds}`.") + + # require output directory + L.debug("Output directory: %s", self.output_test_dir) + if not self.output_test_dir: + raise FuzzFrontendError("Must provide -o/--output_test_dir.") + + if not os.path.exists(self.output_test_dir): + try: + os.mkdir(self.output_test_dir) + except: + raise FuzzFrontendError(f"Output test dir (`{self.output_test_dir}`) doesn't exist, and could not be created.") + + if not os.path.isdir(self.output_test_dir): + raise FuzzFrontendError(f"Output test dir (`{self.output_test_dir}`) is not a directory.") + + # update stats and output file + self.stats_file = os.path.join(self.output_test_dir, self.stats_file) + self.output_file = os.path.join(self.output_test_dir, self.output_file) + + # require seeds flag + self.require_seeds = self.REQUIRE_SEEDS + + # push/pull/crash paths + self.push_dir = os.path.join(self.output_test_dir, self.PUSH_DIR) + self.pull_dir = os.path.join(self.output_test_dir, self.PULL_DIR) + self.crash_dir = os.path.join(self.output_test_dir, self.CRASH_DIR) + + # check if we enabled seed synchronization, and initialize directory + if self.sync_dir: + if not os.path.exists(self.sync_dir): + raise FuzzFrontendError(f"Seed synchronization dir (`{self.sync_dir}`) doesn't exist.") + + if not os.path.isdir(self.sync_dir): + raise FuzzFrontendError(f"Seed synchronization dir (`{self.sync_dir}`) is not a directory.") + + L.info("Will synchronize seed using `%s` directory.", self.sync_dir) + + + ################################## + # Fuzzer command builder methods + ################################## + + + @property + def cmd(self) -> List[str]: + """ + Property method that implements the logic for constructing a valid command + for fuzzing, which is then bootstrapped and consumed by subprocess. User must + implement functionality that can construct a valid list of flags (key-value tuples), + and then returns it to build_cmd(). + """ + raise NotImplementedError("Must implement in frontend subclass.") + + + def build_cmd(self, cmd_list: List[Optional[str]], input_symbol: str = "@@") -> List[Optional[str]]: + """ + Helper method to be invoked by child fuzzer class's cmd() property method in order + to finalize command called by the fuzzer executable with appropriate arguments for the + test harness. Should NOT be called if a fuzzer gets invoked differently (ie arguments necessary + that deviate from how standard fuzzers invoke binaries). + + :param cmd_list: incomplete dict to complete with harness argument information + :param input_symbol: symbol recognized by fuzzer to replace when conducting file-based fuzzing + """ + + # initialize command with harness binary and DeepState flags to pass to it + cmd_list.extend([ + "--", self.binary, + "--input_test_file", input_symbol, + "--abort_on_fail", + "--no_fork", + "--min_log_level", str(self.min_log_level) + ]) + + # append any other DeepState flags + for key, val in self.target_args: + if len(key) == 1: + cmd_list.append('-{}'.format(key)) + else: + cmd_list.append('--{}'.format(key)) + if val is not None: + cmd_list.append(val) + + # test selection + if self.which_test: + cmd_list.extend(["--input_which_test", self.which_test]) + + return cmd_list + + + def main(self): + """ + Helper method for calling fuzzer methods in correct order + """ + try: + self.parse_args() + self.run() + return self.fuzzer_return_code + except AnalysisBackendError as e: + L.error(e) + return 1 + + + ############################################## + # Fuzzer process execution methods + ############################################## + + + def manage(self): + # print and save statistics + self.populate_stats() + self.save_stats() + if not self.fuzzer_out: + self.print_stats() + + # invoke ensemble if sync_dir is provided + if self.sync_dir: + L.info("%s - Performing sync cycle %s", self.name, self.sync_count) + self.ensemble() + self.sync_count += 1 + + + def cleanup(self): + if not self.proc: + return + + L.info(f"Killing process {self.proc.pid} and childs.") + + # terminate + try: + for some_proc in psutil.Process(self.proc.pid).children(recursive=True) + [self.proc]: + some_proc.terminate() + except psutil.NoSuchProcess: + self.proc = None + return + + # hard kill + for some_proc in psutil.Process(self.proc.pid).children(recursive=True) + [self.proc]: + L.warning("Subprocess (PID %d) could not terminate in time, killing.", some_proc.pid) + try: + some_proc.kill() + except psutil.NoSuchProcess: + self.proc = None + + self.proc = None + + + def run(self, runner: Optional[str] = None, no_exec: bool = False, skip_argparse: bool = False): + """ + Interface for spawning and executing fuzzer job. + + :param runner: if necessary, a runner that is invoked before fuzzer executable (ie `dotnet`) + :param no_exec: skips pre- and post-processing steps during execution + :param skip_argparse: if set, skip argparsing, if already done in any auxiliary executor. + + """ + + # NOTE(alan): we don't use namespace param so we "build up" object attributes when we execute run() + if not skip_argparse: + super(FuzzerFrontend, self).init_from_dict() + + # call pre_exec for any checks/inits before execution. + if not no_exec: + L.info("Calling pre_exec before fuzzing") + self.pre_exec() + + # initialize cmd from property + command = [self.fuzzer_exe] + self.cmd # type: ignore + + # prepend runner that invokes fuzzer + if runner: + command.insert(0, runner) + + L.info("Executing command `%s`", command) + self.start_time: int = int(time.time()) + self.command: str = ' '.join(command) + self.sync_count = 0 + + total_execution_time: int = 0 + wait_time: int = self.sync_cycle + run_fuzzer: bool = True + prev_log_level = L.level + + # for fuzzer output + if not self.fuzzer_out: + fuzzer_out_file = open(self.output_file, "wb") + + # run or resume fuzzer process as long as it is needed + # may create new processes continuously + while run_fuzzer: + run_one_fuzzer_process: bool = False + try: + if self.fuzzer_out: + # disable deepstate output + L.info("Using fuzzer output.") + L.setLevel("ERROR") + self.proc = subprocess.Popen(command) + + else: + L.info("Using DeepState output.") + # TODO: frontends uses blocking read in `populate_stats`, + # we may replace PIPE with normal file and do reads non-blocking + self.proc = subprocess.Popen(command, stdout=fuzzer_out_file, stderr=fuzzer_out_file) + + run_one_fuzzer_process = True + L.info("Started fuzzer process with PID %d.", self.proc.pid) + + except (OSError, ValueError): + L.setLevel(prev_log_level) + L.error(traceback.format_exc()) + raise FuzzFrontendError("Exception during fuzzer startup.") + + # run-manage loop, until somethings happens (error, interrupt, fuzzer exits) + # use only one process + while run_one_fuzzer_process: + # general timeout + time_left = float('inf') + total_execution_time = int(time.time() - self.start_time) + if self.timeout != 0: + time_left = self.timeout - total_execution_time + if time_left < 0: + run_one_fuzzer_process = False + run_fuzzer = False + wait_time = 0 + L.info("Timeout") + + try: + # sleep/communicate for `self.sync_cycle` time + timeout_one_cycle: int = wait_time + if wait_time > time_left: + timeout_one_cycle = int(time_left) + + L.debug("One cycle `communicate` with timeout %d.", timeout_one_cycle) + stdout, stderr = self.proc.communicate(timeout=timeout_one_cycle) + + # fuzzer process exited + # it's fine if returncode is 0 or 1 for libfuzzer + if self.proc.returncode == 0 or \ + (self.proc.returncode == 1 and self.name == "libFuzzer"): + L.info("Fuzzer %s (PID %d) exited with return code %d.", + self.name, self.proc.pid, self.proc.returncode) + self.fuzzer_return_code = 0 + run_one_fuzzer_process = False + + else: + if stdout: + L.error(stdout.decode('utf8')) + if stderr: + L.error(stderr.decode('utf8')) + self.fuzzer_return_code = self.proc.returncode + raise FuzzFrontendError(f"Fuzzer {self.name} (PID {self.proc.pid}) exited " + f"with return code {self.proc.returncode}.") + + # Timeout, just continue to management step + except subprocess.TimeoutExpired: + L.debug("One cycle timeout.") + + # Any OS-specific errors encountered + except OSError as e: + L.error("%s run interrupted due to OSError: %s.", self.name, e) + run_one_fuzzer_process = False + + # SIGINT stops fuzzer, but continues frontend execution + except KeyboardInterrupt: + L.info("Stopped the %s fuzzer.", self.name) + run_one_fuzzer_process = False + run_fuzzer = False + + # bad things happened, inform user and exit + except Exception: + L.error(traceback.format_exc()) + L.error("Exception during fuzzer %s run.", self.name) + run_one_fuzzer_process = False + run_fuzzer = False + + # manage + try: + L.debug("Management cycle starts after %ss.", total_execution_time) + self.manage() + + # error in management, exit + except Exception: + L.error(traceback.format_exc()) + L.error("Exception during fuzzer %s run.", self.name) + run_one_fuzzer_process = False + run_fuzzer = False + + if self.do_restart(): + L.info(f"Restarting fuzzer {self.name}.") + run_one_fuzzer_process = False + + # cleanup + try: + self.cleanup() + sleep(1) # wait so all fuzzer processes are killed + except: + pass + + if run_fuzzer: + self.post_exec() + + # and... maybe loop again! + + if not self.fuzzer_out: + fuzzer_out_file.close() + + L.setLevel(prev_log_level) + # calculate total execution time + exec_time: float = round(time.time() - self.start_time, 2) + L.info("Fuzzer exec time: %ss", exec_time) + + # do post-fuzz operations + if not no_exec: + L.info("Calling post-exec for fuzzer post-processing") + self.post_exec() + + + ############################################ + # Auxiliary reporting and processing methods + ############################################ + + + def reporter(self): + """ + Provides an interface for fuzzers to output important statistics during an ensemble + cycle. This ensure that fuzzer outputs don't clobber STDOUT, and that users can gain + insight during ensemble run. + """ + return NotImplementedError("Must implement in frontend subclass.") + + + def do_restart(self): + """ + Some fuzzers need restart to use seeds from external sources + (can't pull seeds in runtime). + This function should determine if the fuzzer should be restarted too look + for new seeds. + This may be based on time of last new path discovered or whatever. + + Should return False if self.sync_dir is None. + """ + if not self.sync_dir: + return False + + # if time.time() - self.start_time > 20: + # return True + return False + + + def populate_stats(self): + """ + Parses out stats generated by fuzzer output. Should be implemented by user, and can return custom + feedback. + """ + crashes: int = len(os.listdir(self.crash_dir)) + if os.path.isfile(os.path.join(self.crash_dir, "README.txt")): + crashes -= 1 + self.stats["unique_crashes"] = str(crashes) + self.stats["start_time"] = str(int(self.start_time)) + if self.proc: + self.stats["fuzzer_pid"] = str(self.proc.pid) + if self.sync_dir: + self.stats["sync_dir_size"] = str(len(os.listdir(self.sync_dir))) + + + def print_stats(self): + for key, value in self.stats.items(): + if value: + L.fuzz_stats("%s:%s", key, value) + L.fuzz_stats("-"*30) + + + def save_stats(self): + with open(self.stats_file, 'w') as f: + for key, value in self.stats.items(): + if value: + f.write(f"{key}:{value}\n") + + + def post_exec(self): + """ + Performs user-specified post-processing execution logic. Should be implemented by user, and can implement + things like crash triaging, testcase minimization (ie with `deepstate-reduce`), or any other manipulations + with produced testcases. + """ + # make sure that child processes are killed + self.cleanup() + + + ################################### + # Ensemble/Parallel Fuzzing methods + ################################### + + + def _sync_seeds(self, src: str, dest: str, excludes: List[str] = []) -> None: + """ + Helper that invokes rsync for convenient file syncing between two files. + + TODO(alan): implement functionality for syncing across servers. + TODO(alan): consider implementing "native" syncing alongside current "rsync mode". + + :param src: path to source queue + :param dest: path to destination queue + :param excludes: list of string patterns for paths to ignore when rsync-ing + """ + + rsync_cmd: List[str] = [ + "rsync", + "--recursive", + "--archive", + "--checksum", + "--compress", + "--ignore-existing" + ] + + # subclass should invoke with list of pattern ignores + if len(excludes) > 0: + rsync_cmd += [f"--exclude={e}" for e in excludes] + + rsync_cmd += [ + os.path.join(src, ""), # append trailing / + dest + ] + + # L.debug("rsync command: %s", rsync_cmd) + L.debug("rsync %s: from `%s` to `%s`.", self.name, src, dest) + try: + subprocess.Popen(rsync_cmd) + except subprocess.CalledProcessError as e: + raise FuzzFrontendError(f"{self.name} rsync interrupted due to exception {e}.") + + + def ensemble(self, local_queue: Optional[str] = None, global_queue: Optional[str] = None): + """ + Base method for implementing ensemble fuzzing with seed synchronization. User should + implement any additional logic for determining whether to sync/get seeds as if in event loop. + """ + + if not self.sync_dir: + L.warning("Called `ensemble`, but `--sync_dir` not provided.") + return + + global_queue = os.path.join(self.sync_dir, "queue") + global_crashes = os.path.join(self.sync_dir, "crashes") + local_queue = self.push_dir + local_crashes = self.crash_dir + + # check global queue + global_len: int = len(os.listdir(self.crash_dir)) + L.debug("Global seed queue: `%s` with %d files", global_queue, global_len) + + # update local queue with new findings + self._sync_seeds(src=self.pull_dir, dest=self.push_dir) + + # check local queue + local_len: int = len(os.listdir(self.push_dir)) + L.debug("Fuzzer local seed queue: `%s` with %d files", local_queue, local_len) + + # get seeds from local to global queue, rsync will deal with duplicates + self._sync_seeds(src=local_queue, dest=global_queue) + self._sync_seeds(src=local_crashes, dest=global_crashes) + + # push seeds from global queue to local, rsync will deal with duplicates + self._sync_seeds(src=global_queue, dest=local_queue) diff --git a/bin/deepstate/core/symex.py b/bin/deepstate/core/symex.py index 96009913..5c9754ef 100644 --- a/bin/deepstate/core/symex.py +++ b/bin/deepstate/core/symex.py @@ -1,607 +1,607 @@ -# Copyright (c) 2019 Trail of Bits, Inc. -# -# 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 logging - -import os -import struct -import argparse -import hashlib - -from deepstate import (LOG_LEVEL_INT_TO_LOGGER, - LOG_LEVEL_TRACE, LOG_LEVEL_ERROR, LOG_LEVEL_CRITICAL) -from deepstate.core.base import AnalysisBackend - - -LOGGER = logging.getLogger(__name__) - - -class TestInfo(object): - """Represents a `DeepState_TestInfo` data structure from the program, as - well as associated meta-data about the test.""" - def __init__(self, ea, name, file_name, line_number): - self.ea = ea - self.name = name - self.file_name = file_name - self.line_number = line_number - - -class Stream(object): - def __init__(self, entries): - self.entries = entries - - -class SymexFrontend(AnalysisBackend): - """Wrapper around a symbolic executor for making it easy to do common DeepState- - specific things.""" - def __init__(self): - self.num_workers: int = 1 - - def get_context(self): - raise NotImplementedError("Must be implemented by engine.") - - def create_symbol(self, name, size_in_bits): - raise NotImplementedError("Must be implemented by engine.") - - def is_symbolic(self, val): - raise NotImplementedError("Must be implemented by engine.") - - def read_uintptr_t(self, ea, concretize=True, constrain=False): - raise NotImplementedError("Must be implemented by engine.") - - def read_uint64_t(self, ea, concretize=True, constrain=False): - raise NotImplementedError("Must be implemented by engine.") - - def read_uint32_t(self, ea, concretize=True, constrain=False): - raise NotImplementedError("Must be implemented by engine.") - - def read_uint8_t(self, ea, concretize=True, constrain=False): - raise NotImplementedError("Must be implemented by engine.") - - def write_uint8_t(self, ea, val): - raise NotImplementedError("Must be implemented by engine.") - - def write_uint32_t(self, ea, val): - raise NotImplementedError("Must be implemented by engine.") - - def concretize(self, val, constrain=False): - raise NotImplementedError("Must be implemented by engine.") - - def concretize_min(self, val, constrain=False): - raise NotImplementedError("Must be implemented by engine.") - - def concretize_max(self, val, constrain=False): - raise NotImplementedError("Must be implemented by engine.") - - def concretize_many(self, val, max_num): - raise NotImplementedError("Must be implemented by engine.") - - def add_constraint(self, expr): - raise NotImplementedError("Must be implemented by engine.") - - - @classmethod - def parse_args(cls): - """Parses command-line arguments needed by symbolic engine.""" - if cls._ARGS: - return cls._ARGS - - parser = argparse.ArgumentParser( - description="Symbolically execute unit tests with {}".format(cls.NAME)) - - parser.add_argument( - "--take_over", action='store_true', - help="Explore the program starting at the `TakeOver` hook.") - - parser.add_argument( - "--klee", action='store_true', - help="Expect the test binary to use the KLEE API and use `main()` as entry point.") - - parser.add_argument( - "--verbosity", default=1, type=int, - help="Verbosity level for symbolic execution tool (default: 1, lower means less output).") - - parser.add_argument( - "-w", "--num_workers", default=1, type=int, - help="Number of worker jobs to spawn for analysis (default is 1).") - - cls.parser = parser - return super(SymexFrontend, cls).parse_args() - - - @property - def context(self): - """Gives convenient property-based access to a dictionary holding state-local variables.""" - return self.get_context() - - - def read_c_string(self, ea, concretize=True, constrain=False): - """Read a NULL-terminated string from address `ea`.""" - - chars = [] - while True: - b, ea = self.read_uint8_t(ea, concretize=concretize, constrain=constrain) - if self.is_symbolic(b): - b_maybe_nul = self.concretize_min(b) - if not b_maybe_nul: - break # Stop at the first possible NUL byte. - else: - # Concretize if it's not symbolic; we might have a concrete bitvector. - b = self.concretize(b) - if not b: - break - chars.append(b) - - next_ea = ea + len(chars) + 1 - if concretize: - return "".join(chr(b) for b in chars), next_ea - else: - return chars, next_ea - - - def _read_test_info(self, ea): - """Read in a `DeepState_TestInfo` info structure from memory.""" - prev_test_ea, ea = self.read_uintptr_t(ea) - test_func_ea, ea = self.read_uintptr_t(ea) - test_name_ea, ea = self.read_uintptr_t(ea) - file_name_ea, ea = self.read_uintptr_t(ea) - file_line_num, _ = self.read_uint32_t(ea) - - if not test_func_ea or \ - not test_name_ea or \ - not file_name_ea or \ - not file_line_num: # `__LINE__` in C always starts at `1` ;-) - return None, prev_test_ea - - test_name, _ = self.read_c_string(test_name_ea) - file_name, _ = self.read_c_string(file_name_ea) - info = TestInfo(test_func_ea, test_name, file_name, file_line_num) - return info, prev_test_ea - - def _split_path(self, path): - """Split a path into all of its components.""" - parts = [] - while path: - root, ext = os.path.split(path) - if not ext: - break - path = root - parts.insert(0, ext) - return parts - - def find_test_cases(self): - """Find the test case descriptors.""" - tests = [] - info_ea, _ = self.read_uintptr_t(self.context['apis']['LastTestInfo']) - - while info_ea: - test, info_ea = self._read_test_info(info_ea) - if test: - tests.append(test) - tests.sort(key=lambda t: (t.file_name, t.line_number)) - return tests - - def read_api_table(self, ea, base = 0): - """Reads in the API table.""" - ea = ea + base - apis = {} - while True: - api_name_ea, ea = self.read_uintptr_t(ea) - api_ea, ea = self.read_uintptr_t(ea) - if not api_name_ea or not api_ea: - break - api_name, _ = self.read_c_string(api_name_ea + base) - apis[api_name] = api_ea + base - self.context['apis'] = apis - return apis - - def begin_test(self, info): - """Begin processing the test associated with `info`.""" - self.context['failed'] = False - self.context['crashed'] = False - self.context['abandoned'] = False - self.context['log'] = [] - for level in LOG_LEVEL_INT_TO_LOGGER: - self.context['stream_{}'.format(level)] = [] - - self.context['info'] = info - self.log_message(LOG_LEVEL_TRACE, "Running {} from {}({})".format( - info.name, info.file_name, info.line_number)) - - apis = self.context['apis'] - - # Create the symbols that feed API functions like `DeepState_Int`. - symbols = [] - for i, ea in enumerate(range(apis['InputBegin'], apis['InputEnd'])): - symbol = self.create_symbol('DEEP_INPUT_{}'.format(i), 8) - self.write_uint8_t(ea, symbol) - symbols.append(symbol) - - self.context['symbols'] = symbols - - # Create the output directory for this test case. - args = self.parse_args() - - if args.output_test_dir: - test_dir = os.path.join(args.output_test_dir, - os.path.basename(info.file_name), - info.name) - try: - os.makedirs(test_dir) - except: - pass - - if not os.path.isdir(test_dir): - LOGGER.critical("Cannot create test output directory: %s", test_dir) - - self.context['test_dir'] = test_dir - else: - LOGGER.warning("Argument `--output_test_dir` not given, will not save test cases.") - - def log_message(self, level, message): - """Add `message` to the `level`-specific log as a `Stream` object for - deferred logging (at the end of the state).""" - assert level in LOG_LEVEL_INT_TO_LOGGER - log = list(self.context['log']) # Make a shallow copy (needed for Angr). - - if isinstance(message, (str, list, tuple)): - log.append((level, Stream([(str, "%s", None, message)]))) - else: - assert isinstance(message, Stream) - log.append((level, message)) - - self.context['log'] = log - - def _concretize_bytes(self, byte_str): - """Concretize the bytes of `byte_str`.""" - new_bytes = [] - for b in byte_str: - if isinstance(b, str): - new_bytes.extend(ord(bn) for bn in b) - elif isinstance(b, (int)): - new_bytes.append(b) - elif isinstance(b, (list, tuple)): - new_bytes.extend(self._concretize_bytes(b)) - else: - new_bytes.append(self.concretize(b, constrain=True)) - return new_bytes - - def _stream_to_message(self, stream): - """Convert a `Stream` object into a single string message representing - the concatenation of all formatted stream entries.""" - assert isinstance(stream, Stream) - message = [] - for val_type, format_str, unpack_str, val_bytes in stream.entries: - val_bytes = self._concretize_bytes(val_bytes) - if val_type == str: - val = "".join(chr(b) for b in val_bytes) - elif val_type == float: - data = struct.pack('BBBBBBBB', *val_bytes) - val = struct.unpack("d", data) - else: - assert val_type == int - - # TODO(pag): I am pretty sure that this is wrong for big-endian. - data = struct.pack('BBBBBBBB', *val_bytes) - val = struct.unpack(unpack_str, data[:struct.calcsize(unpack_str)])[0] - - if type(val) == bytes: - val = val.decode('unicode_escape') - - # Remove length specifiers that are not supported. - format_str = format_str.replace('l', '') - format_str = format_str.replace('h', '') - format_str = format_str.replace('z', '') - format_str = format_str.replace('t', '') - - message.append(format_str % val) - - res = "".join(message) - res.rstrip(" \t\r\n\0") - return res - - def _save_test(self, info, input_bytes): - """Save the concretized bytes to a file.""" - if not len(input_bytes) or 'test_dir' not in self.context: - return - - if self.context['abandoned']: - return - - test_dir = self.context['test_dir'] - md5 = hashlib.md5() - md5.update(input_bytes) - test_name = md5.hexdigest() - - passing = False - if self.context['failed']: - test_name += ".fail" - elif self.context['crashed']: - test_name += ".crash" - else: - test_name += ".pass" - passing = True - - test_file = os.path.join(test_dir, test_name) - try: - with open(test_file, "wb") as f: - f.write(input_bytes) - except: - LOGGER.critical("Error saving input to %s", test_file) - - if not passing: - LOGGER.info("Saved test case in file %s", test_file) - else: - LOGGER.trace("Saved test case in file %s", test_file) - - def report(self): - """Report on the pass/fail status of a test case, and dump its log.""" - info = self.context['info'] - apis = self.context['apis'] - input_length, _ = self.read_uint32_t(apis['InputIndex']) - - symbols = self.context['symbols'] - - # Check to see if the test case actually read too many symbols. - if input_length > len(symbols): - LOGGER.critical("Test overflowed DeepState_Input symbol array") - input_length = len(symbols) - - # Concretize the used symbols. We use `concretize_min` so that we're more - # likely to get the same concrete byte values across different tools (e.g. - # Manticore, Angr). - input_bytes = bytearray() - for i in range(input_length): - b = self.concretize_min(symbols[i], constrain=True) - input_bytes.append(b) - - # Print out each log entry. - for level, stream in self.context['log']: - logger = LOG_LEVEL_INT_TO_LOGGER[level] - logger(self._stream_to_message(stream)) - - # Print out the first few input bytes to be helpful. - lots_of_bytes = len(input_bytes) > 20 and " ..." or "" - bytes_to_show = min(20, len(input_bytes)) - LOGGER.trace("Input: %s%s", - " ".join("{:02x}".format(b) for b in input_bytes[:bytes_to_show]), - lots_of_bytes) - - self._save_test(info, input_bytes) - - def pass_test(self): - """Notify the symbolic executor that this test has passed and stop - executing the current state.""" - pass - - def crash_test(self): - """Notify the symbolic executor that this test has crashed and stop - executing the current state.""" - self.context['crashed'] = True - - def fail_test(self): - """Notify the symbolic executor that this test has failed and stop - executing the current state.""" - self.context['failed'] = True - - def abandon_test(self): - """Notify the symbolic executor that this test has been abandoned due to - some critical error and stop executing the current state.""" - self.context['abandoned'] = True - - def api_min_uint(self, arg): - """Implements the `DeepState_MinUInt` API function, which returns the - minimum satisfiable value for `arg`.""" - return self.concretize_min(arg, constrain=False) - - def api_max_uint(self, arg): - """Implements the `DeepState_MaxUInt` API function, which returns the - minimum satisfiable value for `arg`.""" - return self.concretize_max(arg, constrain=False) - - def api_is_symbolic_uint(self, arg): - """Implements the `DeepState_IsSymbolicUInt` API, which returns whether or - not a given value is symbolic.""" - solutions = self.concretize_many(arg, 2) - if not solutions: - return 0 - elif 1 == len(solutions): - if self.is_symbolic(arg): - self.add_constraint(arg == solutions[0]) - return 0 - else: - return 1 - - def api_assume(self, arg, expr_ea, file_ea, line): - """Implements the `DeepState_Assume` API function, which injects a - constraint into the solver.""" - if not self.is_symbolic(arg): - concrete_arg = self.concretize(arg) - if concrete_arg == 0: - self.abandon_test() - else: - return - - expr_ea = self.concretize(expr_ea, constrain=True) - file_ea = self.concretize(file_ea, constrain=True) - constraint = arg != 0 - if not self.add_constraint(constraint): - expr, _ = self.read_c_string(expr_ea, concretize=False) - file, _ = self.read_c_string(file_ea, concretize=False) - line = self.concretize(line, constrain=True) - self.log_message( - LOG_LEVEL_CRITICAL, "Failed to add assumption {} in {}:{}".format( - expr, file, line)) - self.abandon_test() - - def api_concretize_data(self, begin_ea, end_ea): - """Implements the `Deepstate_ConcretizeData` API function, which lets the - programmer concretize some data in the exclusive range - `[begin_ea, end_ea)`.""" - begin_ea = self.concretize(begin_ea, constrain=True) - end_ea = self.concretize(end_ea, constrain=True) - if end_ea < begin_ea: - self.log_message( - LOG_LEVEL_CRITICAL, - "Invalid range [{:x}, {:x}) to McTest_Concretize".format( - begin_ea, end_ea)) - self.abandon_test() - - for i in range(end_ea - begin_ea): - val, _ = self.read_uint8_t(begin_ea + i, concretize=True, constrain=True) - self.write_uint8_t(begin_ea + i, val) - - return begin_ea - - def api_concretize_cstr(self, begin_ea): - """Implements the `Deepstate_ConcretizeCStr` API function, which lets the - programmer concretize a NUL-terminated string starting at `begin_ea`.""" - begin_ea = self.concretize(begin_ea, constrain=True) - str_bytes, end_ea = self.read_c_string(begin_ea, concretize=False) - next_ea = begin_ea - for i, b in enumerate(str_bytes): - b = self.concretize_min(b, constrain=True) - next_ea = self.write_uint8_t(begin_ea + i, b) - self.write_uint8_t(next_ea, 0) - return begin_ea - - def api_pass(self): - """Implements the `DeepState_Pass` API function, which marks this test as - having passed, and stops further execution.""" - if self.context['failed']: - self.api_fail() - else: - info = self.context['info'] - self.log_message(LOG_LEVEL_TRACE, "Passed: {}".format(info.name)) - self.pass_test() - - def api_crash(self): - """Implements the `DeepState_Crash` API function, which marks this test as - having crashed, and stops further execution.""" - self.context['crashed'] = True - info = self.context['info'] - self.log_message(LOG_LEVEL_ERROR, "Crashed: {}".format(info.name)) - self.crash_test() - - def api_fail(self): - """Implements the `DeepState_Fail` API function, which marks this test as - having failed, and stops further execution.""" - self.context['failed'] = True - info = self.context['info'] - self.log_message(LOG_LEVEL_ERROR, "Failed: {}".format(info.name)) - self.fail_test() - - def api_soft_fail(self): - """Implements the `DeepState_SoftFail` API function, which marks this test - as having failed, but lets execution continue.""" - self.context['failed'] = True - - def api_abandon(self, arg): - """Implements the `DeepState_Abandon` API function, which marks this test - as having aborted due to some unrecoverable error.""" - info = self.context['info'] - ea = self.concretize(arg, constrain=True) - self.log_message(LOG_LEVEL_CRITICAL, self.read_c_string(ea)[0]) - self.log_message(LOG_LEVEL_CRITICAL, "Abandoned: {}".format(info.name)) - self.abandon_test() - - def api_log(self, level, ea): - """Implements the `DeepState_Log` API function, which prints a C string - to a specific log level.""" - self.api_log_stream(level) - - level = self.concretize(level, constrain=True) - ea = self.concretize(ea, constrain=True) - assert level in LOG_LEVEL_INT_TO_LOGGER - self.log_message(level, self.read_c_string(ea, concretize=False)[0]) - - if level == LOG_LEVEL_CRITICAL: - self.api_fail() - elif level == LOG_LEVEL_ERROR: - self.api_soft_fail() - - def _api_stream_int_float(self, level, format_ea, unpack_ea, uint64_ea, - val_type): - """Read the format information and int or float value data from memory - and record it into a stream.""" - level = self.concretize(level, constrain=True) - assert level in LOG_LEVEL_INT_TO_LOGGER - - format_ea = self.concretize(format_ea, constrain=True) - unpack_ea = self.concretize(unpack_ea, constrain=True) - uint64_ea = self.concretize(uint64_ea, constrain=True) - - format_str = self.read_c_string(format_ea)[0] - unpack_str = self.read_c_string(unpack_ea)[0] - uint64_bytes = [] - for i in range(8): - b, _ = self.read_uint8_t(uint64_ea + i, concretize=False) - uint64_bytes.append(b) - - stream_id = 'stream_{}'.format(level) - stream = list(self.context[stream_id]) - stream.append((val_type, format_str, unpack_str, uint64_bytes)) - self.context[stream_id] = stream - - def api_stream_int(self, level, format_ea, unpack_ea, uint64_ea): - """Implements the `_DeepState_StreamInt`, which streams an integer into a - holding buffer for the log.""" - return self._api_stream_int_float(level, format_ea, unpack_ea, - uint64_ea, int) - - def api_stream_float(self, level, format_ea, unpack_ea, double_ea): - """Implements the `_DeepState_StreamFloat`, which streams an integer into a - holding buffer for the log.""" - return self._api_stream_int_float(level, format_ea, unpack_ea, - double_ea, float) - - def api_stream_string(self, level, format_ea, str_ea): - """Implements the `_DeepState_StreamString`, which streams a C-string into a - holding buffer for the log.""" - level = self.concretize(level, constrain=True) - assert level in LOG_LEVEL_INT_TO_LOGGER - - format_ea = self.concretize(format_ea, constrain=True) - str_ea = self.concretize(str_ea, constrain=True) - format_str = self.read_c_string(format_ea)[0] - print_str = self.read_c_string(str_ea, concretize=False)[0] - - stream_id = 'stream_{}'.format(level) - stream = list(self.context[stream_id]) - stream.append((str, format_str, None, print_str)) - self.context[stream_id] = stream - - def api_clear_stream(self, level): - """Implements DeepState_ClearStream, which clears the contents of a stream - for level `level`.""" - level = self.concretize(level, constrain=True) - assert level in LOG_LEVEL_INT_TO_LOGGER - stream_id = 'stream_{}'.format(level) - self.context[stream_id] = [] - - def api_log_stream(self, level): - """Implements DeepState_LogStream, which converts the contents of a stream - for level `level` into a log for level `level`.""" - level = self.concretize(level, constrain=True) - assert level in LOG_LEVEL_INT_TO_LOGGER - stream_id = 'stream_{}'.format(level) - stream = self.context[stream_id] - if len(stream): - self.context[stream_id] = [] - self.log_message(level, Stream(stream)) - - if level == LOG_LEVEL_CRITICAL: - self.api_fail() - elif level == LOG_LEVEL_ERROR: - self.api_soft_fail() +# Copyright (c) 2019 Trail of Bits, Inc. +# +# 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 logging + +import os +import struct +import argparse +import hashlib + +from deepstate import (LOG_LEVEL_INT_TO_LOGGER, + LOG_LEVEL_TRACE, LOG_LEVEL_ERROR, LOG_LEVEL_CRITICAL) +from deepstate.core.base import AnalysisBackend + + +LOGGER = logging.getLogger(__name__) + + +class TestInfo(object): + """Represents a `DeepState_TestInfo` data structure from the program, as + well as associated meta-data about the test.""" + def __init__(self, ea, name, file_name, line_number): + self.ea = ea + self.name = name + self.file_name = file_name + self.line_number = line_number + + +class Stream(object): + def __init__(self, entries): + self.entries = entries + + +class SymexFrontend(AnalysisBackend): + """Wrapper around a symbolic executor for making it easy to do common DeepState- + specific things.""" + def __init__(self): + self.num_workers: int = 1 + + def get_context(self): + raise NotImplementedError("Must be implemented by engine.") + + def create_symbol(self, name, size_in_bits): + raise NotImplementedError("Must be implemented by engine.") + + def is_symbolic(self, val): + raise NotImplementedError("Must be implemented by engine.") + + def read_uintptr_t(self, ea, concretize=True, constrain=False): + raise NotImplementedError("Must be implemented by engine.") + + def read_uint64_t(self, ea, concretize=True, constrain=False): + raise NotImplementedError("Must be implemented by engine.") + + def read_uint32_t(self, ea, concretize=True, constrain=False): + raise NotImplementedError("Must be implemented by engine.") + + def read_uint8_t(self, ea, concretize=True, constrain=False): + raise NotImplementedError("Must be implemented by engine.") + + def write_uint8_t(self, ea, val): + raise NotImplementedError("Must be implemented by engine.") + + def write_uint32_t(self, ea, val): + raise NotImplementedError("Must be implemented by engine.") + + def concretize(self, val, constrain=False): + raise NotImplementedError("Must be implemented by engine.") + + def concretize_min(self, val, constrain=False): + raise NotImplementedError("Must be implemented by engine.") + + def concretize_max(self, val, constrain=False): + raise NotImplementedError("Must be implemented by engine.") + + def concretize_many(self, val, max_num): + raise NotImplementedError("Must be implemented by engine.") + + def add_constraint(self, expr): + raise NotImplementedError("Must be implemented by engine.") + + + @classmethod + def parse_args(cls): + """Parses command-line arguments needed by symbolic engine.""" + if cls._ARGS: + return cls._ARGS + + parser = argparse.ArgumentParser( + description="Symbolically execute unit tests with {}".format(cls.NAME)) + + parser.add_argument( + "--take_over", action='store_true', + help="Explore the program starting at the `TakeOver` hook.") + + parser.add_argument( + "--klee", action='store_true', + help="Expect the test binary to use the KLEE API and use `main()` as entry point.") + + parser.add_argument( + "--verbosity", default=1, type=int, + help="Verbosity level for symbolic execution tool (default: 1, lower means less output).") + + parser.add_argument( + "-w", "--num_workers", default=1, type=int, + help="Number of worker jobs to spawn for analysis (default is 1).") + + cls.parser = parser + return super(SymexFrontend, cls).parse_args() + + + @property + def context(self): + """Gives convenient property-based access to a dictionary holding state-local variables.""" + return self.get_context() + + + def read_c_string(self, ea, concretize=True, constrain=False): + """Read a NULL-terminated string from address `ea`.""" + + chars = [] + while True: + b, ea = self.read_uint8_t(ea, concretize=concretize, constrain=constrain) + if self.is_symbolic(b): + b_maybe_nul = self.concretize_min(b) + if not b_maybe_nul: + break # Stop at the first possible NUL byte. + else: + # Concretize if it's not symbolic; we might have a concrete bitvector. + b = self.concretize(b) + if not b: + break + chars.append(b) + + next_ea = ea + len(chars) + 1 + if concretize: + return "".join(chr(b) for b in chars), next_ea + else: + return chars, next_ea + + + def _read_test_info(self, ea): + """Read in a `DeepState_TestInfo` info structure from memory.""" + prev_test_ea, ea = self.read_uintptr_t(ea) + test_func_ea, ea = self.read_uintptr_t(ea) + test_name_ea, ea = self.read_uintptr_t(ea) + file_name_ea, ea = self.read_uintptr_t(ea) + file_line_num, _ = self.read_uint32_t(ea) + + if not test_func_ea or \ + not test_name_ea or \ + not file_name_ea or \ + not file_line_num: # `__LINE__` in C always starts at `1` ;-) + return None, prev_test_ea + + test_name, _ = self.read_c_string(test_name_ea) + file_name, _ = self.read_c_string(file_name_ea) + info = TestInfo(test_func_ea, test_name, file_name, file_line_num) + return info, prev_test_ea + + def _split_path(self, path): + """Split a path into all of its components.""" + parts = [] + while path: + root, ext = os.path.split(path) + if not ext: + break + path = root + parts.insert(0, ext) + return parts + + def find_test_cases(self): + """Find the test case descriptors.""" + tests = [] + info_ea, _ = self.read_uintptr_t(self.context['apis']['LastTestInfo']) + + while info_ea: + test, info_ea = self._read_test_info(info_ea) + if test: + tests.append(test) + tests.sort(key=lambda t: (t.file_name, t.line_number)) + return tests + + def read_api_table(self, ea, base = 0): + """Reads in the API table.""" + ea = ea + base + apis = {} + while True: + api_name_ea, ea = self.read_uintptr_t(ea) + api_ea, ea = self.read_uintptr_t(ea) + if not api_name_ea or not api_ea: + break + api_name, _ = self.read_c_string(api_name_ea + base) + apis[api_name] = api_ea + base + self.context['apis'] = apis + return apis + + def begin_test(self, info): + """Begin processing the test associated with `info`.""" + self.context['failed'] = False + self.context['crashed'] = False + self.context['abandoned'] = False + self.context['log'] = [] + for level in LOG_LEVEL_INT_TO_LOGGER: + self.context['stream_{}'.format(level)] = [] + + self.context['info'] = info + self.log_message(LOG_LEVEL_TRACE, "Running {} from {}({})".format( + info.name, info.file_name, info.line_number)) + + apis = self.context['apis'] + + # Create the symbols that feed API functions like `DeepState_Int`. + symbols = [] + for i, ea in enumerate(range(apis['InputBegin'], apis['InputEnd'])): + symbol = self.create_symbol('DEEP_INPUT_{}'.format(i), 8) + self.write_uint8_t(ea, symbol) + symbols.append(symbol) + + self.context['symbols'] = symbols + + # Create the output directory for this test case. + args = self.parse_args() + + if args.output_test_dir: + test_dir = os.path.join(args.output_test_dir, + os.path.basename(info.file_name), + info.name) + try: + os.makedirs(test_dir) + except: + pass + + if not os.path.isdir(test_dir): + LOGGER.critical("Cannot create test output directory: %s", test_dir) + + self.context['test_dir'] = test_dir + else: + LOGGER.warning("Argument `--output_test_dir` not given, will not save test cases.") + + def log_message(self, level, message): + """Add `message` to the `level`-specific log as a `Stream` object for + deferred logging (at the end of the state).""" + assert level in LOG_LEVEL_INT_TO_LOGGER + log = list(self.context['log']) # Make a shallow copy (needed for Angr). + + if isinstance(message, (str, list, tuple)): + log.append((level, Stream([(str, "%s", None, message)]))) + else: + assert isinstance(message, Stream) + log.append((level, message)) + + self.context['log'] = log + + def _concretize_bytes(self, byte_str): + """Concretize the bytes of `byte_str`.""" + new_bytes = [] + for b in byte_str: + if isinstance(b, str): + new_bytes.extend(ord(bn) for bn in b) + elif isinstance(b, (int)): + new_bytes.append(b) + elif isinstance(b, (list, tuple)): + new_bytes.extend(self._concretize_bytes(b)) + else: + new_bytes.append(self.concretize(b, constrain=True)) + return new_bytes + + def _stream_to_message(self, stream): + """Convert a `Stream` object into a single string message representing + the concatenation of all formatted stream entries.""" + assert isinstance(stream, Stream) + message = [] + for val_type, format_str, unpack_str, val_bytes in stream.entries: + val_bytes = self._concretize_bytes(val_bytes) + if val_type == str: + val = "".join(chr(b) for b in val_bytes) + elif val_type == float: + data = struct.pack('BBBBBBBB', *val_bytes) + val = struct.unpack("d", data) + else: + assert val_type == int + + # TODO(pag): I am pretty sure that this is wrong for big-endian. + data = struct.pack('BBBBBBBB', *val_bytes) + val = struct.unpack(unpack_str, data[:struct.calcsize(unpack_str)])[0] + + if type(val) == bytes: + val = val.decode('unicode_escape') + + # Remove length specifiers that are not supported. + format_str = format_str.replace('l', '') + format_str = format_str.replace('h', '') + format_str = format_str.replace('z', '') + format_str = format_str.replace('t', '') + + message.append(format_str % val) + + res = "".join(message) + res.rstrip(" \t\r\n\0") + return res + + def _save_test(self, info, input_bytes): + """Save the concretized bytes to a file.""" + if not len(input_bytes) or 'test_dir' not in self.context: + return + + if self.context['abandoned']: + return + + test_dir = self.context['test_dir'] + md5 = hashlib.md5() + md5.update(input_bytes) + test_name = md5.hexdigest() + + passing = False + if self.context['failed']: + test_name += ".fail" + elif self.context['crashed']: + test_name += ".crash" + else: + test_name += ".pass" + passing = True + + test_file = os.path.join(test_dir, test_name) + try: + with open(test_file, "wb") as f: + f.write(input_bytes) + except: + LOGGER.critical("Error saving input to %s", test_file) + + if not passing: + LOGGER.info("Saved test case in file %s", test_file) + else: + LOGGER.trace("Saved test case in file %s", test_file) + + def report(self): + """Report on the pass/fail status of a test case, and dump its log.""" + info = self.context['info'] + apis = self.context['apis'] + input_length, _ = self.read_uint32_t(apis['InputIndex']) + + symbols = self.context['symbols'] + + # Check to see if the test case actually read too many symbols. + if input_length > len(symbols): + LOGGER.critical("Test overflowed DeepState_Input symbol array") + input_length = len(symbols) + + # Concretize the used symbols. We use `concretize_min` so that we're more + # likely to get the same concrete byte values across different tools (e.g. + # Manticore, Angr). + input_bytes = bytearray() + for i in range(input_length): + b = self.concretize_min(symbols[i], constrain=True) + input_bytes.append(b) + + # Print out each log entry. + for level, stream in self.context['log']: + logger = LOG_LEVEL_INT_TO_LOGGER[level] + logger(self._stream_to_message(stream)) + + # Print out the first few input bytes to be helpful. + lots_of_bytes = len(input_bytes) > 20 and " ..." or "" + bytes_to_show = min(20, len(input_bytes)) + LOGGER.trace("Input: %s%s", + " ".join("{:02x}".format(b) for b in input_bytes[:bytes_to_show]), + lots_of_bytes) + + self._save_test(info, input_bytes) + + def pass_test(self): + """Notify the symbolic executor that this test has passed and stop + executing the current state.""" + pass + + def crash_test(self): + """Notify the symbolic executor that this test has crashed and stop + executing the current state.""" + self.context['crashed'] = True + + def fail_test(self): + """Notify the symbolic executor that this test has failed and stop + executing the current state.""" + self.context['failed'] = True + + def abandon_test(self): + """Notify the symbolic executor that this test has been abandoned due to + some critical error and stop executing the current state.""" + self.context['abandoned'] = True + + def api_min_uint(self, arg): + """Implements the `DeepState_MinUInt` API function, which returns the + minimum satisfiable value for `arg`.""" + return self.concretize_min(arg, constrain=False) + + def api_max_uint(self, arg): + """Implements the `DeepState_MaxUInt` API function, which returns the + minimum satisfiable value for `arg`.""" + return self.concretize_max(arg, constrain=False) + + def api_is_symbolic_uint(self, arg): + """Implements the `DeepState_IsSymbolicUInt` API, which returns whether or + not a given value is symbolic.""" + solutions = self.concretize_many(arg, 2) + if not solutions: + return 0 + elif 1 == len(solutions): + if self.is_symbolic(arg): + self.add_constraint(arg == solutions[0]) + return 0 + else: + return 1 + + def api_assume(self, arg, expr_ea, file_ea, line): + """Implements the `DeepState_Assume` API function, which injects a + constraint into the solver.""" + if not self.is_symbolic(arg): + concrete_arg = self.concretize(arg) + if concrete_arg == 0: + self.abandon_test() + else: + return + + expr_ea = self.concretize(expr_ea, constrain=True) + file_ea = self.concretize(file_ea, constrain=True) + constraint = arg != 0 + if not self.add_constraint(constraint): + expr, _ = self.read_c_string(expr_ea, concretize=False) + file, _ = self.read_c_string(file_ea, concretize=False) + line = self.concretize(line, constrain=True) + self.log_message( + LOG_LEVEL_CRITICAL, "Failed to add assumption {} in {}:{}".format( + expr, file, line)) + self.abandon_test() + + def api_concretize_data(self, begin_ea, end_ea): + """Implements the `Deepstate_ConcretizeData` API function, which lets the + programmer concretize some data in the exclusive range + `[begin_ea, end_ea)`.""" + begin_ea = self.concretize(begin_ea, constrain=True) + end_ea = self.concretize(end_ea, constrain=True) + if end_ea < begin_ea: + self.log_message( + LOG_LEVEL_CRITICAL, + "Invalid range [{:x}, {:x}) to McTest_Concretize".format( + begin_ea, end_ea)) + self.abandon_test() + + for i in range(end_ea - begin_ea): + val, _ = self.read_uint8_t(begin_ea + i, concretize=True, constrain=True) + self.write_uint8_t(begin_ea + i, val) + + return begin_ea + + def api_concretize_cstr(self, begin_ea): + """Implements the `Deepstate_ConcretizeCStr` API function, which lets the + programmer concretize a NUL-terminated string starting at `begin_ea`.""" + begin_ea = self.concretize(begin_ea, constrain=True) + str_bytes, end_ea = self.read_c_string(begin_ea, concretize=False) + next_ea = begin_ea + for i, b in enumerate(str_bytes): + b = self.concretize_min(b, constrain=True) + next_ea = self.write_uint8_t(begin_ea + i, b) + self.write_uint8_t(next_ea, 0) + return begin_ea + + def api_pass(self): + """Implements the `DeepState_Pass` API function, which marks this test as + having passed, and stops further execution.""" + if self.context['failed']: + self.api_fail() + else: + info = self.context['info'] + self.log_message(LOG_LEVEL_TRACE, "Passed: {}".format(info.name)) + self.pass_test() + + def api_crash(self): + """Implements the `DeepState_Crash` API function, which marks this test as + having crashed, and stops further execution.""" + self.context['crashed'] = True + info = self.context['info'] + self.log_message(LOG_LEVEL_ERROR, "Crashed: {}".format(info.name)) + self.crash_test() + + def api_fail(self): + """Implements the `DeepState_Fail` API function, which marks this test as + having failed, and stops further execution.""" + self.context['failed'] = True + info = self.context['info'] + self.log_message(LOG_LEVEL_ERROR, "Failed: {}".format(info.name)) + self.fail_test() + + def api_soft_fail(self): + """Implements the `DeepState_SoftFail` API function, which marks this test + as having failed, but lets execution continue.""" + self.context['failed'] = True + + def api_abandon(self, arg): + """Implements the `DeepState_Abandon` API function, which marks this test + as having aborted due to some unrecoverable error.""" + info = self.context['info'] + ea = self.concretize(arg, constrain=True) + self.log_message(LOG_LEVEL_CRITICAL, self.read_c_string(ea)[0]) + self.log_message(LOG_LEVEL_CRITICAL, "Abandoned: {}".format(info.name)) + self.abandon_test() + + def api_log(self, level, ea): + """Implements the `DeepState_Log` API function, which prints a C string + to a specific log level.""" + self.api_log_stream(level) + + level = self.concretize(level, constrain=True) + ea = self.concretize(ea, constrain=True) + assert level in LOG_LEVEL_INT_TO_LOGGER + self.log_message(level, self.read_c_string(ea, concretize=False)[0]) + + if level == LOG_LEVEL_CRITICAL: + self.api_fail() + elif level == LOG_LEVEL_ERROR: + self.api_soft_fail() + + def _api_stream_int_float(self, level, format_ea, unpack_ea, uint64_ea, + val_type): + """Read the format information and int or float value data from memory + and record it into a stream.""" + level = self.concretize(level, constrain=True) + assert level in LOG_LEVEL_INT_TO_LOGGER + + format_ea = self.concretize(format_ea, constrain=True) + unpack_ea = self.concretize(unpack_ea, constrain=True) + uint64_ea = self.concretize(uint64_ea, constrain=True) + + format_str = self.read_c_string(format_ea)[0] + unpack_str = self.read_c_string(unpack_ea)[0] + uint64_bytes = [] + for i in range(8): + b, _ = self.read_uint8_t(uint64_ea + i, concretize=False) + uint64_bytes.append(b) + + stream_id = 'stream_{}'.format(level) + stream = list(self.context[stream_id]) + stream.append((val_type, format_str, unpack_str, uint64_bytes)) + self.context[stream_id] = stream + + def api_stream_int(self, level, format_ea, unpack_ea, uint64_ea): + """Implements the `_DeepState_StreamInt`, which streams an integer into a + holding buffer for the log.""" + return self._api_stream_int_float(level, format_ea, unpack_ea, + uint64_ea, int) + + def api_stream_float(self, level, format_ea, unpack_ea, double_ea): + """Implements the `_DeepState_StreamFloat`, which streams an integer into a + holding buffer for the log.""" + return self._api_stream_int_float(level, format_ea, unpack_ea, + double_ea, float) + + def api_stream_string(self, level, format_ea, str_ea): + """Implements the `_DeepState_StreamString`, which streams a C-string into a + holding buffer for the log.""" + level = self.concretize(level, constrain=True) + assert level in LOG_LEVEL_INT_TO_LOGGER + + format_ea = self.concretize(format_ea, constrain=True) + str_ea = self.concretize(str_ea, constrain=True) + format_str = self.read_c_string(format_ea)[0] + print_str = self.read_c_string(str_ea, concretize=False)[0] + + stream_id = 'stream_{}'.format(level) + stream = list(self.context[stream_id]) + stream.append((str, format_str, None, print_str)) + self.context[stream_id] = stream + + def api_clear_stream(self, level): + """Implements DeepState_ClearStream, which clears the contents of a stream + for level `level`.""" + level = self.concretize(level, constrain=True) + assert level in LOG_LEVEL_INT_TO_LOGGER + stream_id = 'stream_{}'.format(level) + self.context[stream_id] = [] + + def api_log_stream(self, level): + """Implements DeepState_LogStream, which converts the contents of a stream + for level `level` into a log for level `level`.""" + level = self.concretize(level, constrain=True) + assert level in LOG_LEVEL_INT_TO_LOGGER + stream_id = 'stream_{}'.format(level) + stream = self.context[stream_id] + if len(stream): + self.context[stream_id] = [] + self.log_message(level, Stream(stream)) + + if level == LOG_LEVEL_CRITICAL: + self.api_fail() + elif level == LOG_LEVEL_ERROR: + self.api_soft_fail() diff --git a/bin/deepstate/executors/auxiliary/ensembler.py b/bin/deepstate/executors/auxiliary/ensembler.py index 03f19210..06d75b46 100644 --- a/bin/deepstate/executors/auxiliary/ensembler.py +++ b/bin/deepstate/executors/auxiliary/ensembler.py @@ -1,387 +1,387 @@ -#!/usr/bin/env python3.6 -# Copyright (c) 2019 Trail of Bits, Inc. -# -# 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 logging - -import os -import sys -import time -import string -import random -import argparse -import multiprocessing - -from multiprocessing import Process -from collections import defaultdict - -from deepstate.core.fuzz import FuzzerFrontend -from deepstate.executors.fuzz.afl import AFL -from deepstate.executors.fuzz.honggfuzz import Honggfuzz -from deepstate.executors.fuzz.angora import Angora -from deepstate.executors.fuzz.eclipser import Eclipser - - -L = logging.getLogger(__name__) - - -class Ensembler(FuzzerFrontend): - """ - Ensembler is the ensemble-based fuzzer that orchestrates and invokes fuzzer frontends, while also - supporting seed synchronization between those frontends. It initializes a set of global input args - for each frontend, performs an "ensemble compile", and spawns fuzzers in parallel while maintaining - seed synchronization between them. - """ - - NAME = "Ensembler" - EXECUTABLES = {"FUZZER": "deepstate-ensembler"} - - @classmethod - def parse_args(cls): - parser = argparse.ArgumentParser(description="Ensemble-based fuzzer executor for DeepState") - test_group = parser.add_mutually_exclusive_group(required=True) - - # Mutually exclusive target options - test_group.add_argument("--test", type=str, \ - help="Path to test case harness for compilation and instrumentation.") - - test_group.add_argument("--test_dir", type=str, \ - help="Path to existing workspace directory with compiled and instrumented binaries.") - - # Compilation options - parser.add_argument("-a", "--compiler_args", type=str, \ - help="Compiler linker arguments for test harness, if provided as argument.") - - parser.add_argument("--ignore_calls", type=str, \ - help="Path to static/shared libraries (colon seperated) to blackbox for taint analysis.") - - parser.add_argument("-w", "--workspace", type=str, default="ensemble_bins", \ - help="Path to workspace to store compiled and instrumented binaries (default is `ensemble_bins`).") - - # Ensembler execution options - parser.add_argument("-n", "--num_cores", type=int, default=multiprocessing.cpu_count(), \ - help="Override number of cores to use.") - - parser.add_argument("--no_global", action="store_true", \ - help="If set, disable global ensembler output, and instead report individual fuzzer stats.") - - # TODO(alan): other execution options - - #parser.add_argument("--fuzzers", type=str, \ - # help="Comma-separated string of fuzzers to ensemble with (overrides default ensemble).") - - #parser.add_argument("--abort_on_crash", action="store_true", \ - # help="Stop ensembler when any base fuzzer returns a crash.") - - cls.parser = parser - super(Ensembler, cls).parse_args() - - - def pre_exec(self): - """ - Implements pre_exec method from frontend superclass, and does sanity-checking on - parsed arguments before we can go ahead and provision an environment for ensemble fuzzing. - """ - - # `--fuzzer_help` equivalent to `--help` - if self.fuzzer_help: - self.parser.print_help() - - # ignore compiler-related arguments if not necessary - if self.test_dir and (self.ignore_calls or self.compiler_args): - L.info("Ignoring --ignore_calls and/or --compiler_args arguments passed") - - # initial path check - _test = self.test if not self.test_dir else self.test_dir - if not os.path.exists(_test): - L.error("Target path `%s` does not exist. Exiting.", _test) - sys.exit(1) - - if not os.path.isdir(self.input_seeds): - L.error("Input seeds directory `%s` does not exist. Exiting.", self.input_seeds) - sys.exit(1) - - if not os.path.isdir(self.output_test_dir): - L.warn("Output directory does not exist. Creating.") - os.mkdir(self.output_test_dir) - - if not self.sync_dir: - L.warn("No seed synchronization dir specified, using `sync`.") - self.sync_dir = "sync" - - sync_dir = self.output_test_dir + "/" + self.sync_dir - if not os.path.isdir(sync_dir): - L.warn("Sync directory does not exist. Creating.") - os.mkdir(sync_dir) - elif os.path.isdir(sync_dir) and len([f for f in os.listdir(sync_dir)]) != 0: - L.error("Sync directory exists and is not empty. Exiting.") - sys.exit(1) - - - @staticmethod - def _init_fuzzers(ret_all=False): - """ - Initialize a pre-defined ensemble of fuzzer objects. Return all subclasses if - param is set. - - Default fuzzer ensemble (four cores): - afl,honggfuzz,angora,eclipser - - """ - if ret_all: - return [subclass() for subclass in FuzzerFrontend.__subclasses__()] - else: - return [AFL(), Honggfuzz(), Angora(), Eclipser()] - - - def _get_tests(self, tests): - """ - Given a workspace path, retrieve testcases and map to specific fuzzer. We map - based on the condition that the generated test binary contains an extension - denoting the fuzzer name. - - :param tests: list of paths to workspace with already-compiled target binaries - """ - - def _get_fuzzer(test): - ext = test.split(".")[-1] - if ext in ["fast", "taint"]: - return "angora" - elif ext == "hfuzz": - return "honggfuzz" - return ext.lower() - - fuzz_map = defaultdict(list) - for test in tests: - for fuzzer in self.fuzzers: - if str(fuzzer).lower() == _get_fuzzer(test): - fuzz_map[fuzzer].append(test) - - L.debug("Fuzzer and corresponding test cases: %s", fuzz_map) - return fuzz_map - - - def provision(self): - """ - Initializes our ensemble of fuzzers, and creates a workspace with instrumented - harness binaries, if necessary. - """ - - # manually call pre_exec (we don't use frontend's runner routine) before provisioning - self.pre_exec() - - # initialize target - test str if user specified a harness, or a list to already-compiled binaries - target = self.test if not self.test_dir else list([f for f in os.listdir(self.test_dir)]) - L.info("Provisioning environment with target `%s`", target) - - self.fuzzers = list(self._init_fuzzers()) - L.debug("Fuzzers for ensembling: %s", self.fuzzers) - - # given a path to a DeepState harness, provision/compile, and retrieve test bins - if isinstance(target, str): - L.info("Detected source target. Compiling and then retrieving harnesses from workspace.") - self.targets = self._get_tests(self._provision_workspace(target)) - - # given a list of paths from a workspace, instantiate normally - elif isinstance(target, list): - L.info("Detected workspace target. Retrieving harnesses from workspace.") - self.targets = self._get_tests(target) - - L.debug("Target for analysis: %s", self.targets) - - - def _provision_workspace(self, test_case): - """ - Given a testcase source, provision a workspace with appropriate target binaries. - - :param test_case: path to uncompiled test case directory - """ - if not os.path.isdir(self.workspace): - L.info("Workspace doesn't exist. Creating.") - os.mkdir(self.workspace) - - L.info("Provisioning test case into workspace with instrumented binaries") - for fuzzer in self.fuzzers: - - test_name = self.workspace + "/" + test_case.split(".")[0] - L.debug("Compiling `%s` for fuzzer `%s`", test_name, fuzzer) - - cmd_map = { - "compile_test": test_case, - "out_test_name": test_name, - "compiler_args": self.compiler_args if self.compiler_args else None - } - - if isinstance(fuzzer, Angora): - cmd_map["mode"] = "llvm" - cmd_map["ignore_calls"] = self.ignore_calls - - fuzzer.init_from_dict(cmd_map) - - L.info("Compiling test case %s as `%s` with %s", test_case, test_name, fuzzer) - fuzzer.compile() - - return [test for test in os.listdir(self.workspace)] - - - def report(self): - """ - Global status reporter for ensemble fuzzing. We store and parse each individual - fuzzers reporter and provide a global output during fuzzer execution. - """ - while True: - - global_stats = dict() - for fuzzer in self.fuzzers: - time.sleep(self.sync_cycle) - - stats = fuzzer.reporter() - global_stats.update(stats) - - print("\n\n[\tEnsemble Fuzzer Status\t\t]\n") - for head, stat in global_stats.items(): - print(f"Total {head}\t:\t{stat}") - - - def run_ensembler(self): - """ - Bootstraps all fuzzers for ensembling with appropriate arguments, - and run fuzzers in parallel. - - TODO(alan): exit_crash arg to kill fuzzer and report when one crash is found - """ - - def _rand_id(): - return "".join(random.choice(string.ascii_uppercase + string.digits) - for _ in range(4)) - - #pool = multiprocessing.Pool(processes=self.num_cores) - procs = [] - - L.info("Initializing fuzzers for ensembling.") - - # for each fuzzer, instantiate fuzzer arguments manually using () rather than - # the parse_args() interface in each frontend. Specific fuzzers need specific options, so - # we also set those - # TODO(alan): migrate instantiation to provision or _provision_workspace - for fuzzer, binary in self.targets.items(): - fuzzer_args = { - - # default fuzzer execution related options - "timeout": self.timeout, - "binary": self.workspace + "/" + binary[0], - "input_seeds": self.input_seeds, - "output_test_dir": "{}/{}_{}_out".format(self.output_test_dir, str(fuzzer), _rand_id()), - "dictionary": None, - "max_input_size": self.max_input_size if self.max_input_size else 8192, - "mem_limit": "none", - "which_test": self.which_test, - "target_args": self.target_args, - - # set sync options for all fuzzers (TODO): configurable exec cycle - # set sync_out to output global fuzzer stats, set as default - "enable_sync": True, - "sync_cycle": self.sync_cycle, - "sync_dir": self.sync_dir, - "sync_out": not self.no_global - } - - # TODO(alan): store default dict in each fuzzer's _ARGS such that we don't need to - # manually instantiate fuzzer-specific attributes - - # manually set and override options for Angora, due to the requirement of two binaries - if isinstance(fuzzer, Angora): - fuzzer_args.update({ - "binary": next((self.workspace + "/" + b for b in binary if ".fast" in b), None), - "taint_binary": next((self.workspace + "/" + b for b in binary if ".taint" in b), None), - "no_afl": False, - "mode": "llvm", - "no_exploration": False - }) - - # manually set and override "AFL modes" that configured during execution - elif isinstance(fuzzer, AFL): - fuzzer_args.update({ - "parallel_mode": False, - "dirty_mode": False, - "dumb_mode": False, - "qemu_mode": False, - "crash_explore": False, - "file": None - }) - - # manually set Honggfuzz options - elif isinstance(fuzzer, Honggfuzz): - fuzzer_args.update({ - "iterations": None, - "persistent": False, - "no_inst": False, - "keep_output": False, - "sanitizers": False, - "clear_env": False, - "save_all": True, - "keep_aslr": False, - "perf_instr": False, - "perf_branch": False - }) - - fuzzer.init_from_dict(fuzzer_args) - - # sets compiler, no_exec and skip_argparse params before execution - # Eclipser requires `dotnet` to be invoked before fuzzer executable. - if isinstance(fuzzer, Eclipser): - args = ("dotnet", True, True) - else: - args = (None, True, True) - - - L.info("Initialized %s for ensemble-fuzzing and spinning up child proc.", fuzzer) - - # initialize concurrent process and add to process pool - proc = Process(target=fuzzer.run, args=args) - procs.append(proc) - - # TODO(alan): fix up delayed reporter; try not to have an individual proc run for - # reporting - if not self.no_global: - L.info("Creating child proc for global stats reporting.") - report_proc = Process(target=self.report, args=()) - procs.append(report_proc) - - for proc in procs: - L.info("Starting child proc for fuzzing.") - proc.start() - - # sleep until fuzzers finalize initialization, approx 5 seconds - time.sleep(5) - - for proc in procs: - proc.join() - - -def main(): - ensembler = Ensembler() - - # parse arguments and provision ensembler - ensembler.parse_args() - ensembler.init_from_dict() - ensembler.provision() - - # call ensembler routine - ensembler.run_ensembler() - return 0 - - -if __name__ == "__main__": - exit(main()) +#!/usr/bin/env python3.6 +# Copyright (c) 2019 Trail of Bits, Inc. +# +# 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 logging + +import os +import sys +import time +import string +import random +import argparse +import multiprocessing + +from multiprocessing import Process +from collections import defaultdict + +from deepstate.core.fuzz import FuzzerFrontend +from deepstate.executors.fuzz.afl import AFL +from deepstate.executors.fuzz.honggfuzz import Honggfuzz +from deepstate.executors.fuzz.angora import Angora +from deepstate.executors.fuzz.eclipser import Eclipser + + +L = logging.getLogger(__name__) + + +class Ensembler(FuzzerFrontend): + """ + Ensembler is the ensemble-based fuzzer that orchestrates and invokes fuzzer frontends, while also + supporting seed synchronization between those frontends. It initializes a set of global input args + for each frontend, performs an "ensemble compile", and spawns fuzzers in parallel while maintaining + seed synchronization between them. + """ + + NAME = "Ensembler" + EXECUTABLES = {"FUZZER": "deepstate-ensembler"} + + @classmethod + def parse_args(cls): + parser = argparse.ArgumentParser(description="Ensemble-based fuzzer executor for DeepState") + test_group = parser.add_mutually_exclusive_group(required=True) + + # Mutually exclusive target options + test_group.add_argument("--test", type=str, \ + help="Path to test case harness for compilation and instrumentation.") + + test_group.add_argument("--test_dir", type=str, \ + help="Path to existing workspace directory with compiled and instrumented binaries.") + + # Compilation options + parser.add_argument("-a", "--compiler_args", type=str, \ + help="Compiler linker arguments for test harness, if provided as argument.") + + parser.add_argument("--ignore_calls", type=str, \ + help="Path to static/shared libraries (colon seperated) to blackbox for taint analysis.") + + parser.add_argument("-w", "--workspace", type=str, default="ensemble_bins", \ + help="Path to workspace to store compiled and instrumented binaries (default is `ensemble_bins`).") + + # Ensembler execution options + parser.add_argument("-n", "--num_cores", type=int, default=multiprocessing.cpu_count(), \ + help="Override number of cores to use.") + + parser.add_argument("--no_global", action="store_true", \ + help="If set, disable global ensembler output, and instead report individual fuzzer stats.") + + # TODO(alan): other execution options + + #parser.add_argument("--fuzzers", type=str, \ + # help="Comma-separated string of fuzzers to ensemble with (overrides default ensemble).") + + #parser.add_argument("--abort_on_crash", action="store_true", \ + # help="Stop ensembler when any base fuzzer returns a crash.") + + cls.parser = parser + super(Ensembler, cls).parse_args() + + + def pre_exec(self): + """ + Implements pre_exec method from frontend superclass, and does sanity-checking on + parsed arguments before we can go ahead and provision an environment for ensemble fuzzing. + """ + + # `--fuzzer_help` equivalent to `--help` + if self.fuzzer_help: + self.parser.print_help() + + # ignore compiler-related arguments if not necessary + if self.test_dir and (self.ignore_calls or self.compiler_args): + L.info("Ignoring --ignore_calls and/or --compiler_args arguments passed") + + # initial path check + _test = self.test if not self.test_dir else self.test_dir + if not os.path.exists(_test): + L.error("Target path `%s` does not exist. Exiting.", _test) + sys.exit(1) + + if not os.path.isdir(self.input_seeds): + L.error("Input seeds directory `%s` does not exist. Exiting.", self.input_seeds) + sys.exit(1) + + if not os.path.isdir(self.output_test_dir): + L.warn("Output directory does not exist. Creating.") + os.mkdir(self.output_test_dir) + + if not self.sync_dir: + L.warn("No seed synchronization dir specified, using `sync`.") + self.sync_dir = "sync" + + sync_dir = self.output_test_dir + "/" + self.sync_dir + if not os.path.isdir(sync_dir): + L.warn("Sync directory does not exist. Creating.") + os.mkdir(sync_dir) + elif os.path.isdir(sync_dir) and len([f for f in os.listdir(sync_dir)]) != 0: + L.error("Sync directory exists and is not empty. Exiting.") + sys.exit(1) + + + @staticmethod + def _init_fuzzers(ret_all=False): + """ + Initialize a pre-defined ensemble of fuzzer objects. Return all subclasses if + param is set. + + Default fuzzer ensemble (four cores): + afl,honggfuzz,angora,eclipser + + """ + if ret_all: + return [subclass() for subclass in FuzzerFrontend.__subclasses__()] + else: + return [AFL(), Honggfuzz(), Angora(), Eclipser()] + + + def _get_tests(self, tests): + """ + Given a workspace path, retrieve testcases and map to specific fuzzer. We map + based on the condition that the generated test binary contains an extension + denoting the fuzzer name. + + :param tests: list of paths to workspace with already-compiled target binaries + """ + + def _get_fuzzer(test): + ext = test.split(".")[-1] + if ext in ["fast", "taint"]: + return "angora" + elif ext == "hfuzz": + return "honggfuzz" + return ext.lower() + + fuzz_map = defaultdict(list) + for test in tests: + for fuzzer in self.fuzzers: + if str(fuzzer).lower() == _get_fuzzer(test): + fuzz_map[fuzzer].append(test) + + L.debug("Fuzzer and corresponding test cases: %s", fuzz_map) + return fuzz_map + + + def provision(self): + """ + Initializes our ensemble of fuzzers, and creates a workspace with instrumented + harness binaries, if necessary. + """ + + # manually call pre_exec (we don't use frontend's runner routine) before provisioning + self.pre_exec() + + # initialize target - test str if user specified a harness, or a list to already-compiled binaries + target = self.test if not self.test_dir else list([f for f in os.listdir(self.test_dir)]) + L.info("Provisioning environment with target `%s`", target) + + self.fuzzers = list(self._init_fuzzers()) + L.debug("Fuzzers for ensembling: %s", self.fuzzers) + + # given a path to a DeepState harness, provision/compile, and retrieve test bins + if isinstance(target, str): + L.info("Detected source target. Compiling and then retrieving harnesses from workspace.") + self.targets = self._get_tests(self._provision_workspace(target)) + + # given a list of paths from a workspace, instantiate normally + elif isinstance(target, list): + L.info("Detected workspace target. Retrieving harnesses from workspace.") + self.targets = self._get_tests(target) + + L.debug("Target for analysis: %s", self.targets) + + + def _provision_workspace(self, test_case): + """ + Given a testcase source, provision a workspace with appropriate target binaries. + + :param test_case: path to uncompiled test case directory + """ + if not os.path.isdir(self.workspace): + L.info("Workspace doesn't exist. Creating.") + os.mkdir(self.workspace) + + L.info("Provisioning test case into workspace with instrumented binaries") + for fuzzer in self.fuzzers: + + test_name = self.workspace + "/" + test_case.split(".")[0] + L.debug("Compiling `%s` for fuzzer `%s`", test_name, fuzzer) + + cmd_map = { + "compile_test": test_case, + "out_test_name": test_name, + "compiler_args": self.compiler_args if self.compiler_args else None + } + + if isinstance(fuzzer, Angora): + cmd_map["mode"] = "llvm" + cmd_map["ignore_calls"] = self.ignore_calls + + fuzzer.init_from_dict(cmd_map) + + L.info("Compiling test case %s as `%s` with %s", test_case, test_name, fuzzer) + fuzzer.compile() + + return [test for test in os.listdir(self.workspace)] + + + def report(self): + """ + Global status reporter for ensemble fuzzing. We store and parse each individual + fuzzers reporter and provide a global output during fuzzer execution. + """ + while True: + + global_stats = dict() + for fuzzer in self.fuzzers: + time.sleep(self.sync_cycle) + + stats = fuzzer.reporter() + global_stats.update(stats) + + print("\n\n[\tEnsemble Fuzzer Status\t\t]\n") + for head, stat in global_stats.items(): + print(f"Total {head}\t:\t{stat}") + + + def run_ensembler(self): + """ + Bootstraps all fuzzers for ensembling with appropriate arguments, + and run fuzzers in parallel. + + TODO(alan): exit_crash arg to kill fuzzer and report when one crash is found + """ + + def _rand_id(): + return "".join(random.choice(string.ascii_uppercase + string.digits) + for _ in range(4)) + + #pool = multiprocessing.Pool(processes=self.num_cores) + procs = [] + + L.info("Initializing fuzzers for ensembling.") + + # for each fuzzer, instantiate fuzzer arguments manually using () rather than + # the parse_args() interface in each frontend. Specific fuzzers need specific options, so + # we also set those + # TODO(alan): migrate instantiation to provision or _provision_workspace + for fuzzer, binary in self.targets.items(): + fuzzer_args = { + + # default fuzzer execution related options + "timeout": self.timeout, + "binary": self.workspace + "/" + binary[0], + "input_seeds": self.input_seeds, + "output_test_dir": "{}/{}_{}_out".format(self.output_test_dir, str(fuzzer), _rand_id()), + "dictionary": None, + "max_input_size": self.max_input_size if self.max_input_size else 8192, + "mem_limit": "none", + "which_test": self.which_test, + "target_args": self.target_args, + + # set sync options for all fuzzers (TODO): configurable exec cycle + # set sync_out to output global fuzzer stats, set as default + "enable_sync": True, + "sync_cycle": self.sync_cycle, + "sync_dir": self.sync_dir, + "sync_out": not self.no_global + } + + # TODO(alan): store default dict in each fuzzer's _ARGS such that we don't need to + # manually instantiate fuzzer-specific attributes + + # manually set and override options for Angora, due to the requirement of two binaries + if isinstance(fuzzer, Angora): + fuzzer_args.update({ + "binary": next((self.workspace + "/" + b for b in binary if ".fast" in b), None), + "taint_binary": next((self.workspace + "/" + b for b in binary if ".taint" in b), None), + "no_afl": False, + "mode": "llvm", + "no_exploration": False + }) + + # manually set and override "AFL modes" that configured during execution + elif isinstance(fuzzer, AFL): + fuzzer_args.update({ + "parallel_mode": False, + "dirty_mode": False, + "dumb_mode": False, + "qemu_mode": False, + "crash_explore": False, + "file": None + }) + + # manually set Honggfuzz options + elif isinstance(fuzzer, Honggfuzz): + fuzzer_args.update({ + "iterations": None, + "persistent": False, + "no_inst": False, + "keep_output": False, + "sanitizers": False, + "clear_env": False, + "save_all": True, + "keep_aslr": False, + "perf_instr": False, + "perf_branch": False + }) + + fuzzer.init_from_dict(fuzzer_args) + + # sets compiler, no_exec and skip_argparse params before execution + # Eclipser requires `dotnet` to be invoked before fuzzer executable. + if isinstance(fuzzer, Eclipser): + args = ("dotnet", True, True) + else: + args = (None, True, True) + + + L.info("Initialized %s for ensemble-fuzzing and spinning up child proc.", fuzzer) + + # initialize concurrent process and add to process pool + proc = Process(target=fuzzer.run, args=args) + procs.append(proc) + + # TODO(alan): fix up delayed reporter; try not to have an individual proc run for + # reporting + if not self.no_global: + L.info("Creating child proc for global stats reporting.") + report_proc = Process(target=self.report, args=()) + procs.append(report_proc) + + for proc in procs: + L.info("Starting child proc for fuzzing.") + proc.start() + + # sleep until fuzzers finalize initialization, approx 5 seconds + time.sleep(5) + + for proc in procs: + proc.join() + + +def main(): + ensembler = Ensembler() + + # parse arguments and provision ensembler + ensembler.parse_args() + ensembler.init_from_dict() + ensembler.provision() + + # call ensembler routine + ensembler.run_ensembler() + return 0 + + +if __name__ == "__main__": + exit(main()) diff --git a/bin/deepstate/executors/auxiliary/reducer.py b/bin/deepstate/executors/auxiliary/reducer.py index 380a7eb0..1027ab18 100644 --- a/bin/deepstate/executors/auxiliary/reducer.py +++ b/bin/deepstate/executors/auxiliary/reducer.py @@ -1,632 +1,632 @@ -#!/usr/bin/env python -# Copyright (c) 2019 Trail of Bits, Inc. -# -# 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. - -from __future__ import print_function -import argparse -import subprocess -import os -import re -import sys -import time - - -def main(): - global candidateRuns, currentTest, s, passStart - - parser = argparse.ArgumentParser(description="Intelligently reduce test case") - - parser.add_argument( - "binary", type=str, help="Path to the test binary to run.") - parser.add_argument( - "input_test", type=str, help="Path to test to reduce.") - parser.add_argument( - "output_test", type=str, help="Path for reduced test.") - parser.add_argument( - "--which_test", type=str, help="Which test to run (equivalent to --input_which_test).", default=None) - parser.add_argument( - "--criterion", type=str, help="String to search for in valid reduction outputs (criteria are ORed by default).", - default=None) - parser.add_argument( - "--regexpCriterion", type=str, help="Regexp to search for in valid reduction outputs (criteria are ORed by default).", - default=None) - parser.add_argument( - "--exitCriterion", type=int, help="Exit code for valid reductions (criteria are ORed by default).", - default=None) - parser.add_argument("--andCriteria", action="store_true", help="AND criteria instead of ORing them") - parser.add_argument( - "--cmdArgs", type=str, help="Command line to use in place of standard DeepState arguments, file replaces @@") - parser.add_argument( - "--candidateName", type=str, help="Candidate name to use in place of default") - parser.add_argument( - "--search", action="store_true", help="Allow initial test to not satisfy criterion (search for test).", - default=None) - parser.add_argument( - "--timeout", type=int, help="After this amount of time (in seconds), give up on reduction (default is 20 minutes (1200s)).", - default=1200) - parser.add_argument( - "--maxByteRange", type=int, help="Maximum size of byte chunk to try in range removals.", - default=16) - parser.add_argument( - "--fast", action='store_true', - help="Faster, less complete, reduction (no byte range removal pass).") - parser.add_argument( - "--slow", action='store_true', - help="Slower, more complete, reduction (byte pattern pass).") - parser.add_argument( - "--slowest", action='store_true', - help="Slowest, most complete, reduction (byte pattern pass, tries all byte ranges).") - parser.add_argument( - "--verbose", action='store_true', - help="Verbose reduction.") - parser.add_argument( - "--fork", action='store_true', - help="Fork when running.") - parser.add_argument( - "--noStructure", action='store_true', - help="Don't use test structure.") - parser.add_argument( - "--noStaticStructure", action='store_true', - help='''Don't use "static" test structure (e.g., parens/quotes/brackets).''') - parser.add_argument( - "--noPad", action='store_true', - help="Don't pad test with zeros.") - - class TimeoutException(Exception): - pass - - args = parser.parse_args() - - maxByteRange = args.maxByteRange - deepstate = args.binary - test = args.input_test - out = args.output_test - checkString = args.criterion - if args.regexpCriterion: - checkRegExp = re.compile(args.regexpCriterion) - else: - checkRegExp = None - whichTest = args.which_test - - start = time.time() - candidateRuns = 0 - - candidateName = ".candidate." + str(os.getpid()) + ".test" - if args.candidateName is not None: - candidateName = args.candidateName - - def runCandidate(candidate): - global candidateRuns - - candidateRuns += 1 - if (time.time() - start) > args.timeout: - raise TimeoutException - with open(".reducer." + str(os.getpid()) + ".out", 'w') as outf: - if args.cmdArgs is None: - cmd = [deepstate + " --input_test_file " + - candidate + " --verbose_reads"] - if whichTest is not None: - cmd += ["--input_which_test", whichTest] - if not args.fork: - cmd += ["--no_fork"] - else: - cmd = [deepstate + " " + args.cmdArgs.replace("@@", candidate)] - exitCode = subprocess.call(cmd, shell=True, stdout=outf, stderr=outf) - result = [] - with open(".reducer." + str(os.getpid()) + ".out", 'rb') as inf: - for line in inf: - dline = line.decode("utf-8", "ignore") - result.append(dline) - return (result, exitCode) - - def checks(resultAndExitCode): - (result, exitCode) = resultAndExitCode - if (args.exitCriterion is None) and (checkRegExp is None) and (checkString is None): - # Only apply default DeepState failure check if no other criteria were defined - for line in result: - if "ERROR: Failed:" in line: - return True - if "ERROR: Crashed" in line: - return True - - if args.exitCriterion is not None: - exitHolds = exitCode == args.exitCriterion - else: - exitHolds = args.andCriteria - if checkRegExp is not None: - regexpHolds = re.search(checkRegExp, "\n".join(result)) is not None - else: - regexpHolds = args.andCriteria - if checkString is not None: - stringHolds = checkString in "\n".join(result) - else: - stringHolds = args.andCriteria - if args.andCriteria: - return exitHolds and regexpHolds and stringHolds - else: - return exitHolds or regexpHolds or stringHolds - - def writeAndRunCandidate(test): - with open(candidateName, 'wb') as outf: - outf.write(test) - r = runCandidate(candidateName) - return r - - def augmentWithDelims(OneOfsAndLastRead, testBytes): - if args.noStaticStructure: - return OneOfsAndLastRead - (OneOfs, lastRead) = OneOfsAndLastRead - delimPairs = [ - ("{", "}"), - ("(", ")"), - ("[", "]"), - (";", ";"), - ("{", ";"), - (";", "}"), - ("BEGIN", "\n"), - ("\n", "END"), - ("\n", "\n"), - ("'", "'"), - ('"', '"'), - ("/", "/"), - ("/", "*"), - ("/", "\n"), - (",", ","), - ("(", ","), - (",", ")"), - ("<", ">")] - delims = [] - for (tstart, tstop) in delimPairs: - if tstart not in ["BEGIN", "END"]: - tstartBytes = bytearray(tstart, encoding="utf8") - start = tstartBytes[0] - if tstop not in ["BEGIN", "END"]: - tstopBytes = bytearray(tstop, encoding="utf8") - stop = tstopBytes[0] - for i in range(len(testBytes)): - for j in range(len(testBytes) - 1, i, -1): - if tstart not in ["BEGIN", "END"]: - imatch = testBytes[i] == start - else: - if tstart == "BEGIN": - imatch = (i == 0) - if tstop not in ["BEGIN", "END"]: - jmatch = testBytes[j] == stop - else: - jmatch = (j == len(testBytes) - 1) - if imatch and jmatch: - delims.append((i, j)) - delims.append((i + 1, j - 1)) - return (OneOfs + delims, lastRead) - - def structure(resultAndExitCode): - (result, exitCode) = resultAndExitCode - lastRead = len(currentTest) - 1 - if args.noStructure: - return ([], lastRead) - OneOfs = [] - currentOneOf = [] - for line in result: - if "STARTING OneOf CALL" in line: - currentOneOf.append(-1) - elif "Reading byte at" in line: - lastRead = int(line.split()[-1]) - if len(currentOneOf) > 0: - if currentOneOf[-1] == -1: - currentOneOf[-1] = lastRead - elif "FINISHED OneOf CALL" in line: - OneOfs.append((currentOneOf[-1], lastRead)) - currentOneOf = currentOneOf[:-1] - return (OneOfs, lastRead) - - def rangeConversions(resultAndExitCode): - (result, exitCode) = resultAndExitCode - conversions = [] - startedMulti = False - multiFirst = None - for line in result: - if "Reading byte at" in line: - lastRead = int(line.split()[-1]) - if "STARTING MULTI-BYTE READ" in line: - startedMulti = True - if startedMulti and (multiFirst is None) and ("Reading byte at" in line): - multiFirst = lastRead - if "FINISHED MULTI-BYTE READ" in line: - currentMulti = (multiFirst, lastRead) - startedMulti = False - multiFirst = None - if "Converting out-of-range value" in line: - conversions.append((currentMulti, int(line.split()[-1]))) - return conversions - - def fixRangeConversions(test, conversions): - if args.noStructure: - return - numConversions = 0 - for (pos, value) in conversions: - if pos[1] >= len(test): - break - if (value >= 0) and (value < 255) and (value < test[pos[1]]): - numConversions += 1 - for b in range(pos[0], pos[1]): - test[b] = 0 - test[pos[1]] = value - if numConversions > 0: - print("Applied", numConversions, "range conversions") - - initial = runCandidate(test) - if (not args.search) and (not checks(initial)): - print("STARTING TEST DOES NOT SATISFY REDUCTION CRITERION!") - return 1 - - with open(test, 'rb') as test: - currentTest = bytearray(test.read()) - original = bytearray(currentTest) - - print("Original test has", len(currentTest), "bytes") - if args.slowest: - maxByteRange = len(currentTest) - - fixRangeConversions(currentTest, rangeConversions(initial)) - r = writeAndRunCandidate(currentTest) - assert(checks(r)) - - s = structure(r) - if (s[1] + 1) < len(currentTest): - print("Last byte read:", s[1]) - print("Shrinking to ignore unread bytes") - currentTest = currentTest[:s[1] + 1] - s = augmentWithDelims(s, currentTest) - - if currentTest != original: - print("Writing reduced test with", len(currentTest), "bytes to", out) - with open(out, 'wb') as outf: - outf.write(currentTest) - - initialSize = float(len(currentTest)) - iteration = 0 - - def updateCurrent(newTest, lastRunResult): - global currentTest, s - # Ensure this is actually shrinking size or simplifying - assert ((len(newTest) < len(currentTest)) or - (newTest < currentTest)), "New test is neither smaller nor simpler!" - currentTest = newTest - fixRangeConversions(currentTest, rangeConversions(lastRunResult)) - print("Writing reduced test with", len(currentTest), "bytes to", out) - with open(out, 'wb') as outf: - outf.write(currentTest) - s = augmentWithDelims(structure(lastRunResult), currentTest) - percent = 100.0 * ((initialSize - len(currentTest)) / initialSize) - print(round(time.time()-start, 2), "secs /", - candidateRuns, "execs /", str(round(percent, 2)) + "% reduction") - print("="*80) - sys.stdout.flush() - - def passInfo(passName): - global passStart - percent = 100.0 * ((initialSize - len(currentTest)) / initialSize) - print(passName + ":", "PASS FINISHED IN", round(time.time() - passStart, 2), "SECONDS, RUN:", - round(time.time()-start, 2), "secs /", candidateRuns, "execs /", str(round(percent, 2)) + "% reduction") - passStart = time.time() - - oldTest = [] - lastOneOfRemovalTest = [] - lastEdgeRemovalTest = [] - lastChunkRemovalTest = {} - lastChunkRemovalTest[1] = [] - lastChunkRemovalTest[4] = [] - lastChunkRemovalTest[8] = [] - lastReduceAndDeleteTest = {} - lastReduceAndDeleteTest[1] = [] - lastReduceAndDeleteTest[4] = [] - lastReduceAndDeleteTest[8] = [] - lastAllRangeTest = [] - lastOneOfSwapTest = [] - lastByteReduceTest = [] - lastPatternSearchTest = [] - - passStart = time.time() - try: - while oldTest != currentTest: - oldTest = bytearray(currentTest) - - iteration += 1 - percent = 100.0 * ((initialSize - len(currentTest)) / initialSize) - print("=" * 80) - print("Iteration #" + str(iteration), round(time.time()-start, 2), "secs /", - candidateRuns, "execs /", str(round(percent, 2)) + "% reduction") - - if not (args.noStructure) and (currentTest != lastOneOfRemovalTest) and (len(s[0]) != 0): - if args.verbose: - print("*" * 80 + "\nPASS: structured deletions...") - changed = True - while changed: - changed = False - cuts = s[0] - for c in cuts: - newTest = currentTest[:c[0]] + currentTest[c[1] + 1:] - if len(newTest) == len(currentTest): - continue # Ignore non-shrinking reductions - r = writeAndRunCandidate(newTest) - if checks(r): - print("Structured deletion reduced test to", len(newTest), "bytes") - changed = True - updateCurrent(newTest, r) - break - lastOneOfRemovalTest = bytearray(currentTest) - passInfo("Structured deletion") - - if not (args.noStructure) and (currentTest != lastEdgeRemovalTest) and (len(s[0]) != 0): - if args.verbose: - print("*" * 80 + "\nPASS: structure edge deletions...") - changed = True - while changed: - changed = False - cuts = s[0] - for c in cuts: - newTest = currentTest[:c[0]] + currentTest[c[0] + 1:c[1]] + currentTest[c[1] + 1:] - if len(newTest) == len(currentTest): - continue # Ignore non-shrinking reductions - r = writeAndRunCandidate(newTest) - if checks(r): - print("Structure edge deletion reduced test to", len(newTest), "bytes") - changed = True - updateCurrent(newTest, r) - break - lastEdgeRemovalTest = bytearray(currentTest) - passInfo("Structured edge deletion") - - for k in [1, 4, 8]: - if currentTest != lastChunkRemovalTest[k]: - if args.verbose: - print("*" * 80 + "\nPASS: trying", k, "byte chunk removals...") - changed = True - startingPos = 0 - while changed: - changed = False - for b in range(startingPos, len(currentTest)): - newTest = currentTest[:b] + currentTest[b + k:] - r = writeAndRunCandidate(newTest) - if checks(r): - print("Removed", k, "byte(s) @", str(b) + ": reduced test to", len(newTest), "bytes") - changed = True - updateCurrent(newTest, r) - startingPos = b - break - if not changed: - for b in range(0, startingPos): - newTest = currentTest[:b] + currentTest[b + k:] - r = writeAndRunCandidate(newTest) - if checks(r): - print("Removed", k, "byte(s) @", str(b) + ": reduced test to", len(newTest), "bytes") - changed = True - updateCurrent(newTest, r) - startingPos = b - break - lastChunkRemovalTest[k] = bytearray(currentTest) - passInfo(str(k) + "-byte chunk removal") - - for k in [1, 4, 8]: - if currentTest != lastReduceAndDeleteTest[k]: - if args.verbose: - print("*" * 80 + "\nPASS: byte reduce and delete", str(k) + "...") - changed = True - while changed: - changed = False - for b in range(0, len(currentTest) - k): - if currentTest[b] == 0: - continue - newTest = bytearray(currentTest) - newTest[b] = currentTest[b] - 1 - newTest = newTest[:b + 1] + newTest[b + k + 1:] - r = writeAndRunCandidate(newTest) - if checks(r): - print("Reduced byte", b, "by 1 and deleted", k, "bytes, reducing test to", len(newTest), "bytes") - changed = True - updateCurrent(newTest, r) - break - lastReduceAndDeleteTest[k] = bytearray(currentTest) - passInfo(str(k) + "-byte reduce and delete") - - if not args.fast: - if currentTest != lastAllRangeTest: - if args.verbose: - print("*" * 80 + "\nPASS: trying all byte range removals...") - changed = True - startingPos = 0 - while changed: - changed = False - for b in range(startingPos, len(currentTest)): - if args.verbose: - print("Trying byte range removal from", str(b) + "...") - for v in range(b + 2, min(len(currentTest), b + maxByteRange)): - if (v-b) in [4, 8]: - continue - newTest = currentTest[:b] + currentTest[v:] - r = writeAndRunCandidate(newTest) - if checks(r): - print("Byte range removal of bytes", str(b) + "-" + str(v - 1), - "reduced test to", len(newTest), "bytes") - changed = True - updateCurrent(newTest, r) - startingPos = b - break - if changed: - break - if not changed: - for b in range(0, startingPos): - if args.verbose: - print("Trying byte range removal from", str(b) + "...") - for v in range(b + 2, min(len(currentTest), b + maxByteRange)): - if (v-b) in [4, 8]: - continue - newTest = currentTest[:b] + currentTest[v:] - r = writeAndRunCandidate(newTest) - if checks(r): - print("Byte range removal of bytes", str(b) + "-" + str(v - 1), - "reduced test to", len(newTest), "bytes") - changed = True - updateCurrent(newTest, r) - startingPos = b - break - if changed: - break - lastAllRangeTest = bytearray(currentTest) - passInfo("Byte range removal") - - if (not args.noStructure) and (currentTest != lastOneOfSwapTest) and (len(s[0]) != 0): - if args.verbose: - print("*" * 80 + "\nPASS: swapping structures...") - changed = True - while changed: - changed = False - cuts = s[0] - for i in range(len(cuts) - 1): - cuti = cuts[i] - bytesi = currentTest[cuti[0]:cuti[1] + 1] - if args.verbose: - print("Trying structured swap from byte", cuti[0], "[" + " ".join(map(str, bytesi)) + "]") - for j in range(i + 1, len(cuts)): - cutj = cuts[j] - if cutj[0] > cuti[1]: - bytesj = currentTest[cutj[0]:cutj[1] + 1] - if (len(bytesj) > 0) and (bytesi > bytesj): - newTest = currentTest[:cuti[0]] + bytesj + currentTest[cuti[1] + 1:cutj[0]] - newTest += bytesi - newTest += currentTest[cutj[1] + 1:] - newTest = bytearray(newTest) - if newTest != currentTest: - r = writeAndRunCandidate(newTest) - if checks(r): - print("Structured swap @ byte", cuti[0], "[" + " ".join(map(str, bytesi)) + "]", - "with", cutj[0], "[" + " ".join(map(str, bytesj)) + "]") - changed = True - updateCurrent(newTest, r) - break - if changed: - break - if changed: - break - lastOneOfSwapTest = bytearray(currentTest) - passInfo("Structured swap") - - if currentTest != lastByteReduceTest: - if args.verbose: - print("*" * 80 + "\nPASS: byte reductions...") - changed = True - startingPos = 0 - while changed: - changed = False - for b in range(startingPos, len(currentTest)): - for v in range(0, currentTest[b]): - newTest = bytearray(currentTest) - newTest[b] = v - r = writeAndRunCandidate(newTest) - if checks(r): - print("Reduced byte", b, "from", currentTest[b], "to", v) - changed = True - updateCurrent(newTest, r) - startingPos = b + 1 - break - if changed: - break - if changed: - continue - for b in range(0, startingPos): - for v in range(0, currentTest[b]): - newTest = bytearray(currentTest) - newTest[b] = v - r = writeAndRunCandidate(newTest) - if checks(r): - print("Reduced byte", b, "from", currentTest[b], "to", v) - changed = True - updateCurrent(newTest, r) - startingPos = b + 1 - break - if changed: - break - lastByteReduceTest = bytearray(currentTest) - passInfo("Byte reduce") - - if (args.slow or args.slowest) and (oldTest == currentTest): - if currentTest != lastPatternSearchTest: - if args.verbose: - print("*" * 80 + "\nPASS: byte pattern search...") - changed = True - while changed: - changed = False - for b1 in range(0, len(currentTest)-4): - if args.verbose: - print("Trying byte pattern search from byte", str(b1) + "...") - for b2 in range(b1 + 2, len(currentTest) - 4): - v1 = (currentTest[b1], currentTest[b1 + 1]) - v2 = (currentTest[b2], currentTest[b2 + 1]) - if (v1 == v2): - ba = bytearray(v1) - part1 = currentTest[:b1] - part2 = currentTest[b1 + 2:b2] - part3 = currentTest[b2 + 2:] - banews = [] - banews.append(ba[0:1]) - banews.append(ba[1:2]) - if ba[0] > 0: - for v in range(0, ba[0]): - banews.append(bytearray([v, ba[1]])) - banews.append(bytearray([ba[0] - 1])) - if ba[1] > 0: - for v in range(0, ba[1]): - banews.append(bytearray([ba[0], v])) - for banew in banews: - newTest = part1 + banew + part2 + banew + part3 - r = writeAndRunCandidate(newTest) - if checks(r): - print("Byte pattern", tuple(ba), "at", b1, "and", b2, "changed to", tuple(banew)) - changed = True - updateCurrent(newTest, r) - break - if changed: - break - if changed: - break - lastPatternSearchTest = bytearray(currentTest) - passInfo("Byte pattern change") - - if oldTest == currentTest: - print("*" * 80) - print("DONE: NO (MORE) REDUCTIONS FOUND") - except TimeoutException: - print("*" * 80) - print("DONE: REDUCTION TIMED OUT AFTER", args.timeout, "SECONDS") - - print("=" * 80) - percent = 100.0 * ((initialSize - len(currentTest)) / initialSize) - print("Completed", iteration, "iterations:", round(time.time()-start, 2), "secs /", - candidateRuns, "execs /", str(round(percent, 2)) + "% reduction") - - if not args.noPad: - if (s[1] + 1) > len(currentTest): - print("Padding test with", (s[1] + 1) - len(currentTest), "zeroes") - padding = bytearray('\x00' * ((s[1] + 1) - len(currentTest)), 'utf-8') - currentTest = currentTest + padding - - print("Writing reduced test with", len(currentTest), "bytes to", out) - - with open(out, 'wb') as outf: - outf.write(currentTest) - - return 0 - -if "__main__" == __name__: - exit(main()) +#!/usr/bin/env python +# Copyright (c) 2019 Trail of Bits, Inc. +# +# 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. + +from __future__ import print_function +import argparse +import subprocess +import os +import re +import sys +import time + + +def main(): + global candidateRuns, currentTest, s, passStart + + parser = argparse.ArgumentParser(description="Intelligently reduce test case") + + parser.add_argument( + "binary", type=str, help="Path to the test binary to run.") + parser.add_argument( + "input_test", type=str, help="Path to test to reduce.") + parser.add_argument( + "output_test", type=str, help="Path for reduced test.") + parser.add_argument( + "--which_test", type=str, help="Which test to run (equivalent to --input_which_test).", default=None) + parser.add_argument( + "--criterion", type=str, help="String to search for in valid reduction outputs (criteria are ORed by default).", + default=None) + parser.add_argument( + "--regexpCriterion", type=str, help="Regexp to search for in valid reduction outputs (criteria are ORed by default).", + default=None) + parser.add_argument( + "--exitCriterion", type=int, help="Exit code for valid reductions (criteria are ORed by default).", + default=None) + parser.add_argument("--andCriteria", action="store_true", help="AND criteria instead of ORing them") + parser.add_argument( + "--cmdArgs", type=str, help="Command line to use in place of standard DeepState arguments, file replaces @@") + parser.add_argument( + "--candidateName", type=str, help="Candidate name to use in place of default") + parser.add_argument( + "--search", action="store_true", help="Allow initial test to not satisfy criterion (search for test).", + default=None) + parser.add_argument( + "--timeout", type=int, help="After this amount of time (in seconds), give up on reduction (default is 20 minutes (1200s)).", + default=1200) + parser.add_argument( + "--maxByteRange", type=int, help="Maximum size of byte chunk to try in range removals.", + default=16) + parser.add_argument( + "--fast", action='store_true', + help="Faster, less complete, reduction (no byte range removal pass).") + parser.add_argument( + "--slow", action='store_true', + help="Slower, more complete, reduction (byte pattern pass).") + parser.add_argument( + "--slowest", action='store_true', + help="Slowest, most complete, reduction (byte pattern pass, tries all byte ranges).") + parser.add_argument( + "--verbose", action='store_true', + help="Verbose reduction.") + parser.add_argument( + "--fork", action='store_true', + help="Fork when running.") + parser.add_argument( + "--noStructure", action='store_true', + help="Don't use test structure.") + parser.add_argument( + "--noStaticStructure", action='store_true', + help='''Don't use "static" test structure (e.g., parens/quotes/brackets).''') + parser.add_argument( + "--noPad", action='store_true', + help="Don't pad test with zeros.") + + class TimeoutException(Exception): + pass + + args = parser.parse_args() + + maxByteRange = args.maxByteRange + deepstate = args.binary + test = args.input_test + out = args.output_test + checkString = args.criterion + if args.regexpCriterion: + checkRegExp = re.compile(args.regexpCriterion) + else: + checkRegExp = None + whichTest = args.which_test + + start = time.time() + candidateRuns = 0 + + candidateName = ".candidate." + str(os.getpid()) + ".test" + if args.candidateName is not None: + candidateName = args.candidateName + + def runCandidate(candidate): + global candidateRuns + + candidateRuns += 1 + if (time.time() - start) > args.timeout: + raise TimeoutException + with open(".reducer." + str(os.getpid()) + ".out", 'w') as outf: + if args.cmdArgs is None: + cmd = [deepstate + " --input_test_file " + + candidate + " --verbose_reads"] + if whichTest is not None: + cmd += ["--input_which_test", whichTest] + if not args.fork: + cmd += ["--no_fork"] + else: + cmd = [deepstate + " " + args.cmdArgs.replace("@@", candidate)] + exitCode = subprocess.call(cmd, shell=True, stdout=outf, stderr=outf) + result = [] + with open(".reducer." + str(os.getpid()) + ".out", 'rb') as inf: + for line in inf: + dline = line.decode("utf-8", "ignore") + result.append(dline) + return (result, exitCode) + + def checks(resultAndExitCode): + (result, exitCode) = resultAndExitCode + if (args.exitCriterion is None) and (checkRegExp is None) and (checkString is None): + # Only apply default DeepState failure check if no other criteria were defined + for line in result: + if "ERROR: Failed:" in line: + return True + if "ERROR: Crashed" in line: + return True + + if args.exitCriterion is not None: + exitHolds = exitCode == args.exitCriterion + else: + exitHolds = args.andCriteria + if checkRegExp is not None: + regexpHolds = re.search(checkRegExp, "\n".join(result)) is not None + else: + regexpHolds = args.andCriteria + if checkString is not None: + stringHolds = checkString in "\n".join(result) + else: + stringHolds = args.andCriteria + if args.andCriteria: + return exitHolds and regexpHolds and stringHolds + else: + return exitHolds or regexpHolds or stringHolds + + def writeAndRunCandidate(test): + with open(candidateName, 'wb') as outf: + outf.write(test) + r = runCandidate(candidateName) + return r + + def augmentWithDelims(OneOfsAndLastRead, testBytes): + if args.noStaticStructure: + return OneOfsAndLastRead + (OneOfs, lastRead) = OneOfsAndLastRead + delimPairs = [ + ("{", "}"), + ("(", ")"), + ("[", "]"), + (";", ";"), + ("{", ";"), + (";", "}"), + ("BEGIN", "\n"), + ("\n", "END"), + ("\n", "\n"), + ("'", "'"), + ('"', '"'), + ("/", "/"), + ("/", "*"), + ("/", "\n"), + (",", ","), + ("(", ","), + (",", ")"), + ("<", ">")] + delims = [] + for (tstart, tstop) in delimPairs: + if tstart not in ["BEGIN", "END"]: + tstartBytes = bytearray(tstart, encoding="utf8") + start = tstartBytes[0] + if tstop not in ["BEGIN", "END"]: + tstopBytes = bytearray(tstop, encoding="utf8") + stop = tstopBytes[0] + for i in range(len(testBytes)): + for j in range(len(testBytes) - 1, i, -1): + if tstart not in ["BEGIN", "END"]: + imatch = testBytes[i] == start + else: + if tstart == "BEGIN": + imatch = (i == 0) + if tstop not in ["BEGIN", "END"]: + jmatch = testBytes[j] == stop + else: + jmatch = (j == len(testBytes) - 1) + if imatch and jmatch: + delims.append((i, j)) + delims.append((i + 1, j - 1)) + return (OneOfs + delims, lastRead) + + def structure(resultAndExitCode): + (result, exitCode) = resultAndExitCode + lastRead = len(currentTest) - 1 + if args.noStructure: + return ([], lastRead) + OneOfs = [] + currentOneOf = [] + for line in result: + if "STARTING OneOf CALL" in line: + currentOneOf.append(-1) + elif "Reading byte at" in line: + lastRead = int(line.split()[-1]) + if len(currentOneOf) > 0: + if currentOneOf[-1] == -1: + currentOneOf[-1] = lastRead + elif "FINISHED OneOf CALL" in line: + OneOfs.append((currentOneOf[-1], lastRead)) + currentOneOf = currentOneOf[:-1] + return (OneOfs, lastRead) + + def rangeConversions(resultAndExitCode): + (result, exitCode) = resultAndExitCode + conversions = [] + startedMulti = False + multiFirst = None + for line in result: + if "Reading byte at" in line: + lastRead = int(line.split()[-1]) + if "STARTING MULTI-BYTE READ" in line: + startedMulti = True + if startedMulti and (multiFirst is None) and ("Reading byte at" in line): + multiFirst = lastRead + if "FINISHED MULTI-BYTE READ" in line: + currentMulti = (multiFirst, lastRead) + startedMulti = False + multiFirst = None + if "Converting out-of-range value" in line: + conversions.append((currentMulti, int(line.split()[-1]))) + return conversions + + def fixRangeConversions(test, conversions): + if args.noStructure: + return + numConversions = 0 + for (pos, value) in conversions: + if pos[1] >= len(test): + break + if (value >= 0) and (value < 255) and (value < test[pos[1]]): + numConversions += 1 + for b in range(pos[0], pos[1]): + test[b] = 0 + test[pos[1]] = value + if numConversions > 0: + print("Applied", numConversions, "range conversions") + + initial = runCandidate(test) + if (not args.search) and (not checks(initial)): + print("STARTING TEST DOES NOT SATISFY REDUCTION CRITERION!") + return 1 + + with open(test, 'rb') as test: + currentTest = bytearray(test.read()) + original = bytearray(currentTest) + + print("Original test has", len(currentTest), "bytes") + if args.slowest: + maxByteRange = len(currentTest) + + fixRangeConversions(currentTest, rangeConversions(initial)) + r = writeAndRunCandidate(currentTest) + assert(checks(r)) + + s = structure(r) + if (s[1] + 1) < len(currentTest): + print("Last byte read:", s[1]) + print("Shrinking to ignore unread bytes") + currentTest = currentTest[:s[1] + 1] + s = augmentWithDelims(s, currentTest) + + if currentTest != original: + print("Writing reduced test with", len(currentTest), "bytes to", out) + with open(out, 'wb') as outf: + outf.write(currentTest) + + initialSize = float(len(currentTest)) + iteration = 0 + + def updateCurrent(newTest, lastRunResult): + global currentTest, s + # Ensure this is actually shrinking size or simplifying + assert ((len(newTest) < len(currentTest)) or + (newTest < currentTest)), "New test is neither smaller nor simpler!" + currentTest = newTest + fixRangeConversions(currentTest, rangeConversions(lastRunResult)) + print("Writing reduced test with", len(currentTest), "bytes to", out) + with open(out, 'wb') as outf: + outf.write(currentTest) + s = augmentWithDelims(structure(lastRunResult), currentTest) + percent = 100.0 * ((initialSize - len(currentTest)) / initialSize) + print(round(time.time()-start, 2), "secs /", + candidateRuns, "execs /", str(round(percent, 2)) + "% reduction") + print("="*80) + sys.stdout.flush() + + def passInfo(passName): + global passStart + percent = 100.0 * ((initialSize - len(currentTest)) / initialSize) + print(passName + ":", "PASS FINISHED IN", round(time.time() - passStart, 2), "SECONDS, RUN:", + round(time.time()-start, 2), "secs /", candidateRuns, "execs /", str(round(percent, 2)) + "% reduction") + passStart = time.time() + + oldTest = [] + lastOneOfRemovalTest = [] + lastEdgeRemovalTest = [] + lastChunkRemovalTest = {} + lastChunkRemovalTest[1] = [] + lastChunkRemovalTest[4] = [] + lastChunkRemovalTest[8] = [] + lastReduceAndDeleteTest = {} + lastReduceAndDeleteTest[1] = [] + lastReduceAndDeleteTest[4] = [] + lastReduceAndDeleteTest[8] = [] + lastAllRangeTest = [] + lastOneOfSwapTest = [] + lastByteReduceTest = [] + lastPatternSearchTest = [] + + passStart = time.time() + try: + while oldTest != currentTest: + oldTest = bytearray(currentTest) + + iteration += 1 + percent = 100.0 * ((initialSize - len(currentTest)) / initialSize) + print("=" * 80) + print("Iteration #" + str(iteration), round(time.time()-start, 2), "secs /", + candidateRuns, "execs /", str(round(percent, 2)) + "% reduction") + + if not (args.noStructure) and (currentTest != lastOneOfRemovalTest) and (len(s[0]) != 0): + if args.verbose: + print("*" * 80 + "\nPASS: structured deletions...") + changed = True + while changed: + changed = False + cuts = s[0] + for c in cuts: + newTest = currentTest[:c[0]] + currentTest[c[1] + 1:] + if len(newTest) == len(currentTest): + continue # Ignore non-shrinking reductions + r = writeAndRunCandidate(newTest) + if checks(r): + print("Structured deletion reduced test to", len(newTest), "bytes") + changed = True + updateCurrent(newTest, r) + break + lastOneOfRemovalTest = bytearray(currentTest) + passInfo("Structured deletion") + + if not (args.noStructure) and (currentTest != lastEdgeRemovalTest) and (len(s[0]) != 0): + if args.verbose: + print("*" * 80 + "\nPASS: structure edge deletions...") + changed = True + while changed: + changed = False + cuts = s[0] + for c in cuts: + newTest = currentTest[:c[0]] + currentTest[c[0] + 1:c[1]] + currentTest[c[1] + 1:] + if len(newTest) == len(currentTest): + continue # Ignore non-shrinking reductions + r = writeAndRunCandidate(newTest) + if checks(r): + print("Structure edge deletion reduced test to", len(newTest), "bytes") + changed = True + updateCurrent(newTest, r) + break + lastEdgeRemovalTest = bytearray(currentTest) + passInfo("Structured edge deletion") + + for k in [1, 4, 8]: + if currentTest != lastChunkRemovalTest[k]: + if args.verbose: + print("*" * 80 + "\nPASS: trying", k, "byte chunk removals...") + changed = True + startingPos = 0 + while changed: + changed = False + for b in range(startingPos, len(currentTest)): + newTest = currentTest[:b] + currentTest[b + k:] + r = writeAndRunCandidate(newTest) + if checks(r): + print("Removed", k, "byte(s) @", str(b) + ": reduced test to", len(newTest), "bytes") + changed = True + updateCurrent(newTest, r) + startingPos = b + break + if not changed: + for b in range(0, startingPos): + newTest = currentTest[:b] + currentTest[b + k:] + r = writeAndRunCandidate(newTest) + if checks(r): + print("Removed", k, "byte(s) @", str(b) + ": reduced test to", len(newTest), "bytes") + changed = True + updateCurrent(newTest, r) + startingPos = b + break + lastChunkRemovalTest[k] = bytearray(currentTest) + passInfo(str(k) + "-byte chunk removal") + + for k in [1, 4, 8]: + if currentTest != lastReduceAndDeleteTest[k]: + if args.verbose: + print("*" * 80 + "\nPASS: byte reduce and delete", str(k) + "...") + changed = True + while changed: + changed = False + for b in range(0, len(currentTest) - k): + if currentTest[b] == 0: + continue + newTest = bytearray(currentTest) + newTest[b] = currentTest[b] - 1 + newTest = newTest[:b + 1] + newTest[b + k + 1:] + r = writeAndRunCandidate(newTest) + if checks(r): + print("Reduced byte", b, "by 1 and deleted", k, "bytes, reducing test to", len(newTest), "bytes") + changed = True + updateCurrent(newTest, r) + break + lastReduceAndDeleteTest[k] = bytearray(currentTest) + passInfo(str(k) + "-byte reduce and delete") + + if not args.fast: + if currentTest != lastAllRangeTest: + if args.verbose: + print("*" * 80 + "\nPASS: trying all byte range removals...") + changed = True + startingPos = 0 + while changed: + changed = False + for b in range(startingPos, len(currentTest)): + if args.verbose: + print("Trying byte range removal from", str(b) + "...") + for v in range(b + 2, min(len(currentTest), b + maxByteRange)): + if (v-b) in [4, 8]: + continue + newTest = currentTest[:b] + currentTest[v:] + r = writeAndRunCandidate(newTest) + if checks(r): + print("Byte range removal of bytes", str(b) + "-" + str(v - 1), + "reduced test to", len(newTest), "bytes") + changed = True + updateCurrent(newTest, r) + startingPos = b + break + if changed: + break + if not changed: + for b in range(0, startingPos): + if args.verbose: + print("Trying byte range removal from", str(b) + "...") + for v in range(b + 2, min(len(currentTest), b + maxByteRange)): + if (v-b) in [4, 8]: + continue + newTest = currentTest[:b] + currentTest[v:] + r = writeAndRunCandidate(newTest) + if checks(r): + print("Byte range removal of bytes", str(b) + "-" + str(v - 1), + "reduced test to", len(newTest), "bytes") + changed = True + updateCurrent(newTest, r) + startingPos = b + break + if changed: + break + lastAllRangeTest = bytearray(currentTest) + passInfo("Byte range removal") + + if (not args.noStructure) and (currentTest != lastOneOfSwapTest) and (len(s[0]) != 0): + if args.verbose: + print("*" * 80 + "\nPASS: swapping structures...") + changed = True + while changed: + changed = False + cuts = s[0] + for i in range(len(cuts) - 1): + cuti = cuts[i] + bytesi = currentTest[cuti[0]:cuti[1] + 1] + if args.verbose: + print("Trying structured swap from byte", cuti[0], "[" + " ".join(map(str, bytesi)) + "]") + for j in range(i + 1, len(cuts)): + cutj = cuts[j] + if cutj[0] > cuti[1]: + bytesj = currentTest[cutj[0]:cutj[1] + 1] + if (len(bytesj) > 0) and (bytesi > bytesj): + newTest = currentTest[:cuti[0]] + bytesj + currentTest[cuti[1] + 1:cutj[0]] + newTest += bytesi + newTest += currentTest[cutj[1] + 1:] + newTest = bytearray(newTest) + if newTest != currentTest: + r = writeAndRunCandidate(newTest) + if checks(r): + print("Structured swap @ byte", cuti[0], "[" + " ".join(map(str, bytesi)) + "]", + "with", cutj[0], "[" + " ".join(map(str, bytesj)) + "]") + changed = True + updateCurrent(newTest, r) + break + if changed: + break + if changed: + break + lastOneOfSwapTest = bytearray(currentTest) + passInfo("Structured swap") + + if currentTest != lastByteReduceTest: + if args.verbose: + print("*" * 80 + "\nPASS: byte reductions...") + changed = True + startingPos = 0 + while changed: + changed = False + for b in range(startingPos, len(currentTest)): + for v in range(0, currentTest[b]): + newTest = bytearray(currentTest) + newTest[b] = v + r = writeAndRunCandidate(newTest) + if checks(r): + print("Reduced byte", b, "from", currentTest[b], "to", v) + changed = True + updateCurrent(newTest, r) + startingPos = b + 1 + break + if changed: + break + if changed: + continue + for b in range(0, startingPos): + for v in range(0, currentTest[b]): + newTest = bytearray(currentTest) + newTest[b] = v + r = writeAndRunCandidate(newTest) + if checks(r): + print("Reduced byte", b, "from", currentTest[b], "to", v) + changed = True + updateCurrent(newTest, r) + startingPos = b + 1 + break + if changed: + break + lastByteReduceTest = bytearray(currentTest) + passInfo("Byte reduce") + + if (args.slow or args.slowest) and (oldTest == currentTest): + if currentTest != lastPatternSearchTest: + if args.verbose: + print("*" * 80 + "\nPASS: byte pattern search...") + changed = True + while changed: + changed = False + for b1 in range(0, len(currentTest)-4): + if args.verbose: + print("Trying byte pattern search from byte", str(b1) + "...") + for b2 in range(b1 + 2, len(currentTest) - 4): + v1 = (currentTest[b1], currentTest[b1 + 1]) + v2 = (currentTest[b2], currentTest[b2 + 1]) + if (v1 == v2): + ba = bytearray(v1) + part1 = currentTest[:b1] + part2 = currentTest[b1 + 2:b2] + part3 = currentTest[b2 + 2:] + banews = [] + banews.append(ba[0:1]) + banews.append(ba[1:2]) + if ba[0] > 0: + for v in range(0, ba[0]): + banews.append(bytearray([v, ba[1]])) + banews.append(bytearray([ba[0] - 1])) + if ba[1] > 0: + for v in range(0, ba[1]): + banews.append(bytearray([ba[0], v])) + for banew in banews: + newTest = part1 + banew + part2 + banew + part3 + r = writeAndRunCandidate(newTest) + if checks(r): + print("Byte pattern", tuple(ba), "at", b1, "and", b2, "changed to", tuple(banew)) + changed = True + updateCurrent(newTest, r) + break + if changed: + break + if changed: + break + lastPatternSearchTest = bytearray(currentTest) + passInfo("Byte pattern change") + + if oldTest == currentTest: + print("*" * 80) + print("DONE: NO (MORE) REDUCTIONS FOUND") + except TimeoutException: + print("*" * 80) + print("DONE: REDUCTION TIMED OUT AFTER", args.timeout, "SECONDS") + + print("=" * 80) + percent = 100.0 * ((initialSize - len(currentTest)) / initialSize) + print("Completed", iteration, "iterations:", round(time.time()-start, 2), "secs /", + candidateRuns, "execs /", str(round(percent, 2)) + "% reduction") + + if not args.noPad: + if (s[1] + 1) > len(currentTest): + print("Padding test with", (s[1] + 1) - len(currentTest), "zeroes") + padding = bytearray('\x00' * ((s[1] + 1) - len(currentTest)), 'utf-8') + currentTest = currentTest + padding + + print("Writing reduced test with", len(currentTest), "bytes to", out) + + with open(out, 'wb') as outf: + outf.write(currentTest) + + return 0 + +if "__main__" == __name__: + exit(main()) diff --git a/bin/deepstate/executors/fuzz/__init__.py b/bin/deepstate/executors/fuzz/__init__.py index 2fcc3d5b..6d4ed8ef 100644 --- a/bin/deepstate/executors/fuzz/__init__.py +++ b/bin/deepstate/executors/fuzz/__init__.py @@ -1,31 +1,31 @@ -#!/usr/bin/env python3.6 -# Copyright (c) 2019 Trail of Bits, Inc. -# -# 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 sys -import pkgutil -import importlib - - -def import_fuzzers(pkg_name): - """ - dynamically load fuzzer frontends using importlib - """ - package = sys.modules[pkg_name] - return [ - importlib.import_module(pkg_name + '.' + submod) - for _, submod, _ in pkgutil.walk_packages(package.__path__) - ] - -__all__ = import_fuzzers(__name__) +#!/usr/bin/env python3.6 +# Copyright (c) 2019 Trail of Bits, Inc. +# +# 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 sys +import pkgutil +import importlib + + +def import_fuzzers(pkg_name): + """ + dynamically load fuzzer frontends using importlib + """ + package = sys.modules[pkg_name] + return [ + importlib.import_module(pkg_name + '.' + submod) + for _, submod, _ in pkgutil.walk_packages(package.__path__) + ] + +__all__ = import_fuzzers(__name__) diff --git a/bin/deepstate/executors/fuzz/afl.py b/bin/deepstate/executors/fuzz/afl.py index 41559a39..f09a6e4c 100644 --- a/bin/deepstate/executors/fuzz/afl.py +++ b/bin/deepstate/executors/fuzz/afl.py @@ -1,204 +1,204 @@ -#!/usr/bin/env python3.6 -# Copyright (c) 2019 Trail of Bits, Inc. -# -# 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 os -import logging -import argparse - -from typing import List, Dict, Optional - -from deepstate.core import FuzzerFrontend, FuzzFrontendError - - -L = logging.getLogger(__name__) - - -class AFL(FuzzerFrontend): - """ Defines AFL fuzzer frontend """ - - NAME = "AFL" - EXECUTABLES = {"FUZZER": "afl-fuzz", "COMPILER": "afl-clang++"} - - ENVVAR = "AFL_HOME" - REQUIRE_SEEDS = True - - PUSH_DIR = os.path.join("sync_dir", "queue") - PULL_DIR = os.path.join("the_fuzzer", "queue") - CRASH_DIR = os.path.join("the_fuzzer", "crashes") - - @classmethod - def parse_args(cls) -> None: - parser: argparse.ArgumentParser = argparse.ArgumentParser( - description="Use AFL as a backend for DeepState") - - cls.parser = parser - super(AFL, cls).parse_args() - - - def compile(self) -> None: # type: ignore - lib_path: str = "/usr/local/lib/libdeepstate_AFL.a" - - flags: List[str] = list() - if self.compiler_args: - flags += [arg for arg in self.compiler_args.split(" ")] - flags.append("-ldeepstate_AFL") - - super().compile(lib_path, flags, self.out_test_name) - - - def pre_exec(self): - """ - Perform argparse and environment-related sanity checks. - """ - # check for afl-qemu-trace if in QEMU mode - if 'Q' in self.fuzzer_args or self.blackbox == True: - self.EXECUTABLES["AFL-QEMU-TRACE"] = "afl-qemu-trace" - - super().pre_exec() - - # check if core dump pattern is set as `core` - if os.path.isfile("/proc/sys/kernel/core_pattern"): - with open("/proc/sys/kernel/core_pattern") as f: - if f.read().strip() != "core": - raise FuzzFrontendError("No core dump pattern set. Execute 'echo core | sudo tee /proc/sys/kernel/core_pattern'") - - # check if CPU scaling governor is set to `performance` - if os.path.isfile("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"): - with open("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor") as f: - if not "perf" in f.read(4): - with open("/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq") as f_min: - with open("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq") as f_max: - if f_min.read() != f_max.read(): - raise FuzzFrontendError("Suboptimal CPU scaling governor. Execute 'echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor'") - - # if we are in dumb mode and we are not using crash mode - if 'n' in self.fuzzer_args and 'C' not in self.fuzzer_args: - self.require_seeds = False - - # resume fuzzing - if len(os.listdir(self.output_test_dir)) > 1: - self.check_required_directories([self.push_dir, self.pull_dir, self.crash_dir]) - self.input_seeds = '-' - L.info(f"Resuming fuzzing using seeds from {self.pull_dir} (skipping --input_seeds option).") - else: - self.setup_new_session([self.push_dir]) - - - @property - def cmd(self): - cmd_list: List[str] = list() - - # guaranteed arguments - cmd_list.extend([ - "-o", self.output_test_dir, # auto-create, reusable - ]) - - arg_keys = map(lambda x:x[0], self.fuzzer_args) - if ("d" not in arg_keys) and ("S" not in arg_keys): - cmd_list.extend(["-M", "the_fuzzer"]) - - if self.mem_limit == 0: - cmd_list.extend(["-m", "1099511627776"]) # use 1TiB as unlimited - else: - cmd_list.extend(["-m", str(self.mem_limit)]) - - for key, val in self.fuzzer_args: - if key == "d": - cmd_list.extend(["-S", "the_fuzzer"]) - elif len(key) == 1: - cmd_list.append('-{}'.format(key)) - else: - cmd_list.append('--{}'.format(key)) - if val is not None: - cmd_list.append(val) - - # QEMU mode - if self.blackbox == True: - cmd_list.append('-Q') - - # optional arguments: - # required, if provided: not auto-create and require any file inside - if self.input_seeds: - cmd_list.extend(["-i", self.input_seeds]) - - if self.exec_timeout: - cmd_list.extend(["-t", str(self.exec_timeout)]) - - if self.dictionary: - cmd_list.extend(["-x", self.dictionary]) - - cmd_list.extend([ - "--", self.binary, - "--input_stdin", - "--abort_on_fail", - "--no_fork", - "--min_log_level", str(self.min_log_level) - ]) - return cmd_list - - - def populate_stats(self): - """ - Retrieves and parses the stats file produced by AFL - """ - stat_file_path: str = os.path.join(self.output_test_dir, "the_fuzzer", "fuzzer_stats") - if not os.path.isfile(stat_file_path): - super().populate_stats() - return - - with open(stat_file_path, "r") as stat_file: - for line in stat_file: - key = line.split(":", 1)[0].strip() - value = line.split(":", 1)[1].strip() - if key in self.stats: - self.stats[key] = value - super().populate_stats() - - - def reporter(self) -> Dict[str, Optional[str]]: - """ - Report a summarized version of statistics, ideal for ensembler output. - """ - self.populate_stats() - return dict({ - "Execs Done": self.stats["execs_done"], - "Cycle Completed": self.stats["cycles_done"], - "Unique Crashes": self.stats["unique_crashes"], - "Unique Hangs": self.stats["unique_hangs"], - }) - - - def _sync_seeds(self, src, dest, excludes=[]) -> None: - excludes += ["*.cur_input", ".state"] - super()._sync_seeds(src, dest, excludes=excludes) - - - def post_exec(self) -> None: - """ - AFL post_exec outputs last updated fuzzer stats, - and (TODO) performs crash triaging with seeds from - both sync_dir and local queue. - """ - # TODO: merge output_test_dir/the_fuzzer/crashes* into one dir - super().post_exec() - - -def main(): - fuzzer = AFL() - return fuzzer.main() - - -if __name__ == "__main__": - exit(main()) +#!/usr/bin/env python3.6 +# Copyright (c) 2019 Trail of Bits, Inc. +# +# 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 os +import logging +import argparse + +from typing import List, Dict, Optional + +from deepstate.core import FuzzerFrontend, FuzzFrontendError + + +L = logging.getLogger(__name__) + + +class AFL(FuzzerFrontend): + """ Defines AFL fuzzer frontend """ + + NAME = "AFL" + EXECUTABLES = {"FUZZER": "afl-fuzz", "COMPILER": "afl-clang++"} + + ENVVAR = "AFL_HOME" + REQUIRE_SEEDS = True + + PUSH_DIR = os.path.join("sync_dir", "queue") + PULL_DIR = os.path.join("the_fuzzer", "queue") + CRASH_DIR = os.path.join("the_fuzzer", "crashes") + + @classmethod + def parse_args(cls) -> None: + parser: argparse.ArgumentParser = argparse.ArgumentParser( + description="Use AFL as a backend for DeepState") + + cls.parser = parser + super(AFL, cls).parse_args() + + + def compile(self) -> None: # type: ignore + lib_path: str = "/usr/local/lib/libdeepstate_AFL.a" + + flags: List[str] = list() + if self.compiler_args: + flags += [arg for arg in self.compiler_args.split(" ")] + flags.append("-ldeepstate_AFL") + + super().compile(lib_path, flags, self.out_test_name) + + + def pre_exec(self): + """ + Perform argparse and environment-related sanity checks. + """ + # check for afl-qemu-trace if in QEMU mode + if 'Q' in self.fuzzer_args or self.blackbox == True: + self.EXECUTABLES["AFL-QEMU-TRACE"] = "afl-qemu-trace" + + super().pre_exec() + + # check if core dump pattern is set as `core` + if os.path.isfile("/proc/sys/kernel/core_pattern"): + with open("/proc/sys/kernel/core_pattern") as f: + if f.read().strip() != "core": + raise FuzzFrontendError("No core dump pattern set. Execute 'echo core | sudo tee /proc/sys/kernel/core_pattern'") + + # check if CPU scaling governor is set to `performance` + if os.path.isfile("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"): + with open("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor") as f: + if not "perf" in f.read(4): + with open("/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq") as f_min: + with open("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq") as f_max: + if f_min.read() != f_max.read(): + raise FuzzFrontendError("Suboptimal CPU scaling governor. Execute 'echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor'") + + # if we are in dumb mode and we are not using crash mode + if 'n' in self.fuzzer_args and 'C' not in self.fuzzer_args: + self.require_seeds = False + + # resume fuzzing + if len(os.listdir(self.output_test_dir)) > 1: + self.check_required_directories([self.push_dir, self.pull_dir, self.crash_dir]) + self.input_seeds = '-' + L.info(f"Resuming fuzzing using seeds from {self.pull_dir} (skipping --input_seeds option).") + else: + self.setup_new_session([self.push_dir]) + + + @property + def cmd(self): + cmd_list: List[str] = list() + + # guaranteed arguments + cmd_list.extend([ + "-o", self.output_test_dir, # auto-create, reusable + ]) + + arg_keys = map(lambda x:x[0], self.fuzzer_args) + if ("d" not in arg_keys) and ("S" not in arg_keys): + cmd_list.extend(["-M", "the_fuzzer"]) + + if self.mem_limit == 0: + cmd_list.extend(["-m", "1099511627776"]) # use 1TiB as unlimited + else: + cmd_list.extend(["-m", str(self.mem_limit)]) + + for key, val in self.fuzzer_args: + if key == "d": + cmd_list.extend(["-S", "the_fuzzer"]) + elif len(key) == 1: + cmd_list.append('-{}'.format(key)) + else: + cmd_list.append('--{}'.format(key)) + if val is not None: + cmd_list.append(val) + + # QEMU mode + if self.blackbox == True: + cmd_list.append('-Q') + + # optional arguments: + # required, if provided: not auto-create and require any file inside + if self.input_seeds: + cmd_list.extend(["-i", self.input_seeds]) + + if self.exec_timeout: + cmd_list.extend(["-t", str(self.exec_timeout)]) + + if self.dictionary: + cmd_list.extend(["-x", self.dictionary]) + + cmd_list.extend([ + "--", self.binary, + "--input_stdin", + "--abort_on_fail", + "--no_fork", + "--min_log_level", str(self.min_log_level) + ]) + return cmd_list + + + def populate_stats(self): + """ + Retrieves and parses the stats file produced by AFL + """ + stat_file_path: str = os.path.join(self.output_test_dir, "the_fuzzer", "fuzzer_stats") + if not os.path.isfile(stat_file_path): + super().populate_stats() + return + + with open(stat_file_path, "r") as stat_file: + for line in stat_file: + key = line.split(":", 1)[0].strip() + value = line.split(":", 1)[1].strip() + if key in self.stats: + self.stats[key] = value + super().populate_stats() + + + def reporter(self) -> Dict[str, Optional[str]]: + """ + Report a summarized version of statistics, ideal for ensembler output. + """ + self.populate_stats() + return dict({ + "Execs Done": self.stats["execs_done"], + "Cycle Completed": self.stats["cycles_done"], + "Unique Crashes": self.stats["unique_crashes"], + "Unique Hangs": self.stats["unique_hangs"], + }) + + + def _sync_seeds(self, src, dest, excludes=[]) -> None: + excludes += ["*.cur_input", ".state"] + super()._sync_seeds(src, dest, excludes=excludes) + + + def post_exec(self) -> None: + """ + AFL post_exec outputs last updated fuzzer stats, + and (TODO) performs crash triaging with seeds from + both sync_dir and local queue. + """ + # TODO: merge output_test_dir/the_fuzzer/crashes* into one dir + super().post_exec() + + +def main(): + fuzzer = AFL() + return fuzzer.main() + + +if __name__ == "__main__": + exit(main()) diff --git a/bin/deepstate/executors/fuzz/angora.py b/bin/deepstate/executors/fuzz/angora.py index f57e9353..0679c10f 100644 --- a/bin/deepstate/executors/fuzz/angora.py +++ b/bin/deepstate/executors/fuzz/angora.py @@ -1,285 +1,285 @@ -#!/usr/bin/env python -# Copyright (c) 2019 Trail of Bits, Inc. -# -# 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 os -import json -import logging -import argparse -import subprocess -import time - -from typing import List, Dict, Optional, Any - -from deepstate.core import FuzzerFrontend, FuzzFrontendError - - -L = logging.getLogger(__name__) - - -class Angora(FuzzerFrontend): - - # these classvars are set under the assumption that $ANGORA_PATH is set to the built source - NAME = "Angora" - SEARCH_DIRS = ["clang+llvm/bin", "bin", "tools"] - EXECUTABLES = {"FUZZER": "angora_fuzzer", - "COMPILER": "angora-clang++", - "GEN_LIB_ABILIST": "gen_library_abilist.sh", - "CLANG_COMPILER": "clang++" - } - - ENVVAR = "ANGORA_HOME" - REQUIRE_SEEDS = True - - PUSH_DIR = os.path.join("sync_dir", "queue") - PULL_DIR = os.path.join("angora", "queue") - CRASH_DIR = os.path.join("angora", "crashes") - - - @classmethod - def parse_args(cls) -> None: - parser: argparse.ArgumentParser = argparse.ArgumentParser( - description="Use Angora as a backend for DeepState.") - - # Other compilation arguments - compile_group = parser.add_argument_group("compilation and instrumentation arguments") - compile_group.add_argument("--ignore_calls", type=str, - help="Path to static/shared libraries (colon seperated) for functions to skip (blackbox) for taint analysis.") - - # Angora-specific test execution options - parser.add_argument("taint_binary", nargs="?", type=str, - help="Path to binary compiled with taint tracking.") - - cls.parser = parser - super(Angora, cls).parse_args() - - - def compile(self) -> None: # type: ignore - """ - Compilation interface provides extra support for generating taint policy for - blacklisted ABI calls with DFsan. - """ - - env: Dict[str, str] = os.environ.copy() - - # generate ignored functions output for taint tracking - # set envvar to file with ignored lib functions for taint tracking - if self.ignore_calls: # type: ignore - - libpath: List[str] = self.ignore_calls.split(":") # type: ignore - L.debug("Ignoring library objects: %s", libpath) - - out_file: str = "abilist.txt" - - # TODO(alan): more robust library check - ignore_bufs: List[bytes] = [] - for path in libpath: - if not os.path.isfile(path): - raise FuzzFrontendError(f"Library `{path}` to skip (blackbox) is not a valid library path.") - - # instantiate command to call, but store output to buffer - cmd: List[str] = [self.EXECUTABLES["GEN_LIB_ABILIST"], path, "discard"] - L.debug("Compilation command: %s", cmd) - - out: bytes = subprocess.check_output(cmd) - ignore_bufs += [out] - - # write all to final out_file - with open(out_file, "wb") as f: - for buf in ignore_bufs: - f.write(buf) - - # set envvar for fuzzer compilers - env["ANGORA_TAINT_RULE_LIST"] = os.path.abspath(out_file) - - # make a binary with taint tracking information - # env["USE_PIN"] = "1" # TODO, add pin support - env["USE_TRACK"] = "1" - - taint_path: str = "/usr/local/lib/libdeepstate_taint.a" - L.debug("Static library path: %s", taint_path) - - taint_flags: List[str] = ["-ldeepstate_taint"] - if self.compiler_args: - taint_flags += [arg for arg in self.compiler_args.split(' ')] - L.info("Compiling %s for %s with taint tracking", self.compile_test, self.name) - super().compile(taint_path, taint_flags, self.out_test_name + ".taint", env=env) - - self.taint_binary = self.binary - self.binary = None - env.pop("USE_TRACK") - - # make a binary with light instrumentation - env["USE_FAST"] = "1" - - fast_path: str = "/usr/local/lib/libdeepstate_fast.a" - L.debug("Static library path: %s", fast_path) - - fast_flags: List[str] = ["-ldeepstate_fast"] - if self.compiler_args: - fast_flags += [arg for arg in self.compiler_args.split(" ")] - L.info("Compiling %s for %s with light instrumentation.", self.compile_test, self.name) - super().compile(fast_path, fast_flags, self.out_test_name + ".fast", env=env) - - - def pre_exec(self): - # correct version of clang is required - self._set_executables() - clang_for_angora_path = os.path.dirname(self.EXECUTABLES["CLANG_COMPILER"]) - os.environ["PATH"] = ":".join((clang_for_angora_path, os.environ.get("PATH", ""))) - L.info(f"Adding `{clang_for_angora_path}` to $PATH.") - - super().pre_exec() - - # since base method checks for self.binary by default - if not self.taint_binary: - self.parser.print_help() - raise FuzzFrontendError(f"Must provide taint binary for {self.name}.") - - if not os.path.exists(self.taint_binary): - raise FuzzFrontendError("Taint binary doesn't exist") - - # resume fuzzing - if len(os.listdir(self.output_test_dir)) > 1: - self.check_required_directories([self.push_dir, self.pull_dir, self.crash_dir]) - self.input_seeds = '-' - L.info(f"Resuming fuzzing using seeds from {self.pull_dir} (skipping --input_seeds option).") - else: - self.setup_new_session([self.push_dir]) - - if self.blackbox is True: - raise FuzzFrontendError(f"Blackbox fuzzing is not supported by {self.name}.") - - if self.dictionary: - L.error("%s can't use dictionaries.", self.name) - - - @property - def cmd(self): - cmd_list: List[str] = list() - - # guaranteed arguments - cmd_list.extend([ - "--mode", "llvm", # TODO, add pin support - "--track", os.path.abspath(self.taint_binary), - "--memory_limit", str(self.mem_limit), - "--output", self.output_test_dir, # auto-create, not reusable - "--sync_afl" - ]) - - for key, val in self.fuzzer_args: - if len(key) == 1: - cmd_list.append('-{}'.format(key)) - else: - cmd_list.append('--{}'.format(key)) - if val is not None: - cmd_list.append(val) - - # optional arguments: - # required, if provided: not auto-create and require any file inside - if self.input_seeds: - cmd_list.extend(["--input", self.input_seeds]) - - if self.exec_timeout: - cmd_list.extend(["--time_limit", str(self.exec_timeout / 1000)]) - - # autodetect - cmd_list.append("--disable_exploitation") - - return self.build_cmd(cmd_list) - - - def populate_stats(self): - """ - Parses Angora output JSON config to dict for reporting. - """ - super().populate_stats() - - stat_file_path: str = os.path.join(self.output_test_dir, "angora", "fuzzer_stats") - try: - with open(stat_file_path, "r") as stat_file: - self.stats["fuzzer_pid"] = stat_file.read().split(":", 1)[1].strip() - except: - pass - - stat_file_path = os.path.join(self.output_test_dir, "angora", "chart_stat.json") - new_stats: Dict[str, str] = {} - try: - with open(stat_file_path, "r") as stat_file: - new_stats = json.loads(stat_file.read()) - except json.decoder.JSONDecodeError as e: - L.error(f"Error parsing {stat_file_path}: {e}.") - except: - return - - # previous_stats = self.stats.copy() - - if new_stats.get("init_time"): - self.stats["start_time"] = str(int(time.time() - int(new_stats.get("init_time")))) - elif self.proc: - self.stats["start_time"] = str(int(self.start_time)) - - self.stats["last_update"] = str(int(os.path.getmtime(stat_file_path))) - - self.stats["execs_done"] = new_stats.get("num_exec", 0) - self.stats["execs_per_sec"] = new_stats.get("speed", [0])[0] - self.stats["paths_total"] = new_stats.get("num_inputs", 0) - - if new_stats.get("num_crashes"): - self.stats["unique_crashes"] = new_stats.get("num_crashes") - self.stats["unique_hangs"] = new_stats.get("num_hangs", 0) - - # all_fuzz = [] - # for one_fuzz in new_stats.get("fuzz", []): - # time_key = one_fuzz.pop("time", {}) - # s = time_key.get("secs", 0) - # ns = time_key.get("nanos", 0) - # t = float('{}.{:09d}'.format(s, ns)) - # all_fuzz.append((t, one_fuzz)) - # all_fuzz = sorted(all_fuzz, key=operator.itemgetter(0), reverse=True) - - # if len(all_fuzz) >= 2: - # last_crash_execs = 0 - # for one_fuzz in all_fuzz: - # if one_fuzz.get("num_crashes") < self.stats["unique_crashes"]: - # last_crash_execs = one_fuzz["num_exec"] - # self.stats["execs_since_crash"] = self.stats["execs_done"] - last_crash_execs - - # self.stats["command_line"] = self.command - - - def reporter(self) -> Optional[Dict[str, Any]]: - - # included to silence mypy error - if self.stats is None: - return None - - return dict({ - "Execs Done": self.stats["num_exec"], - "Unique Crashes": self.stats["num_crashes"], - "Unique Hangs": self.stats["num_hangs"], - }) - - - def post_exec(self): - pass - - -def main(): - fuzzer = Angora() - return fuzzer.main() - - -if __name__ == "__main__": - exit(main()) +#!/usr/bin/env python +# Copyright (c) 2019 Trail of Bits, Inc. +# +# 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 os +import json +import logging +import argparse +import subprocess +import time + +from typing import List, Dict, Optional, Any + +from deepstate.core import FuzzerFrontend, FuzzFrontendError + + +L = logging.getLogger(__name__) + + +class Angora(FuzzerFrontend): + + # these classvars are set under the assumption that $ANGORA_PATH is set to the built source + NAME = "Angora" + SEARCH_DIRS = ["clang+llvm/bin", "bin", "tools"] + EXECUTABLES = {"FUZZER": "angora_fuzzer", + "COMPILER": "angora-clang++", + "GEN_LIB_ABILIST": "gen_library_abilist.sh", + "CLANG_COMPILER": "clang++" + } + + ENVVAR = "ANGORA_HOME" + REQUIRE_SEEDS = True + + PUSH_DIR = os.path.join("sync_dir", "queue") + PULL_DIR = os.path.join("angora", "queue") + CRASH_DIR = os.path.join("angora", "crashes") + + + @classmethod + def parse_args(cls) -> None: + parser: argparse.ArgumentParser = argparse.ArgumentParser( + description="Use Angora as a backend for DeepState.") + + # Other compilation arguments + compile_group = parser.add_argument_group("compilation and instrumentation arguments") + compile_group.add_argument("--ignore_calls", type=str, + help="Path to static/shared libraries (colon seperated) for functions to skip (blackbox) for taint analysis.") + + # Angora-specific test execution options + parser.add_argument("taint_binary", nargs="?", type=str, + help="Path to binary compiled with taint tracking.") + + cls.parser = parser + super(Angora, cls).parse_args() + + + def compile(self) -> None: # type: ignore + """ + Compilation interface provides extra support for generating taint policy for + blacklisted ABI calls with DFsan. + """ + + env: Dict[str, str] = os.environ.copy() + + # generate ignored functions output for taint tracking + # set envvar to file with ignored lib functions for taint tracking + if self.ignore_calls: # type: ignore + + libpath: List[str] = self.ignore_calls.split(":") # type: ignore + L.debug("Ignoring library objects: %s", libpath) + + out_file: str = "abilist.txt" + + # TODO(alan): more robust library check + ignore_bufs: List[bytes] = [] + for path in libpath: + if not os.path.isfile(path): + raise FuzzFrontendError(f"Library `{path}` to skip (blackbox) is not a valid library path.") + + # instantiate command to call, but store output to buffer + cmd: List[str] = [self.EXECUTABLES["GEN_LIB_ABILIST"], path, "discard"] + L.debug("Compilation command: %s", cmd) + + out: bytes = subprocess.check_output(cmd) + ignore_bufs += [out] + + # write all to final out_file + with open(out_file, "wb") as f: + for buf in ignore_bufs: + f.write(buf) + + # set envvar for fuzzer compilers + env["ANGORA_TAINT_RULE_LIST"] = os.path.abspath(out_file) + + # make a binary with taint tracking information + # env["USE_PIN"] = "1" # TODO, add pin support + env["USE_TRACK"] = "1" + + taint_path: str = "/usr/local/lib/libdeepstate_taint.a" + L.debug("Static library path: %s", taint_path) + + taint_flags: List[str] = ["-ldeepstate_taint"] + if self.compiler_args: + taint_flags += [arg for arg in self.compiler_args.split(' ')] + L.info("Compiling %s for %s with taint tracking", self.compile_test, self.name) + super().compile(taint_path, taint_flags, self.out_test_name + ".taint", env=env) + + self.taint_binary = self.binary + self.binary = None + env.pop("USE_TRACK") + + # make a binary with light instrumentation + env["USE_FAST"] = "1" + + fast_path: str = "/usr/local/lib/libdeepstate_fast.a" + L.debug("Static library path: %s", fast_path) + + fast_flags: List[str] = ["-ldeepstate_fast"] + if self.compiler_args: + fast_flags += [arg for arg in self.compiler_args.split(" ")] + L.info("Compiling %s for %s with light instrumentation.", self.compile_test, self.name) + super().compile(fast_path, fast_flags, self.out_test_name + ".fast", env=env) + + + def pre_exec(self): + # correct version of clang is required + self._set_executables() + clang_for_angora_path = os.path.dirname(self.EXECUTABLES["CLANG_COMPILER"]) + os.environ["PATH"] = ":".join((clang_for_angora_path, os.environ.get("PATH", ""))) + L.info(f"Adding `{clang_for_angora_path}` to $PATH.") + + super().pre_exec() + + # since base method checks for self.binary by default + if not self.taint_binary: + self.parser.print_help() + raise FuzzFrontendError(f"Must provide taint binary for {self.name}.") + + if not os.path.exists(self.taint_binary): + raise FuzzFrontendError("Taint binary doesn't exist") + + # resume fuzzing + if len(os.listdir(self.output_test_dir)) > 1: + self.check_required_directories([self.push_dir, self.pull_dir, self.crash_dir]) + self.input_seeds = '-' + L.info(f"Resuming fuzzing using seeds from {self.pull_dir} (skipping --input_seeds option).") + else: + self.setup_new_session([self.push_dir]) + + if self.blackbox is True: + raise FuzzFrontendError(f"Blackbox fuzzing is not supported by {self.name}.") + + if self.dictionary: + L.error("%s can't use dictionaries.", self.name) + + + @property + def cmd(self): + cmd_list: List[str] = list() + + # guaranteed arguments + cmd_list.extend([ + "--mode", "llvm", # TODO, add pin support + "--track", os.path.abspath(self.taint_binary), + "--memory_limit", str(self.mem_limit), + "--output", self.output_test_dir, # auto-create, not reusable + "--sync_afl" + ]) + + for key, val in self.fuzzer_args: + if len(key) == 1: + cmd_list.append('-{}'.format(key)) + else: + cmd_list.append('--{}'.format(key)) + if val is not None: + cmd_list.append(val) + + # optional arguments: + # required, if provided: not auto-create and require any file inside + if self.input_seeds: + cmd_list.extend(["--input", self.input_seeds]) + + if self.exec_timeout: + cmd_list.extend(["--time_limit", str(self.exec_timeout / 1000)]) + + # autodetect + cmd_list.append("--disable_exploitation") + + return self.build_cmd(cmd_list) + + + def populate_stats(self): + """ + Parses Angora output JSON config to dict for reporting. + """ + super().populate_stats() + + stat_file_path: str = os.path.join(self.output_test_dir, "angora", "fuzzer_stats") + try: + with open(stat_file_path, "r") as stat_file: + self.stats["fuzzer_pid"] = stat_file.read().split(":", 1)[1].strip() + except: + pass + + stat_file_path = os.path.join(self.output_test_dir, "angora", "chart_stat.json") + new_stats: Dict[str, str] = {} + try: + with open(stat_file_path, "r") as stat_file: + new_stats = json.loads(stat_file.read()) + except json.decoder.JSONDecodeError as e: + L.error(f"Error parsing {stat_file_path}: {e}.") + except: + return + + # previous_stats = self.stats.copy() + + if new_stats.get("init_time"): + self.stats["start_time"] = str(int(time.time() - int(new_stats.get("init_time")))) + elif self.proc: + self.stats["start_time"] = str(int(self.start_time)) + + self.stats["last_update"] = str(int(os.path.getmtime(stat_file_path))) + + self.stats["execs_done"] = new_stats.get("num_exec", 0) + self.stats["execs_per_sec"] = new_stats.get("speed", [0])[0] + self.stats["paths_total"] = new_stats.get("num_inputs", 0) + + if new_stats.get("num_crashes"): + self.stats["unique_crashes"] = new_stats.get("num_crashes") + self.stats["unique_hangs"] = new_stats.get("num_hangs", 0) + + # all_fuzz = [] + # for one_fuzz in new_stats.get("fuzz", []): + # time_key = one_fuzz.pop("time", {}) + # s = time_key.get("secs", 0) + # ns = time_key.get("nanos", 0) + # t = float('{}.{:09d}'.format(s, ns)) + # all_fuzz.append((t, one_fuzz)) + # all_fuzz = sorted(all_fuzz, key=operator.itemgetter(0), reverse=True) + + # if len(all_fuzz) >= 2: + # last_crash_execs = 0 + # for one_fuzz in all_fuzz: + # if one_fuzz.get("num_crashes") < self.stats["unique_crashes"]: + # last_crash_execs = one_fuzz["num_exec"] + # self.stats["execs_since_crash"] = self.stats["execs_done"] - last_crash_execs + + # self.stats["command_line"] = self.command + + + def reporter(self) -> Optional[Dict[str, Any]]: + + # included to silence mypy error + if self.stats is None: + return None + + return dict({ + "Execs Done": self.stats["num_exec"], + "Unique Crashes": self.stats["num_crashes"], + "Unique Hangs": self.stats["num_hangs"], + }) + + + def post_exec(self): + pass + + +def main(): + fuzzer = Angora() + return fuzzer.main() + + +if __name__ == "__main__": + exit(main()) diff --git a/bin/deepstate/executors/fuzz/eclipser.py b/bin/deepstate/executors/fuzz/eclipser.py index e1c5feca..48a26f67 100644 --- a/bin/deepstate/executors/fuzz/eclipser.py +++ b/bin/deepstate/executors/fuzz/eclipser.py @@ -1,213 +1,213 @@ -#!/usr/bin/env python -# Copyright (c) 2019 Trail of Bits, Inc. -# -# 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 os -import glob -import shutil -import logging -import subprocess - -from typing import List, Dict - -from deepstate.core import FuzzerFrontend, FuzzFrontendError - - -L = logging.getLogger(__name__) - - -class Eclipser(FuzzerFrontend): - """ - Eclipser front-end implemented with a base FuzzerFrontend object - in order to interface the executable DLL for greybox concolic testing. - """ - - NAME = "Eclipser" - SEARCH_DIRS = ["build"] - EXECUTABLES = {"FUZZER": "Eclipser.dll", - "COMPILER": "clang++", # for regular compilation - "RUNNER": "dotnet" - } - - ENVVAR = "ECLIPSER_HOME" - REQUIRE_SEEDS = False - - PUSH_DIR = os.path.join("sync_dir", "queue") - PULL_DIR = os.path.join("sync_dir", "queue") - CRASH_DIR = os.path.join("the_fuzzer", "crashes") - - - def print_help(self): - subprocess.call([self.EXECUTABLES["RUNNER"], self.fuzzer_exe, "fuzz", "--help"]) - - - def compile(self) -> None: # type: ignore - """ - Eclipser actually doesn't need instrumentation, but we still implement - for consistency. - """ - lib_path: str = "/usr/local/lib/libdeepstate.a" - - flags: List[str] = ["-ldeepstate"] - if self.compiler_args: - flags += [arg for arg in self.compiler_args.split(" ")] - super().compile(lib_path, flags, self.out_test_name) - - - def pre_exec(self) -> None: - super().pre_exec() - - # TODO handle that somehow - L.warning("Eclipser doesn't limit child processes memory.") - - self.encoded_testcases_dir: str = os.path.join(self.output_test_dir, "the_fuzzer", "testcase") - self.encoded_crash_dir: str = os.path.join(self.output_test_dir, "the_fuzzer", "crash") - - # resume fuzzing - if len(os.listdir(self.output_test_dir)) > 1: - self.check_required_directories([self.push_dir, self.crash_dir, - self.encoded_crash_dir, self.encoded_testcases_dir]) - L.info(f"Resuming fuzzing using seeds from {self.pull_dir} (skipping --input_seeds option).") - self.decode_testcases() - self.input_seeds = self.push_dir - else: - self.setup_new_session([self.crash_dir, self.push_dir]) - - if self.blackbox == True: - L.info("Blackbox option is redundant. Eclipser works on non-instrumented binaries using QEMU by default.") - - if self.dictionary: - L.error("Eclipser can't use dictionaries.") - - - @property - def cmd(self): - cmd_list: List[str] = list() - - # get deepstate args and remove "-- binary" - deepstate_args = self.build_cmd([], input_symbol='eclipser.input') - binary_index = deepstate_args.index('--') - deepstate_args.pop(binary_index) - deepstate_args.pop(binary_index) - - # guaranteed arguments - cmd_list.extend([ - "fuzz", - "--program", self.binary, - "--src", "file", - "--fixfilepath", "eclipser.input", - "--initarg", " ".join(deepstate_args), - "--outputdir", os.path.join(self.output_test_dir, "the_fuzzer"), # auto-create, reusable - ]) - - if self.max_input_size == 0: - cmd_list.extend(["--maxfilelen", "1099511627776"]) # use 1TiB as unlimited - else: - cmd_list.extend(["--maxfilelen", str(self.max_input_size)]) - - # some timeout is required by eclipser - if self.timeout and self.timeout != 0: - timeout = self.timeout - else: - timeout = 99999 - cmd_list.extend(["--timelimit", str(timeout)]) - - for key, val in self.fuzzer_args: - if len(key) == 1: - cmd_list.append('-{}'.format(key)) - else: - cmd_list.append('--{}'.format(key)) - if val is not None: - cmd_list.append(val) - - # optional arguments: - if self.exec_timeout: - cmd_list.extend(["--exectimeout", str(self.exec_timeout)]) - - # not required, if provided: not auto-create and require any file inside - if self.input_seeds: - cmd_list.extend(["--initseedsdir", self.input_seeds]) - - # no call to helper build_cmd - return cmd_list - - - def ensemble(self) -> None: # type: ignore - """ - Overrides queue path for ensemble-fuzz - """ - local_queue: str = os.path.join(self.output_test_dir, "testcase/") - super().ensemble(local_queue) - - - def decode_testcases(self): - L.info("Performing decoding on testcases and crashes") - decoded_path: str = os.path.join(self.output_test_dir, "decoded") - - subprocess.call([self.EXECUTABLES["RUNNER"], self.fuzzer_exe, "decode", - "-i", self.encoded_crash_dir, "-o", decoded_path], - stdout=subprocess.PIPE) - for f in glob.glob(os.path.join(decoded_path, "decoded_files", "*")): - shutil.copy(f, self.crash_dir) - shutil.rmtree(decoded_path) - - subprocess.call([self.EXECUTABLES["RUNNER"], self.fuzzer_exe, "decode", - "-i", self.encoded_testcases_dir, "-o", decoded_path], - stdout=subprocess.PIPE) - for f in glob.glob(os.path.join(decoded_path, "decoded_files", "*")): - shutil.copy(f, self.pull_dir) - shutil.rmtree(decoded_path) - - - def manage(self): - self.decode_testcases() - super().manage() - - - def post_exec(self) -> None: - """ - Decode and minimize testcases after fuzzing. - """ - self.decode_testcases() - - - def populate_stats(self): - super().populate_stats() - - - def reporter(self) -> Dict[str, int]: - """ - TODO: report more metrics - """ - - num_crashes: int = len([crash for crash in os.listdir(self.output_test_dir + "/crash") - if os.path.isfile(crash)]) - return dict({ - "Unique Crashes": num_crashes - }) - - -def main(): - try: - fuzzer = Eclipser() - fuzzer.parse_args() - fuzzer.run(runner=fuzzer.EXECUTABLES["RUNNER"]) - return 0 - except FuzzFrontendError as e: - L.error(e) - return 1 - - -if __name__ == "__main__": - exit(main()) +#!/usr/bin/env python +# Copyright (c) 2019 Trail of Bits, Inc. +# +# 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 os +import glob +import shutil +import logging +import subprocess + +from typing import List, Dict + +from deepstate.core import FuzzerFrontend, FuzzFrontendError + + +L = logging.getLogger(__name__) + + +class Eclipser(FuzzerFrontend): + """ + Eclipser front-end implemented with a base FuzzerFrontend object + in order to interface the executable DLL for greybox concolic testing. + """ + + NAME = "Eclipser" + SEARCH_DIRS = ["build"] + EXECUTABLES = {"FUZZER": "Eclipser.dll", + "COMPILER": "clang++", # for regular compilation + "RUNNER": "dotnet" + } + + ENVVAR = "ECLIPSER_HOME" + REQUIRE_SEEDS = False + + PUSH_DIR = os.path.join("sync_dir", "queue") + PULL_DIR = os.path.join("sync_dir", "queue") + CRASH_DIR = os.path.join("the_fuzzer", "crashes") + + + def print_help(self): + subprocess.call([self.EXECUTABLES["RUNNER"], self.fuzzer_exe, "fuzz", "--help"]) + + + def compile(self) -> None: # type: ignore + """ + Eclipser actually doesn't need instrumentation, but we still implement + for consistency. + """ + lib_path: str = "/usr/local/lib/libdeepstate.a" + + flags: List[str] = ["-ldeepstate"] + if self.compiler_args: + flags += [arg for arg in self.compiler_args.split(" ")] + super().compile(lib_path, flags, self.out_test_name) + + + def pre_exec(self) -> None: + super().pre_exec() + + # TODO handle that somehow + L.warning("Eclipser doesn't limit child processes memory.") + + self.encoded_testcases_dir: str = os.path.join(self.output_test_dir, "the_fuzzer", "testcase") + self.encoded_crash_dir: str = os.path.join(self.output_test_dir, "the_fuzzer", "crash") + + # resume fuzzing + if len(os.listdir(self.output_test_dir)) > 1: + self.check_required_directories([self.push_dir, self.crash_dir, + self.encoded_crash_dir, self.encoded_testcases_dir]) + L.info(f"Resuming fuzzing using seeds from {self.pull_dir} (skipping --input_seeds option).") + self.decode_testcases() + self.input_seeds = self.push_dir + else: + self.setup_new_session([self.crash_dir, self.push_dir]) + + if self.blackbox == True: + L.info("Blackbox option is redundant. Eclipser works on non-instrumented binaries using QEMU by default.") + + if self.dictionary: + L.error("Eclipser can't use dictionaries.") + + + @property + def cmd(self): + cmd_list: List[str] = list() + + # get deepstate args and remove "-- binary" + deepstate_args = self.build_cmd([], input_symbol='eclipser.input') + binary_index = deepstate_args.index('--') + deepstate_args.pop(binary_index) + deepstate_args.pop(binary_index) + + # guaranteed arguments + cmd_list.extend([ + "fuzz", + "--program", self.binary, + "--src", "file", + "--fixfilepath", "eclipser.input", + "--initarg", " ".join(deepstate_args), + "--outputdir", os.path.join(self.output_test_dir, "the_fuzzer"), # auto-create, reusable + ]) + + if self.max_input_size == 0: + cmd_list.extend(["--maxfilelen", "1099511627776"]) # use 1TiB as unlimited + else: + cmd_list.extend(["--maxfilelen", str(self.max_input_size)]) + + # some timeout is required by eclipser + if self.timeout and self.timeout != 0: + timeout = self.timeout + else: + timeout = 99999 + cmd_list.extend(["--timelimit", str(timeout)]) + + for key, val in self.fuzzer_args: + if len(key) == 1: + cmd_list.append('-{}'.format(key)) + else: + cmd_list.append('--{}'.format(key)) + if val is not None: + cmd_list.append(val) + + # optional arguments: + if self.exec_timeout: + cmd_list.extend(["--exectimeout", str(self.exec_timeout)]) + + # not required, if provided: not auto-create and require any file inside + if self.input_seeds: + cmd_list.extend(["--initseedsdir", self.input_seeds]) + + # no call to helper build_cmd + return cmd_list + + + def ensemble(self) -> None: # type: ignore + """ + Overrides queue path for ensemble-fuzz + """ + local_queue: str = os.path.join(self.output_test_dir, "testcase/") + super().ensemble(local_queue) + + + def decode_testcases(self): + L.info("Performing decoding on testcases and crashes") + decoded_path: str = os.path.join(self.output_test_dir, "decoded") + + subprocess.call([self.EXECUTABLES["RUNNER"], self.fuzzer_exe, "decode", + "-i", self.encoded_crash_dir, "-o", decoded_path], + stdout=subprocess.PIPE) + for f in glob.glob(os.path.join(decoded_path, "decoded_files", "*")): + shutil.copy(f, self.crash_dir) + shutil.rmtree(decoded_path) + + subprocess.call([self.EXECUTABLES["RUNNER"], self.fuzzer_exe, "decode", + "-i", self.encoded_testcases_dir, "-o", decoded_path], + stdout=subprocess.PIPE) + for f in glob.glob(os.path.join(decoded_path, "decoded_files", "*")): + shutil.copy(f, self.pull_dir) + shutil.rmtree(decoded_path) + + + def manage(self): + self.decode_testcases() + super().manage() + + + def post_exec(self) -> None: + """ + Decode and minimize testcases after fuzzing. + """ + self.decode_testcases() + + + def populate_stats(self): + super().populate_stats() + + + def reporter(self) -> Dict[str, int]: + """ + TODO: report more metrics + """ + + num_crashes: int = len([crash for crash in os.listdir(self.output_test_dir + "/crash") + if os.path.isfile(crash)]) + return dict({ + "Unique Crashes": num_crashes + }) + + +def main(): + try: + fuzzer = Eclipser() + fuzzer.parse_args() + fuzzer.run(runner=fuzzer.EXECUTABLES["RUNNER"]) + return 0 + except FuzzFrontendError as e: + L.error(e) + return 1 + + +if __name__ == "__main__": + exit(main()) diff --git a/bin/deepstate/executors/fuzz/honggfuzz.py b/bin/deepstate/executors/fuzz/honggfuzz.py index 08ea285a..4bf3f092 100644 --- a/bin/deepstate/executors/fuzz/honggfuzz.py +++ b/bin/deepstate/executors/fuzz/honggfuzz.py @@ -1,152 +1,152 @@ -# Copyright (c) 2019 Trail of Bits, Inc. -# -# 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 os -import logging -import argparse - -from typing import List, Dict, Optional - -from deepstate.core import FuzzerFrontend - -L = logging.getLogger(__name__) - - -class Honggfuzz(FuzzerFrontend): - - NAME = "HonggFuzz" - SEARCH_DIRS = ["hfuzz_cc"] - EXECUTABLES = {"FUZZER": "honggfuzz", - "COMPILER": "hfuzz-clang++" - } - - REQUIRE_SEEDS = True - - PUSH_DIR = os.path.join("sync_dir", "queue") - PULL_DIR = os.path.join("sync_dir", "queue") - CRASH_DIR = os.path.join("the_fuzzer", "crashes") - - - @classmethod - def parse_args(cls) -> None: - parser: argparse.ArgumentParser = argparse.ArgumentParser( - description="Use Honggfuzz as a backend for DeepState") - - cls.parser = parser - super(Honggfuzz, cls).parse_args() - - - def compile(self) -> None: # type: ignore - lib_path: str = "/usr/local/lib/libdeepstate_HFUZZ.a" - - # check if we should fallback to default static library - if not os.path.isfile(lib_path): - flags: List[str] = ["-ldeepstate"] - else: - flags = ["-ldeepstate_HFUZZ"] - - if self.compiler_args: - flags += [arg for arg in self.compiler_args.split(" ")] - super().compile(lib_path, flags, self.out_test_name) - - - def pre_exec(self): - super().pre_exec() - - # resume fuzzing - if len(os.listdir(self.output_test_dir)) > 1: - self.check_required_directories([self.push_dir, self.pull_dir, self.crash_dir]) - self.input_seeds = self.push_dir - L.info(f"Resuming fuzzing using seeds from {self.push_dir} (skipping --input_seeds option).") - else: - self.setup_new_session([self.pull_dir, self.crash_dir]) - - - @property - def cmd(self): - cmd_list: List[str] = list() - - # guaranteed arguments - cmd_list.extend([ - "--workspace", self.output_test_dir, - "--output", self.push_dir, # auto-create, reusable - "--crashdir", self.crash_dir, - # "--logfile", os.path.join(self.output_test_dir, "hfuzz_log.txt"), - # "--verbose", - "--rlimit_rss", str(self.mem_limit), - "--threads", "1" - ]) - - if self.max_input_size == 0: - cmd_list.extend(["--max_file_size", "1099511627776"]) # use 1TiB as unlimited - else: - cmd_list.extend(["--max_file_size", str(self.max_input_size)]) - - # TODO add qemu mode - if self.blackbox == True: - cmd_list.append("--noinst") - - for key, val in self.fuzzer_args: - if len(key) == 1: - cmd_list.append('-{}'.format(key)) - else: - cmd_list.append('--{}'.format(key)) - if val is not None: - cmd_list.append(val) - - # optional arguments: - # required, if provided: not auto-create and not require any files inside - if self.input_seeds: - cmd_list.extend(["--input", self.input_seeds]) - - if self.exec_timeout: - cmd_list.extend(["--timeout", str(self.exec_timeout / 1000)]) - - if self.dictionary: - cmd_list.extend(["--dict", self.dictionary]) - - # TODO: autodetect hardware features - cmd_list.append("--linux_keep_aslr") - - return self.build_cmd(cmd_list, input_symbol="___FILE___") - - - def populate_stats(self): - """ - Retrieves and parses the stats file produced by Honggfuzz - """ - super().populate_stats() - - - def reporter(self) -> Dict[str, Optional[str]]: - """ - Report a summarized version of statistics, ideal for ensembler output. - """ - return dict({ - "Unique Crashes": self.stats["CRASHES"], - "Mutations Per Run": self.stats["mutationsPerRun"] - }) - - - def post_exec(self) -> None: - super().post_exec() - - -def main(): - fuzzer = Honggfuzz(envvar="HONGGFUZZ_HOME") - return fuzzer.main() - - -if __name__ == "__main__": - exit(main()) +# Copyright (c) 2019 Trail of Bits, Inc. +# +# 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 os +import logging +import argparse + +from typing import List, Dict, Optional + +from deepstate.core import FuzzerFrontend + +L = logging.getLogger(__name__) + + +class Honggfuzz(FuzzerFrontend): + + NAME = "HonggFuzz" + SEARCH_DIRS = ["hfuzz_cc"] + EXECUTABLES = {"FUZZER": "honggfuzz", + "COMPILER": "hfuzz-clang++" + } + + REQUIRE_SEEDS = True + + PUSH_DIR = os.path.join("sync_dir", "queue") + PULL_DIR = os.path.join("sync_dir", "queue") + CRASH_DIR = os.path.join("the_fuzzer", "crashes") + + + @classmethod + def parse_args(cls) -> None: + parser: argparse.ArgumentParser = argparse.ArgumentParser( + description="Use Honggfuzz as a backend for DeepState") + + cls.parser = parser + super(Honggfuzz, cls).parse_args() + + + def compile(self) -> None: # type: ignore + lib_path: str = "/usr/local/lib/libdeepstate_HFUZZ.a" + + # check if we should fallback to default static library + if not os.path.isfile(lib_path): + flags: List[str] = ["-ldeepstate"] + else: + flags = ["-ldeepstate_HFUZZ"] + + if self.compiler_args: + flags += [arg for arg in self.compiler_args.split(" ")] + super().compile(lib_path, flags, self.out_test_name) + + + def pre_exec(self): + super().pre_exec() + + # resume fuzzing + if len(os.listdir(self.output_test_dir)) > 1: + self.check_required_directories([self.push_dir, self.pull_dir, self.crash_dir]) + self.input_seeds = self.push_dir + L.info(f"Resuming fuzzing using seeds from {self.push_dir} (skipping --input_seeds option).") + else: + self.setup_new_session([self.pull_dir, self.crash_dir]) + + + @property + def cmd(self): + cmd_list: List[str] = list() + + # guaranteed arguments + cmd_list.extend([ + "--workspace", self.output_test_dir, + "--output", self.push_dir, # auto-create, reusable + "--crashdir", self.crash_dir, + # "--logfile", os.path.join(self.output_test_dir, "hfuzz_log.txt"), + # "--verbose", + "--rlimit_rss", str(self.mem_limit), + "--threads", "1" + ]) + + if self.max_input_size == 0: + cmd_list.extend(["--max_file_size", "1099511627776"]) # use 1TiB as unlimited + else: + cmd_list.extend(["--max_file_size", str(self.max_input_size)]) + + # TODO add qemu mode + if self.blackbox == True: + cmd_list.append("--noinst") + + for key, val in self.fuzzer_args: + if len(key) == 1: + cmd_list.append('-{}'.format(key)) + else: + cmd_list.append('--{}'.format(key)) + if val is not None: + cmd_list.append(val) + + # optional arguments: + # required, if provided: not auto-create and not require any files inside + if self.input_seeds: + cmd_list.extend(["--input", self.input_seeds]) + + if self.exec_timeout: + cmd_list.extend(["--timeout", str(self.exec_timeout / 1000)]) + + if self.dictionary: + cmd_list.extend(["--dict", self.dictionary]) + + # TODO: autodetect hardware features + cmd_list.append("--linux_keep_aslr") + + return self.build_cmd(cmd_list, input_symbol="___FILE___") + + + def populate_stats(self): + """ + Retrieves and parses the stats file produced by Honggfuzz + """ + super().populate_stats() + + + def reporter(self) -> Dict[str, Optional[str]]: + """ + Report a summarized version of statistics, ideal for ensembler output. + """ + return dict({ + "Unique Crashes": self.stats["CRASHES"], + "Mutations Per Run": self.stats["mutationsPerRun"] + }) + + + def post_exec(self) -> None: + super().post_exec() + + +def main(): + fuzzer = Honggfuzz(envvar="HONGGFUZZ_HOME") + return fuzzer.main() + + +if __name__ == "__main__": + exit(main()) diff --git a/bin/deepstate/executors/fuzz/libfuzzer.py b/bin/deepstate/executors/fuzz/libfuzzer.py index ff066d2e..c9399426 100644 --- a/bin/deepstate/executors/fuzz/libfuzzer.py +++ b/bin/deepstate/executors/fuzz/libfuzzer.py @@ -1,175 +1,175 @@ -#!/usr/bin/env python3.6 -# Copyright (c) 2019 Trail of Bits, Inc. -# -# 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 os -import logging -import argparse - -from typing import List - -from deepstate.core import FuzzerFrontend, FuzzFrontendError - -L = logging.getLogger(__name__) - -class LibFuzzer(FuzzerFrontend): - - NAME = "libFuzzer" - EXECUTABLES = {"FUZZER": "clang++", # placeholder - "COMPILER": "clang++" - } - - ENVVAR = "LIBFUZZER_HOME" - REQUIRE_SEEDS = False - - PUSH_DIR = os.path.join("sync_dir", "queue") - PULL_DIR = os.path.join("sync_dir", "queue") - CRASH_DIR = os.path.join("the_fuzzer", "crashes") - - @classmethod - def parse_args(cls) -> None: - parser: argparse.ArgumentParser = argparse.ArgumentParser( - description="Use libFuzzer as a backend for DeepState") - - cls.parser = parser - super(LibFuzzer, cls).parse_args() - - - def compile(self) -> None: # type: ignore - lib_path: str = "/usr/local/lib/libdeepstate_LF.a" - - flags: List[str] = ["-ldeepstate_LF", "-fsanitize=fuzzer,undefined"] - if self.compiler_args: - flags += [arg for arg in self.compiler_args.split(" ")] - super().compile(lib_path, flags, self.out_test_name) - - - def pre_exec(self) -> None: - """ - Perform argparse and environment-related sanity checks. - """ - # first, redefine and override fuzzer as harness executable - if self.binary: - self.binary = os.path.abspath(self.binary) - self.fuzzer_exe = self.binary # type: ignore - - super().pre_exec() - - # again, because we may had run compiler - if not self.binary: - raise FuzzFrontendError("Binary not set.") - self.binary = os.path.abspath(self.binary) - self.fuzzer_exe = self.binary # type: ignore - - if self.blackbox is True: - raise FuzzFrontendError("Blackbox fuzzing is not supported by libFuzzer.") - - # resuming fuzzing - if len(os.listdir(self.output_test_dir)) > 0: - self.check_required_directories([self.push_dir, self.pull_dir, self.crash_dir]) - self.input_seeds = None - L.info(f"Resuming fuzzing using seeds from {self.push_dir} (skipping --input_seeds option).") - else: - self.setup_new_session([self.pull_dir, self.crash_dir]) - - - @property - def cmd(self): - """ - Initializes a command for an in-process libFuzzer instance that runs - indefinitely until an interrupt. - """ - cmd_list: List[str] = list() - - # guaranteed arguments - cmd_list.extend([ - "-rss_limit_mb={}".format(self.mem_limit), - "-max_len={}".format(self.max_input_size), - "-artifact_prefix={}".format(self.crash_dir + "/"), - # "-jobs={}".format(0), - # "-workers={}".format(1), - # "-fork=1", - "-reload=1", - "-runs=-1", - "-print_final_stats=1" - ]) - - for key, val in self.fuzzer_args: - if val is not None: - cmd_list.append('-{}={}'.format(key, val)) - else: - cmd_list.append('-{}'.format(key)) - - # optional arguments: - if self.dictionary: - cmd_list.append("-dict={}".format(self.dictionary)) - - if self.exec_timeout: - cmd_list.append("-timeout={}".format(self.exec_timeout / 1000)) - - # must be here, this are positional args - cmd_list.append(self.push_dir) # no auto-create, reusable - - # not required, if provided: not auto-create and not require any files inside - if self.input_seeds: - cmd_list.append(self.input_seeds) - - return cmd_list - - - def populate_stats(self): - super().populate_stats() - - if not os.path.isfile(self.output_file): - return - - with open(self.output_file, "rb") as f: - for line in f: - # libFuzzer under DeepState have broken output - # splitted into multiple lines, preceded with "EXTERNAL:" - if line.startswith(b"EXTERNAL: "): - line = line.split(b":", 1)[1].strip() - if line.startswith(b"#"): - # new event code - self.stats["execs_done"] = line.split()[0].strip(b"#").decode() - - elif b":" in line: - line = line.split(b":", 1)[1].strip() - if b":" in line: - key, value = line.split(b":", 1) - if key == b"exec/s": - self.stats["execs_per_sec"] = value.strip().decode() - elif key == b"units": - self.stats["paths_total"] = value.strip().decode() - elif key == b"cov": - self.stats["bitmap_cvg"] = value.strip().decode() - - - def _sync_seeds(self, src, dest, excludes=[]) -> None: - excludes += ["*.cur_input", ".state"] - super()._sync_seeds(src, dest, excludes=excludes) - - - def post_exec(self): - # TODO: remove crashes from seeds dir and from sync_dir - pass - - -def main(): - fuzzer = LibFuzzer() - return fuzzer.main() - - -if __name__ == "__main__": - exit(main()) +#!/usr/bin/env python3.6 +# Copyright (c) 2019 Trail of Bits, Inc. +# +# 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 os +import logging +import argparse + +from typing import List + +from deepstate.core import FuzzerFrontend, FuzzFrontendError + +L = logging.getLogger(__name__) + +class LibFuzzer(FuzzerFrontend): + + NAME = "libFuzzer" + EXECUTABLES = {"FUZZER": "clang++", # placeholder + "COMPILER": "clang++" + } + + ENVVAR = "LIBFUZZER_HOME" + REQUIRE_SEEDS = False + + PUSH_DIR = os.path.join("sync_dir", "queue") + PULL_DIR = os.path.join("sync_dir", "queue") + CRASH_DIR = os.path.join("the_fuzzer", "crashes") + + @classmethod + def parse_args(cls) -> None: + parser: argparse.ArgumentParser = argparse.ArgumentParser( + description="Use libFuzzer as a backend for DeepState") + + cls.parser = parser + super(LibFuzzer, cls).parse_args() + + + def compile(self) -> None: # type: ignore + lib_path: str = "/usr/local/lib/libdeepstate_LF.a" + + flags: List[str] = ["-ldeepstate_LF", "-fsanitize=fuzzer,undefined"] + if self.compiler_args: + flags += [arg for arg in self.compiler_args.split(" ")] + super().compile(lib_path, flags, self.out_test_name) + + + def pre_exec(self) -> None: + """ + Perform argparse and environment-related sanity checks. + """ + # first, redefine and override fuzzer as harness executable + if self.binary: + self.binary = os.path.abspath(self.binary) + self.fuzzer_exe = self.binary # type: ignore + + super().pre_exec() + + # again, because we may had run compiler + if not self.binary: + raise FuzzFrontendError("Binary not set.") + self.binary = os.path.abspath(self.binary) + self.fuzzer_exe = self.binary # type: ignore + + if self.blackbox is True: + raise FuzzFrontendError("Blackbox fuzzing is not supported by libFuzzer.") + + # resuming fuzzing + if len(os.listdir(self.output_test_dir)) > 0: + self.check_required_directories([self.push_dir, self.pull_dir, self.crash_dir]) + self.input_seeds = None + L.info(f"Resuming fuzzing using seeds from {self.push_dir} (skipping --input_seeds option).") + else: + self.setup_new_session([self.pull_dir, self.crash_dir]) + + + @property + def cmd(self): + """ + Initializes a command for an in-process libFuzzer instance that runs + indefinitely until an interrupt. + """ + cmd_list: List[str] = list() + + # guaranteed arguments + cmd_list.extend([ + "-rss_limit_mb={}".format(self.mem_limit), + "-max_len={}".format(self.max_input_size), + "-artifact_prefix={}".format(self.crash_dir + "/"), + # "-jobs={}".format(0), + # "-workers={}".format(1), + # "-fork=1", + "-reload=1", + "-runs=-1", + "-print_final_stats=1" + ]) + + for key, val in self.fuzzer_args: + if val is not None: + cmd_list.append('-{}={}'.format(key, val)) + else: + cmd_list.append('-{}'.format(key)) + + # optional arguments: + if self.dictionary: + cmd_list.append("-dict={}".format(self.dictionary)) + + if self.exec_timeout: + cmd_list.append("-timeout={}".format(self.exec_timeout / 1000)) + + # must be here, this are positional args + cmd_list.append(self.push_dir) # no auto-create, reusable + + # not required, if provided: not auto-create and not require any files inside + if self.input_seeds: + cmd_list.append(self.input_seeds) + + return cmd_list + + + def populate_stats(self): + super().populate_stats() + + if not os.path.isfile(self.output_file): + return + + with open(self.output_file, "rb") as f: + for line in f: + # libFuzzer under DeepState have broken output + # splitted into multiple lines, preceded with "EXTERNAL:" + if line.startswith(b"EXTERNAL: "): + line = line.split(b":", 1)[1].strip() + if line.startswith(b"#"): + # new event code + self.stats["execs_done"] = line.split()[0].strip(b"#").decode() + + elif b":" in line: + line = line.split(b":", 1)[1].strip() + if b":" in line: + key, value = line.split(b":", 1) + if key == b"exec/s": + self.stats["execs_per_sec"] = value.strip().decode() + elif key == b"units": + self.stats["paths_total"] = value.strip().decode() + elif key == b"cov": + self.stats["bitmap_cvg"] = value.strip().decode() + + + def _sync_seeds(self, src, dest, excludes=[]) -> None: + excludes += ["*.cur_input", ".state"] + super()._sync_seeds(src, dest, excludes=excludes) + + + def post_exec(self): + # TODO: remove crashes from seeds dir and from sync_dir + pass + + +def main(): + fuzzer = LibFuzzer() + return fuzzer.main() + + +if __name__ == "__main__": + exit(main()) diff --git a/bin/deepstate/executors/symex/__init__.py b/bin/deepstate/executors/symex/__init__.py index 2fcc3d5b..6d4ed8ef 100644 --- a/bin/deepstate/executors/symex/__init__.py +++ b/bin/deepstate/executors/symex/__init__.py @@ -1,31 +1,31 @@ -#!/usr/bin/env python3.6 -# Copyright (c) 2019 Trail of Bits, Inc. -# -# 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 sys -import pkgutil -import importlib - - -def import_fuzzers(pkg_name): - """ - dynamically load fuzzer frontends using importlib - """ - package = sys.modules[pkg_name] - return [ - importlib.import_module(pkg_name + '.' + submod) - for _, submod, _ in pkgutil.walk_packages(package.__path__) - ] - -__all__ = import_fuzzers(__name__) +#!/usr/bin/env python3.6 +# Copyright (c) 2019 Trail of Bits, Inc. +# +# 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 sys +import pkgutil +import importlib + + +def import_fuzzers(pkg_name): + """ + dynamically load fuzzer frontends using importlib + """ + package = sys.modules[pkg_name] + return [ + importlib.import_module(pkg_name + '.' + submod) + for _, submod, _ in pkgutil.walk_packages(package.__path__) + ] + +__all__ = import_fuzzers(__name__) diff --git a/bin/deepstate/executors/symex/angr.py b/bin/deepstate/executors/symex/angr.py index 57734c57..2d335389 100644 --- a/bin/deepstate/executors/symex/angr.py +++ b/bin/deepstate/executors/symex/angr.py @@ -1,518 +1,518 @@ -#!/usr/bin/env python3.6 -# Copyright (c) 2019 Trail of Bits, Inc. -# -# 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 angr -import logging -import multiprocessing -import traceback - -from deepstate.core import SymexFrontend, TestInfo - -L = logging.getLogger(__name__) - - -class DeepAngr(SymexFrontend): - - NAME = "Angr" - - def __init__(self, state=None, procedure=None): - super(DeepAngr, self).__init__() - if procedure: - self.procedure = procedure - self.state = procedure.state - elif state: - self.procedure = None - self.state = state - - def __del__(self): - self.procedure = None - self.state = None - - def get_context(self): - return self.state.globals - - def is_symbolic(self, val): - if isinstance(val, (int, int)): - return False - return self.state.solver.symbolic(val) - - def create_symbol(self, name, size_in_bits): - return self.state.solver.Unconstrained('name', size_in_bits) - - def read_uintptr_t(self, ea, concretize=True, constrain=False): - addr_size_bytes = self.state.arch.bits // 8 - endness = self.state.arch.memory_endness - next_ea = ea + addr_size_bytes - val = self.state.memory.load(ea, size=addr_size_bytes, endness=endness) - if concretize: - val = self.concretize(val, constrain=constrain) - return val, next_ea - - def read_uint64_t(self, ea, concretize=True, constrain=False): - endness = self.state.arch.memory_endness - val = self.state.memory.load(ea, size=8, endness=endness) - if concretize: - val = self.concretize(val, constrain=constrain) - return val, ea + 8 - - def read_uint32_t(self, ea, concretize=True, constrain=False): - endness = self.state.arch.memory_endness - val = self.state.memory.load(ea, size=4, endness=endness) - if concretize: - val = self.concretize(val, constrain=constrain) - return val, ea + 4 - - def read_uint8_t(self, ea, concretize=True, constrain=False): - val = self.state.memory.load(ea, size=1) - if concretize: - val = self.concretize(val, constrain=constrain) - if isinstance(val, str): - assert len(val) == 1 - val = ord(val) - return val, ea + 1 - - def write_uint8_t(self, ea, val): - self.state.memory.store(ea, val, size=1) - return ea + 1 - - def write_uint32_t(self, ea, val): - self.state.memory.store(ea, val, size=4) - return ea + 4 - - def concretize(self, val, constrain=False): - if isinstance(val, (int, int)): - return val - elif isinstance(val, str): - assert len(val) == 1 - return ord(val[0]) - - concrete_val = self.state.solver.eval(val, cast_to=int) - if constrain: - self.add_constraint(val == concrete_val) - - return concrete_val - - def concretize_min(self, val, constrain=False): - if isinstance(val, (int, int)): - return val - concrete_val = self.state.solver.min(val) - if constrain: - self.add_constraint(val == concrete_val) - return concrete_val - - def concretize_max(self, val, constrain=False): - if isinstance(val, (int, int)): - return val - concrete_val = self.state.solver.max(val) - if constrain: - self.add_constraint(val == concrete_val) - return concrete_val - - def concretize_many(self, val, max_num): - assert 0 < max_num - if isinstance(val, (int, int)): - return [val] - return self.state.solver.eval_upto(val, max_num, cast_to=int) - - def add_constraint(self, expr): - if self.is_symbolic(expr): - self.state.solver.add(expr) - return self.state.solver.satisfiable() - else: - return True - - def pass_test(self): - super(DeepAngr, self).pass_test() - self.procedure.exit(0) - - def fail_test(self): - super(DeepAngr, self).fail_test() - self.procedure.exit(1) - - def abandon_test(self): - super(DeepAngr, self).abandon_test() - self.procedure.exit(1) - - -def hook_function(project, ea, cls): - """Hook the function `ea` with the SimProcedure `cls`.""" - project.hook(ea, cls(project=project)) - - -class IsSymbolicUInt(angr.SimProcedure): - """Implements DeepState_IsSymbolicUInt, which returns 1 if its input argument - has more then one solutions, and zero otherwise.""" - def run(self, arg): - return DeepAngr(procedure=self).api_is_symbolic_uint(arg) - - -class Assume(angr.SimProcedure): - """Implements _DeepState_Assume, which tries to inject a constraint.""" - def run(self, arg, expr_ea, file_ea, line): - DeepAngr(procedure=self).api_assume(arg, expr_ea, file_ea, line) - - -class Pass(angr.SimProcedure): - """Implements DeepState_Pass, which notifies us of a passing test.""" - def run(self): - DeepAngr(procedure=self).api_pass() - - -class Crash(angr.SimProcedure): - """Implements DeepState_Crash, which notifies us of a crashing test.""" - def run(self): - DeepAngr(procedure=self).api_crash() - - -class Fail(angr.SimProcedure): - """Implements DeepState_Fail, which notifies us of a failing test.""" - def run(self): - DeepAngr(procedure=self).api_fail() - - -class Abandon(angr.SimProcedure): - """Implements DeepState_Fail, which notifies us of a failing test.""" - def run(self, reason): - DeepAngr(procedure=self).api_abandon(reason) - - -class SoftFail(angr.SimProcedure): - """Implements DeepState_SoftFail, which notifies us of a failing test.""" - def run(self): - DeepAngr(procedure=self).api_soft_fail() - - -class ConcretizeData(angr.SimProcedure): - """Implements the `Deepstate_ConcretizeData` API function, which lets the - programmer concretize some data in the exclusive range - `[begin_ea, end_ea)`.""" - def run(self, begin_ea, end_ea): - return DeepAngr(procedure=self).api_concretize_data(begin_ea, end_ea) - - -class ConcretizeCStr(angr.SimProcedure): - """Implements the `Deepstate_ConcretizeCStr` API function, which lets the - programmer concretize a NUL-terminated string starting at `begin_ea`.""" - def run(self, begin_ea): - return DeepAngr(procedure=self).api_concretize_cstr(begin_ea) - - -class MinUInt(angr.SimProcedure): - """Implements the `Deepstate_MinUInt` API function, which lets the - programmer ask for the minimum satisfiable value of an unsigned integer.""" - def run(self, val): - return DeepAngr(procedure=self).api_min_uint(val) - - -class MaxUInt(angr.SimProcedure): - """Implements the `Deepstate_MaxUInt` API function, which lets the - programmer ask for the minimum satisfiable value of a signed integer.""" - def run(self, val): - return DeepAngr(procedure=self).api_max_uint(val) - - -class StreamInt(angr.SimProcedure): - """Implements _DeepState_StreamInt, which gives us an integer to stream, and - the format to use for streaming.""" - def run(self, level, format_ea, unpack_ea, uint64_ea): - DeepAngr(procedure=self).api_stream_int(level, format_ea, unpack_ea, - uint64_ea) - -class StreamFloat(angr.SimProcedure): - """Implements _DeepState_StreamFloat, which gives us an double to stream, and - the format to use for streaming.""" - def run(self, level, format_ea, unpack_ea, double_ea): - DeepAngr(procedure=self).api_stream_float(level, format_ea, unpack_ea, - double_ea) - - -class StreamString(angr.SimProcedure): - """Implements _DeepState_StreamString, which gives us an double to stream, and - the format to use for streaming.""" - def run(self, level, format_ea, str_ea): - DeepAngr(procedure=self).api_stream_string(level, format_ea, str_ea) - - -class ClearStream(angr.SimProcedure): - """Implements DeepState_ClearStream, which clears the contents of a stream for - level `level`.""" - def run(self, level): - DeepAngr(procedure=self).api_clear_stream(level) - - -class LogStream(angr.SimProcedure): - """Implements DeepState_LogStream, which converts the contents of a stream for - level `level` into a log for level `level`.""" - def run(self, level): - DeepAngr(procedure=self).api_log_stream(level) - - -class Log(angr.SimProcedure): - """Implements DeepState_Log, which lets Angr intercept and handle the - printing of log messages from the simulated tests.""" - def run(self, level, ea): - DeepAngr(procedure=self).api_log(level, ea) - - -class TakeOver(angr.SimProcedure): - def run(self): - """Do nothing, returning 1 to indicate that `DeepState_TakeOver()` has - been hooked for symbolic execution.""" - return 1 - - -def do_run_test(project, test, apis, run_state, should_call_state): - """Symbolically executes a single test function.""" - - if should_call_state: - test_state = project.factory.call_state( - test.ea, - base_state=run_state) - else: - test_state = run_state - - mc = DeepAngr(state=test_state) - - # Tell the system that we're using symbolic execution. - mc.write_uint32_t(apis["UsingSymExec"], 8589934591) - - mc.begin_test(test) - del mc - - errored = [] - test_manager = angr.SimulationManager( - project=project, - active_states=[test_state], - errored=errored) - - try: - test_manager.run() - except Exception as e: - L.error("Uncaught exception: %s\n%s", e, traceback.format_exc()) - - for state in test_manager.deadended: - DeepAngr(state=state).report() - - for error in test_manager.errored: - da = DeepAngr(state=error.state) - da.crash_test() - da.report() - -def run_test(project, test, apis, run_state, should_call_state=True): - """Symbolically executes a single test function.""" - try: - do_run_test(project, test, apis, run_state, should_call_state) - except Exception as e: - L.error("Uncaught exception: %s\n%s", e, traceback.format_exc()) - - -def find_symbol_ea(project, name): - try: - ea = project.kb.labels.lookup(name) - if ea: - return ea - except: - pass - - try: - return project.kb.labels.lookup("_{}".format(name)) - except: - pass - - return 0 - - -def hook_apis(args, project, run_state): - # Read the API table, which will tell us about the location of various - # symbols. Technically we can look these up with the `labels.lookup` API, - # but we have the API table for Manticore-compatibility, so we may as well - # use it. - ea_of_api_table = find_symbol_ea(project, 'DeepState_API') - if not ea_of_api_table: - L.critical("Could not find API table in binary %s", args.binary) - return 1 - - mc = DeepAngr(state=run_state) - apis = mc.read_api_table(ea_of_api_table) - - # Hook various functions. - hook_function(project, apis['IsSymbolicUInt'], IsSymbolicUInt) - hook_function(project, apis['ConcretizeData'], ConcretizeData) - hook_function(project, apis['ConcretizeCStr'], ConcretizeCStr) - hook_function(project, apis['MinUInt'], MinUInt) - hook_function(project, apis['MaxUInt'], MaxUInt) - hook_function(project, apis['Assume'], Assume) - hook_function(project, apis['Pass'], Pass) - hook_function(project, apis['Crash'], Crash) - hook_function(project, apis['Fail'], Fail) - hook_function(project, apis['Abandon'], Abandon) - hook_function(project, apis['SoftFail'], SoftFail) - hook_function(project, apis['Log'], Log) - hook_function(project, apis['StreamInt'], StreamInt) - hook_function(project, apis['StreamFloat'], StreamFloat) - hook_function(project, apis['StreamString'], StreamString) - hook_function(project, apis['ClearStream'], ClearStream) - hook_function(project, apis['LogStream'], LogStream) - - return mc, apis - - -def main_take_over(args, project, takeover_symbol): - takeover_ea = find_symbol_ea(project, takeover_symbol) - - if not args.klee: - hook_function(project, takeover_ea, TakeOver) - - if not takeover_ea: - L.critical("Cannot find symbol `%s` in binary `%s`", - takeover_symbol, args.binary) - return 1 - - entry_state = project.factory.entry_state( - add_options={angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY, - angr.options.STRICT_PAGE_ACCESS}) - - # addr_size_bits = entry_state.arch.bits - - # Concretely execute up until `DeepState_TakeOver`. - concrete_manager = angr.SimulationManager( - project=project, - active_states=[entry_state]) - concrete_manager.explore(find=takeover_ea) - - try: - takeover_state = concrete_manager.found[0] - except: - L.critical("Execution never hit `%s` in binary `%s`", - takeover_symbol, - args.binary) - return 1 - - try: - run_state = takeover_state.step().successors[0] - except: - L.critical("Unable to exit from `%s` in binary `%s`", - takeover_symbol, - args.binary) - return 1 - - # Read the API table, which will tell us about the location of various - # symbols. Technically we can look these up with the `labels.lookup` API, - # but we have the API table for Manticore-compatibility, so we may as well - # use it. - ea_of_api_table = find_symbol_ea(project, 'DeepState_API') - if not ea_of_api_table: - L.critical("Could not find API table in binary `%s`", args.binary) - return 1 - - _, apis = hook_apis(args, project, run_state) - fake_test = TestInfo(takeover_ea, '_takeover_test', '_takeover_file', 0) - - return run_test(project, fake_test, apis, run_state, should_call_state=False) - - -def main_unit_test(args, project): - setup_ea = find_symbol_ea(project, 'DeepState_Setup') - if not setup_ea: - L.critical("Cannot find symbol `DeepState_Setup` in binary `%s`", args.binary) - return 1 - - entry_state = project.factory.entry_state( - add_options={angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY, - angr.options.STRICT_PAGE_ACCESS}) - - # addr_size_bits = entry_state.arch.bits - - # Concretely execute up until `DeepState_Setup`. - concrete_manager = angr.SimulationManager( - project=project, - active_states=[entry_state]) - concrete_manager.explore(find=setup_ea) - - try: - run_state = concrete_manager.found[0] - except: - L.critical("Execution never hit `DeepState_Setup` in binary `%s`", args.binary) - return 1 - - # Hook the DeepState API functions. - mc, apis = hook_apis(args, project, run_state) - - # Find the test cases that we want to run. - tests = mc.find_test_cases() - del mc - - if not args.which_test: - L.info("Running %d tests across %d workers", len(tests), args.num_workers) - - pool = multiprocessing.Pool(processes=max(1, args.num_workers)) - result = [] - - # For each test, create a simulation manager whose initial state calls into - # the test case function. - for test in tests: - res = pool.apply_async(run_test, (project, test, apis, run_state)) - result.append(res) - - pool.close() - pool.join() - - else: - - test = [t for t in tests if t.name == args.which_test] - if len(test) == 0: - L.error() - exit(1) - elif len(test) > 1: - L.error() - exit(1) - - L.info("Running `%s` test across %d workers", test, args.num_workers) - run_test(project, test[0], apis, run_state) - - return 0 - - -def main(): - """Run DeepState.""" - args = DeepAngr.parse_args() - - try: - project = angr.Project( - args.binary, - use_sim_procedures=True, - translation_cache=True, - support_selfmodifying_code=False, - auto_load_libs=True, - exclude_sim_procedures_list=['printf', '__printf_chk', - 'vprintf', '__vprintf_chk', - 'fprintf', '__fprintf_chk', - 'vfprintf', '__vfprintf_chk', - 'puts', 'abort', '__assert_fail', - '__stack_chk_fail']) - except Exception as e: - L.critical("Cannot create Angr instance on binary %s: %s", args.binary, e) - return 1 - - if args.take_over: - return main_take_over(args, project, 'DeepState_TakeOver') - elif args.klee: - return main_take_over(args, project, 'main') - else: - return main_unit_test(args, project) - - -if "__main__" == __name__: - exit(main()) +#!/usr/bin/env python3.6 +# Copyright (c) 2019 Trail of Bits, Inc. +# +# 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 angr +import logging +import multiprocessing +import traceback + +from deepstate.core import SymexFrontend, TestInfo + +L = logging.getLogger(__name__) + + +class DeepAngr(SymexFrontend): + + NAME = "Angr" + + def __init__(self, state=None, procedure=None): + super(DeepAngr, self).__init__() + if procedure: + self.procedure = procedure + self.state = procedure.state + elif state: + self.procedure = None + self.state = state + + def __del__(self): + self.procedure = None + self.state = None + + def get_context(self): + return self.state.globals + + def is_symbolic(self, val): + if isinstance(val, (int, int)): + return False + return self.state.solver.symbolic(val) + + def create_symbol(self, name, size_in_bits): + return self.state.solver.Unconstrained('name', size_in_bits) + + def read_uintptr_t(self, ea, concretize=True, constrain=False): + addr_size_bytes = self.state.arch.bits // 8 + endness = self.state.arch.memory_endness + next_ea = ea + addr_size_bytes + val = self.state.memory.load(ea, size=addr_size_bytes, endness=endness) + if concretize: + val = self.concretize(val, constrain=constrain) + return val, next_ea + + def read_uint64_t(self, ea, concretize=True, constrain=False): + endness = self.state.arch.memory_endness + val = self.state.memory.load(ea, size=8, endness=endness) + if concretize: + val = self.concretize(val, constrain=constrain) + return val, ea + 8 + + def read_uint32_t(self, ea, concretize=True, constrain=False): + endness = self.state.arch.memory_endness + val = self.state.memory.load(ea, size=4, endness=endness) + if concretize: + val = self.concretize(val, constrain=constrain) + return val, ea + 4 + + def read_uint8_t(self, ea, concretize=True, constrain=False): + val = self.state.memory.load(ea, size=1) + if concretize: + val = self.concretize(val, constrain=constrain) + if isinstance(val, str): + assert len(val) == 1 + val = ord(val) + return val, ea + 1 + + def write_uint8_t(self, ea, val): + self.state.memory.store(ea, val, size=1) + return ea + 1 + + def write_uint32_t(self, ea, val): + self.state.memory.store(ea, val, size=4) + return ea + 4 + + def concretize(self, val, constrain=False): + if isinstance(val, (int, int)): + return val + elif isinstance(val, str): + assert len(val) == 1 + return ord(val[0]) + + concrete_val = self.state.solver.eval(val, cast_to=int) + if constrain: + self.add_constraint(val == concrete_val) + + return concrete_val + + def concretize_min(self, val, constrain=False): + if isinstance(val, (int, int)): + return val + concrete_val = self.state.solver.min(val) + if constrain: + self.add_constraint(val == concrete_val) + return concrete_val + + def concretize_max(self, val, constrain=False): + if isinstance(val, (int, int)): + return val + concrete_val = self.state.solver.max(val) + if constrain: + self.add_constraint(val == concrete_val) + return concrete_val + + def concretize_many(self, val, max_num): + assert 0 < max_num + if isinstance(val, (int, int)): + return [val] + return self.state.solver.eval_upto(val, max_num, cast_to=int) + + def add_constraint(self, expr): + if self.is_symbolic(expr): + self.state.solver.add(expr) + return self.state.solver.satisfiable() + else: + return True + + def pass_test(self): + super(DeepAngr, self).pass_test() + self.procedure.exit(0) + + def fail_test(self): + super(DeepAngr, self).fail_test() + self.procedure.exit(1) + + def abandon_test(self): + super(DeepAngr, self).abandon_test() + self.procedure.exit(1) + + +def hook_function(project, ea, cls): + """Hook the function `ea` with the SimProcedure `cls`.""" + project.hook(ea, cls(project=project)) + + +class IsSymbolicUInt(angr.SimProcedure): + """Implements DeepState_IsSymbolicUInt, which returns 1 if its input argument + has more then one solutions, and zero otherwise.""" + def run(self, arg): + return DeepAngr(procedure=self).api_is_symbolic_uint(arg) + + +class Assume(angr.SimProcedure): + """Implements _DeepState_Assume, which tries to inject a constraint.""" + def run(self, arg, expr_ea, file_ea, line): + DeepAngr(procedure=self).api_assume(arg, expr_ea, file_ea, line) + + +class Pass(angr.SimProcedure): + """Implements DeepState_Pass, which notifies us of a passing test.""" + def run(self): + DeepAngr(procedure=self).api_pass() + + +class Crash(angr.SimProcedure): + """Implements DeepState_Crash, which notifies us of a crashing test.""" + def run(self): + DeepAngr(procedure=self).api_crash() + + +class Fail(angr.SimProcedure): + """Implements DeepState_Fail, which notifies us of a failing test.""" + def run(self): + DeepAngr(procedure=self).api_fail() + + +class Abandon(angr.SimProcedure): + """Implements DeepState_Fail, which notifies us of a failing test.""" + def run(self, reason): + DeepAngr(procedure=self).api_abandon(reason) + + +class SoftFail(angr.SimProcedure): + """Implements DeepState_SoftFail, which notifies us of a failing test.""" + def run(self): + DeepAngr(procedure=self).api_soft_fail() + + +class ConcretizeData(angr.SimProcedure): + """Implements the `Deepstate_ConcretizeData` API function, which lets the + programmer concretize some data in the exclusive range + `[begin_ea, end_ea)`.""" + def run(self, begin_ea, end_ea): + return DeepAngr(procedure=self).api_concretize_data(begin_ea, end_ea) + + +class ConcretizeCStr(angr.SimProcedure): + """Implements the `Deepstate_ConcretizeCStr` API function, which lets the + programmer concretize a NUL-terminated string starting at `begin_ea`.""" + def run(self, begin_ea): + return DeepAngr(procedure=self).api_concretize_cstr(begin_ea) + + +class MinUInt(angr.SimProcedure): + """Implements the `Deepstate_MinUInt` API function, which lets the + programmer ask for the minimum satisfiable value of an unsigned integer.""" + def run(self, val): + return DeepAngr(procedure=self).api_min_uint(val) + + +class MaxUInt(angr.SimProcedure): + """Implements the `Deepstate_MaxUInt` API function, which lets the + programmer ask for the minimum satisfiable value of a signed integer.""" + def run(self, val): + return DeepAngr(procedure=self).api_max_uint(val) + + +class StreamInt(angr.SimProcedure): + """Implements _DeepState_StreamInt, which gives us an integer to stream, and + the format to use for streaming.""" + def run(self, level, format_ea, unpack_ea, uint64_ea): + DeepAngr(procedure=self).api_stream_int(level, format_ea, unpack_ea, + uint64_ea) + +class StreamFloat(angr.SimProcedure): + """Implements _DeepState_StreamFloat, which gives us an double to stream, and + the format to use for streaming.""" + def run(self, level, format_ea, unpack_ea, double_ea): + DeepAngr(procedure=self).api_stream_float(level, format_ea, unpack_ea, + double_ea) + + +class StreamString(angr.SimProcedure): + """Implements _DeepState_StreamString, which gives us an double to stream, and + the format to use for streaming.""" + def run(self, level, format_ea, str_ea): + DeepAngr(procedure=self).api_stream_string(level, format_ea, str_ea) + + +class ClearStream(angr.SimProcedure): + """Implements DeepState_ClearStream, which clears the contents of a stream for + level `level`.""" + def run(self, level): + DeepAngr(procedure=self).api_clear_stream(level) + + +class LogStream(angr.SimProcedure): + """Implements DeepState_LogStream, which converts the contents of a stream for + level `level` into a log for level `level`.""" + def run(self, level): + DeepAngr(procedure=self).api_log_stream(level) + + +class Log(angr.SimProcedure): + """Implements DeepState_Log, which lets Angr intercept and handle the + printing of log messages from the simulated tests.""" + def run(self, level, ea): + DeepAngr(procedure=self).api_log(level, ea) + + +class TakeOver(angr.SimProcedure): + def run(self): + """Do nothing, returning 1 to indicate that `DeepState_TakeOver()` has + been hooked for symbolic execution.""" + return 1 + + +def do_run_test(project, test, apis, run_state, should_call_state): + """Symbolically executes a single test function.""" + + if should_call_state: + test_state = project.factory.call_state( + test.ea, + base_state=run_state) + else: + test_state = run_state + + mc = DeepAngr(state=test_state) + + # Tell the system that we're using symbolic execution. + mc.write_uint32_t(apis["UsingSymExec"], 8589934591) + + mc.begin_test(test) + del mc + + errored = [] + test_manager = angr.SimulationManager( + project=project, + active_states=[test_state], + errored=errored) + + try: + test_manager.run() + except Exception as e: + L.error("Uncaught exception: %s\n%s", e, traceback.format_exc()) + + for state in test_manager.deadended: + DeepAngr(state=state).report() + + for error in test_manager.errored: + da = DeepAngr(state=error.state) + da.crash_test() + da.report() + +def run_test(project, test, apis, run_state, should_call_state=True): + """Symbolically executes a single test function.""" + try: + do_run_test(project, test, apis, run_state, should_call_state) + except Exception as e: + L.error("Uncaught exception: %s\n%s", e, traceback.format_exc()) + + +def find_symbol_ea(project, name): + try: + ea = project.kb.labels.lookup(name) + if ea: + return ea + except: + pass + + try: + return project.kb.labels.lookup("_{}".format(name)) + except: + pass + + return 0 + + +def hook_apis(args, project, run_state): + # Read the API table, which will tell us about the location of various + # symbols. Technically we can look these up with the `labels.lookup` API, + # but we have the API table for Manticore-compatibility, so we may as well + # use it. + ea_of_api_table = find_symbol_ea(project, 'DeepState_API') + if not ea_of_api_table: + L.critical("Could not find API table in binary %s", args.binary) + return 1 + + mc = DeepAngr(state=run_state) + apis = mc.read_api_table(ea_of_api_table) + + # Hook various functions. + hook_function(project, apis['IsSymbolicUInt'], IsSymbolicUInt) + hook_function(project, apis['ConcretizeData'], ConcretizeData) + hook_function(project, apis['ConcretizeCStr'], ConcretizeCStr) + hook_function(project, apis['MinUInt'], MinUInt) + hook_function(project, apis['MaxUInt'], MaxUInt) + hook_function(project, apis['Assume'], Assume) + hook_function(project, apis['Pass'], Pass) + hook_function(project, apis['Crash'], Crash) + hook_function(project, apis['Fail'], Fail) + hook_function(project, apis['Abandon'], Abandon) + hook_function(project, apis['SoftFail'], SoftFail) + hook_function(project, apis['Log'], Log) + hook_function(project, apis['StreamInt'], StreamInt) + hook_function(project, apis['StreamFloat'], StreamFloat) + hook_function(project, apis['StreamString'], StreamString) + hook_function(project, apis['ClearStream'], ClearStream) + hook_function(project, apis['LogStream'], LogStream) + + return mc, apis + + +def main_take_over(args, project, takeover_symbol): + takeover_ea = find_symbol_ea(project, takeover_symbol) + + if not args.klee: + hook_function(project, takeover_ea, TakeOver) + + if not takeover_ea: + L.critical("Cannot find symbol `%s` in binary `%s`", + takeover_symbol, args.binary) + return 1 + + entry_state = project.factory.entry_state( + add_options={angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY, + angr.options.STRICT_PAGE_ACCESS}) + + # addr_size_bits = entry_state.arch.bits + + # Concretely execute up until `DeepState_TakeOver`. + concrete_manager = angr.SimulationManager( + project=project, + active_states=[entry_state]) + concrete_manager.explore(find=takeover_ea) + + try: + takeover_state = concrete_manager.found[0] + except: + L.critical("Execution never hit `%s` in binary `%s`", + takeover_symbol, + args.binary) + return 1 + + try: + run_state = takeover_state.step().successors[0] + except: + L.critical("Unable to exit from `%s` in binary `%s`", + takeover_symbol, + args.binary) + return 1 + + # Read the API table, which will tell us about the location of various + # symbols. Technically we can look these up with the `labels.lookup` API, + # but we have the API table for Manticore-compatibility, so we may as well + # use it. + ea_of_api_table = find_symbol_ea(project, 'DeepState_API') + if not ea_of_api_table: + L.critical("Could not find API table in binary `%s`", args.binary) + return 1 + + _, apis = hook_apis(args, project, run_state) + fake_test = TestInfo(takeover_ea, '_takeover_test', '_takeover_file', 0) + + return run_test(project, fake_test, apis, run_state, should_call_state=False) + + +def main_unit_test(args, project): + setup_ea = find_symbol_ea(project, 'DeepState_Setup') + if not setup_ea: + L.critical("Cannot find symbol `DeepState_Setup` in binary `%s`", args.binary) + return 1 + + entry_state = project.factory.entry_state( + add_options={angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY, + angr.options.STRICT_PAGE_ACCESS}) + + # addr_size_bits = entry_state.arch.bits + + # Concretely execute up until `DeepState_Setup`. + concrete_manager = angr.SimulationManager( + project=project, + active_states=[entry_state]) + concrete_manager.explore(find=setup_ea) + + try: + run_state = concrete_manager.found[0] + except: + L.critical("Execution never hit `DeepState_Setup` in binary `%s`", args.binary) + return 1 + + # Hook the DeepState API functions. + mc, apis = hook_apis(args, project, run_state) + + # Find the test cases that we want to run. + tests = mc.find_test_cases() + del mc + + if not args.which_test: + L.info("Running %d tests across %d workers", len(tests), args.num_workers) + + pool = multiprocessing.Pool(processes=max(1, args.num_workers)) + result = [] + + # For each test, create a simulation manager whose initial state calls into + # the test case function. + for test in tests: + res = pool.apply_async(run_test, (project, test, apis, run_state)) + result.append(res) + + pool.close() + pool.join() + + else: + + test = [t for t in tests if t.name == args.which_test] + if len(test) == 0: + L.error() + exit(1) + elif len(test) > 1: + L.error() + exit(1) + + L.info("Running `%s` test across %d workers", test, args.num_workers) + run_test(project, test[0], apis, run_state) + + return 0 + + +def main(): + """Run DeepState.""" + args = DeepAngr.parse_args() + + try: + project = angr.Project( + args.binary, + use_sim_procedures=True, + translation_cache=True, + support_selfmodifying_code=False, + auto_load_libs=True, + exclude_sim_procedures_list=['printf', '__printf_chk', + 'vprintf', '__vprintf_chk', + 'fprintf', '__fprintf_chk', + 'vfprintf', '__vfprintf_chk', + 'puts', 'abort', '__assert_fail', + '__stack_chk_fail']) + except Exception as e: + L.critical("Cannot create Angr instance on binary %s: %s", args.binary, e) + return 1 + + if args.take_over: + return main_take_over(args, project, 'DeepState_TakeOver') + elif args.klee: + return main_take_over(args, project, 'main') + else: + return main_unit_test(args, project) + + +if "__main__" == __name__: + exit(main()) diff --git a/bin/deepstate/executors/symex/manticore.py b/bin/deepstate/executors/symex/manticore.py index 336fdd79..e53076de 100644 --- a/bin/deepstate/executors/symex/manticore.py +++ b/bin/deepstate/executors/symex/manticore.py @@ -1,518 +1,518 @@ -#!/usr/bin/env python3.6 -# Copyright (c) 2019 Trail of Bits, Inc. -# -# 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 logging - -import sys -try: - import manticore - import manticore.native -except Exception as e: - if "Z3NotFoundError" in repr(type(e)): - print("Manticore requires Z3 to be installed.") - sys.exit(255) - else: - raise -import traceback - -from manticore.utils import config -from manticore.utils import log -from manticore.core.state import TerminateState -from manticore.core.smtlib.solver import SolverType # type: ignore -from manticore.native.manticore import _make_initial_state - -from deepstate.core import SymexFrontend, TestInfo - -L = logging.getLogger(__name__) - -OUR_TERMINATION_REASON = "I DeepState'd it" - -consts = config.get_group("core") - -# make sure we use yices -consts_smt = config.get_group("smt") -consts_smt.solver=SolverType.yices - -class DeepManticore(SymexFrontend): - - NAME = "Manticore" - - def __init__(self, state): - super(DeepManticore, self).__init__() - self.state = state - - def __del__(self): - self.state = None - - def get_context(self): - return self.state.context - - def is_symbolic(self, val): - return manticore.issymbolic(val) - - def create_symbol(self, name, size_in_bits): - return self.state.new_symbolic_value(size_in_bits) - - def read_uintptr_t(self, ea, concretize=True, constrain=False): - addr_size_bits = self.state.cpu.address_bit_size - next_ea = ea + (addr_size_bits // 8) - val = self.state.cpu.read_int(ea, size=addr_size_bits) - if concretize: - val = self.concretize(val, constrain=constrain) - return val, next_ea - - def read_uint64_t(self, ea, concretize=True, constrain=False): - val = self.state.cpu.read_int(ea, size=64) - if concretize: - val = self.concretize(val, constrain=constrain) - return val, ea + 8 - - def read_uint32_t(self, ea, concretize=True, constrain=False): - val = self.state.cpu.read_int(ea, size=32) - if concretize: - val = self.concretize(val, constrain=constrain) - return val, ea + 4 - - def read_uint8_t(self, ea, concretize=True, constrain=False): - val = self.state.cpu.read_int(ea, size=8) - if concretize: - val = self.concretize(val, constrain=constrain) - if isinstance(val, str): - assert len(val) == 1 - val = ord(val) - return val, ea + 1 - - def write_uint8_t(self, ea, val): - self.state.cpu.write_int(ea, val, size=8) - return ea + 1 - - def write_uint32_t(self, ea, val): - self.state.cpu.write_int(ea, val, size=32) - return ea + 4 - - def concretize(self, val, constrain=False): - if isinstance(val, (int)): - return val - elif isinstance(val, str): - assert len(val) == 1 - return ord(val[0]) - - assert self.is_symbolic(val) - concrete_val = self.state.solve_one(val) - if isinstance(concrete_val, str): - assert len(concrete_val) == 1 - concrete_val = ord(concrete_val[0]) - if constrain: - self.add_constraint(val == concrete_val) - return concrete_val - - def concretize_min(self, val, constrain=False): - if isinstance(val, (int)): - return val - concrete_val = min(self.state.concretize(val, policy='MINMAX')) - if constrain: - self.add_constraint(val == concrete_val) - return concrete_val - - def concretize_max(self, val, constrain=False): - if isinstance(val, (int)): - return val - concrete_val = max(self.state.concretize(val, policy='MINMAX')) - if constrain: - self.add_constraint(val == concrete_val) - return concrete_val - - def concretize_many(self, val, max_num): - assert 0 < max_num - if isinstance(val, (int)): - return [val] - return self.state.solve_n(val, max_num) - - def add_constraint(self, expr): - if self.is_symbolic(expr): - self.state.constrain(expr) - return self.state.is_feasible() - return True - - def pass_test(self): - super(DeepManticore, self).pass_test() - raise TerminateState(OUR_TERMINATION_REASON, testcase=False) - - def crash_test(self): - super(DeepManticore, self).crash_test() - raise TerminateState(OUR_TERMINATION_REASON, testcase=False) - - def fail_test(self): - super(DeepManticore, self).fail_test() - raise TerminateState(OUR_TERMINATION_REASON, testcase=False) - - def abandon_test(self): - super(DeepManticore, self).abandon_test() - raise TerminateState(OUR_TERMINATION_REASON, testcase=False) - - -def hook_IsSymbolicUInt(state, arg): - """Implements DeepState_IsSymblicUInt, which returns 1 if its input argument - has more then one solutions, and zero otherwise.""" - return DeepManticore(state).api_is_symbolic_uint(arg) - - -def hook_Assume(state, arg, expr_ea, file_ea, line): - """Implements _DeepState_Assume, which tries to inject a constraint.""" - DeepManticore(state).api_assume(arg, expr_ea, file_ea, line) - - -def hook_StreamInt(state, level, format_ea, unpack_ea, uint64_ea): - """Implements _DeepState_StreamInt, which gives us an integer to stream, and - the format to use for streaming.""" - DeepManticore(state).api_stream_int(level, format_ea, unpack_ea, uint64_ea) - - -def hook_StreamFloat(state, level, format_ea, unpack_ea, double_ea): - """Implements _DeepState_StreamFloat, which gives us an double to stream, and - the format to use for streaming.""" - DeepManticore(state).api_stream_float(level, format_ea, unpack_ea, double_ea) - - -def hook_StreamString(state, level, format_ea, str_ea): - """Implements _DeepState_StreamString, which gives us an double to stream, and - the format to use for streaming.""" - DeepManticore(state).api_stream_string(level, format_ea, str_ea) - - -def hook_ClearStream(state, level): - """Implements DeepState_ClearStream, which clears the contents of a stream - for level `level`.""" - DeepManticore(state).api_clear_stream(level) - - -def hook_LogStream(state, level): - """Implements DeepState_LogStream, which converts the contents of a stream for - level `level` into a log for level `level`.""" - DeepManticore(state).api_log_stream(level) - - -def hook_Pass(state): - """Implements DeepState_Pass, which notifies us of a passing test.""" - DeepManticore(state).api_pass() - -def hook_Crash(state): - """Implements DeepState_Crash, which notifies us of a crashing test.""" - DeepManticore(state).api_crash() - -def hook_Fail(state): - """Implements DeepState_Fail, which notifies us of a failing test.""" - DeepManticore(state).api_fail() - - -def hook_Abandon(state, reason): - """Implements DeepState_Abandon, which notifies us that a problem happened - in DeepState.""" - DeepManticore(state).api_abandon(reason) - - -def hook_SoftFail(state): - """Implements DeepState_Fail, which notifies us of a passing test.""" - DeepManticore(state).api_soft_fail() - - -def hook_ConcretizeData(state, begin_ea, end_ea): - """Implements the `Deepstate_ConcretizeData` API function, which lets the - programmer concretize some data in the exclusive range - `[begin_ea, end_ea)`.""" - return DeepManticore(state).api_concretize_data(begin_ea, end_ea) - - -def hook_ConcretizeCStr(state, begin_ea): - """Implements the `Deepstate_ConcretizeCStr` API function, which lets the - programmer concretize a NUL-terminated string starting at `begin_ea`.""" - return DeepManticore(state).api_concretize_cstr(begin_ea) - - -def hook_MinUInt(state, val): - """Implements the `Deepstate_MinUInt` API function, which lets the - programmer ask for the minimum satisfiable value of an unsigned integer.""" - return DeepManticore(state).api_min_uint(val) - - -def hook_MaxUInt(state, val): - """Implements the `Deepstate_MaxUInt` API function, which lets the - programmer ask for the minimum satisfiable value of a signed integer.""" - return DeepManticore(state).api_max_uint(val) - - -def hook_Log(state, level, ea): - """Implements DeepState_Log, which lets Manticore intercept and handle the - printing of log messages from the simulated tests.""" - DeepManticore(state).api_log(level, ea) - - -def hook_TakeOver(state): - """Implements `DeepState_TakeOver`, returning 1 to indicate that it was - hooked for symbolic execution.""" - return 1 - - -def hook(func): - return lambda state: state.invoke_model(func) - - -def _is_program_crash(reason): - """Using the `reason` for the termination of a Manticore `will_terminate_state` - event, decide if we want to treat the termination as a "crash" of the program - being analyzed.""" - - if not isinstance(reason, TerminateState): - return False - - return 'Invalid memory access' in str(reason) - - -def _is_program_exit(reason): - """Using the `reason` for the termination of a Manticore `will_terminate_state` - event, decide if we want to treat the termination as a simple exit of the program - being analyzed.""" - - if not isinstance(reason, TerminateState): - return False - - return 'Program finished with exit status' in str(reason) - - -def done_test(_, state, reason): - """Called when a state is terminated.""" - mc = DeepManticore(state) - - # Note that `reason` is either an `Exception` or a `str`. If it is the special - # `OUR_TERMINATION_REASON`, then the state was terminated via a hook into the - # DeepState API, so we can just report it as is. Otherwise, we check to see if - # it was due to behavior that would typically crash the program being analyzed. - # If so, we save it as a crash. If not, we abandon it. - - if str(OUR_TERMINATION_REASON) != str(reason): - if _is_program_crash(reason): - L.info("State %s terminated due to crashing program behavior: %s", state._id, reason) - - # Don't raise new `TerminateState` exception - super(DeepManticore, mc).crash_test() - elif _is_program_exit(reason): - L.info("State %s terminated due to program exit: %s", state._id, reason) - super(DeepManticore, mc).pass_test() - #super(DeepManticore, mc).abandon_test() - else: - L.error("State %s terminated due to internal error: %s", state._id, reason) - - # Don't raise new `TerminateState` exception - super(DeepManticore, mc).abandon_test() - - mc.report() - - -def find_symbol_ea(m, name): - try: - ea = m.resolve(name) - if ea: - return ea - except: - pass - - try: - return m.resolve("_{}".format(name)) - except: - pass - - return 0 - - -def do_run_test(state, apis, test, workspace, hook_test=False): - """Run an individual test case.""" - state.cpu.PC = test.ea - - mc = DeepManticore(state) - mc.context['apis'] = apis - - # Tell the system that we're using symbolic execution. - mc.write_uint32_t(apis["UsingSymExec"], 8589934591) - - mc.begin_test(test) - - del mc - - # NOTE(alan): cannot init State with new native.Manticore in 0.3.0 as it - # will try to delete the non-existent stored state from new workspace - m = manticore.native.Manticore(state, sys.argv[1:], workspace_url=workspace) - log.set_verbosity(1) - - m.add_hook(apis['IsSymbolicUInt'], hook(hook_IsSymbolicUInt)) - m.add_hook(apis['ConcretizeData'], hook(hook_ConcretizeData)) - m.add_hook(apis['ConcretizeCStr'], hook(hook_ConcretizeCStr)) - m.add_hook(apis['MinUInt'], hook(hook_MinUInt)) - m.add_hook(apis['MaxUInt'], hook(hook_MaxUInt)) - m.add_hook(apis['Assume'], hook(hook_Assume)) - m.add_hook(apis['Pass'], hook(hook_Pass)) - m.add_hook(apis['Crash'], hook(hook_Crash)) - m.add_hook(apis['Fail'], hook(hook_Fail)) - m.add_hook(apis['SoftFail'], hook(hook_SoftFail)) - m.add_hook(apis['Abandon'], hook(hook_Abandon)) - m.add_hook(apis['Log'], hook(hook_Log)) - m.add_hook(apis['StreamInt'], hook(hook_StreamInt)) - m.add_hook(apis['StreamFloat'], hook(hook_StreamFloat)) - m.add_hook(apis['StreamString'], hook(hook_StreamString)) - m.add_hook(apis['ClearStream'], hook(hook_ClearStream)) - m.add_hook(apis['LogStream'], hook(hook_LogStream)) - - if hook_test: - m.add_hook(test.ea, hook(hook_TakeOver)) - - m.subscribe('will_terminate_state', done_test) - - # attempts to kill after consts.timeout - with m.kill_timeout(consts.timeout): - m.run() - - # if Manticore is stuck, forcefully kill all workers - m.kill() - - -def run_test(state, apis, test, workspace, hook_test=False): - try: - do_run_test(state, apis, test, workspace, hook_test) - except: - L.error("Uncaught exception: %s\n%s", sys.exc_info()[0], traceback.format_exc()) - - -def run_tests(args, state, apis, workspace): - mc = DeepManticore(state) - mc.context['apis'] = apis - tests = mc.find_test_cases() - - if not args.which_test: - L.info("Running %d tests across %d workers", len(tests), args.num_workers) - - for test in tests: - run_test(state, apis, test, workspace) - - else: - test = [t for t in tests if t.name == args.which_test] - if len(test) == 0: - L.error("No test found with specified name.") - exit(1) - elif len(test) > 1: - L.error("Multiple tests found with same name.") - exit(1) - - L.info("Running `%d` test across %d workers", args.which_test, args.num_workers) - run_test(state, apis, test[0], workspace) - - -def get_base(m): - initial_state = _make_initial_state(m.binary_path) - e_type = initial_state.platform.elf['e_type'] - if e_type == 'ET_EXEC': - return 0x0 - elif e_type == 'ET_DYN': - if initial_state.cpu.address_bit_size == 32: - return 0x56555000 - else: - return 0x555555554000 - else: - L.critical("Invalid binary type `%s`", e_type) - exit(1) - -def main_takeover(m, args, takeover_symbol): - takeover_ea = find_symbol_ea(m, takeover_symbol) - if not takeover_ea: - L.critical("Cannot find symbol `%s` in binary `%s`", - takeover_symbol, args.binary) - return 1 - - takeover_state = _make_initial_state(m.binary_path) - - mc = DeepManticore(takeover_state) - - ea_of_api_table = find_symbol_ea(m, 'DeepState_API') - if not ea_of_api_table: - L.critical("Could not find API table in binary `%s`", args.binary) - return 1 - - base = get_base(m) - apis = mc.read_api_table(ea_of_api_table, base) - - del mc - - fake_test = TestInfo(takeover_ea, '_takeover_test', '_takeover_file', 0) - - hook_test = not args.klee - takeover_hook = lambda state: run_test(state, apis, fake_test, m._workspace.uri, hook_test) - m.add_hook(takeover_ea, takeover_hook) - - with m.kill_timeout(consts.timeout): - m.run() - - m.kill() - - -def main_unit_test(m, args): - setup_ea = find_symbol_ea(m, 'DeepState_Setup') - if not setup_ea: - L.critical("Cannot find symbol `DeepState_Setup` in binary `%s`", args.binary) - return 1 - - setup_state = _make_initial_state(m.binary_path) - - mc = DeepManticore(setup_state) - - ea_of_api_table = find_symbol_ea(m, 'DeepState_API') - if not ea_of_api_table: - L.critical("Could not find API table in binary `%s`", args.binary) - return 1 - - base = get_base(m) - apis = mc.read_api_table(ea_of_api_table, base) - del mc - - m.add_hook(setup_ea, lambda state: run_tests(args, state, apis, m._workspace.uri)) - - with m.kill_timeout(consts.timeout): - m.run() - - m.kill() - - -def main(): - args = DeepManticore.parse_args() - - consts.procs = args.num_workers - consts.timeout = args.timeout - consts.mprocessing = consts.mprocessing.single - - try: - m = manticore.native.Manticore(args.binary) - except Exception as e: - L.critical("Cannot create Manticore instance on binary %s: %s", args.binary, e) - return 1 - - log.set_verbosity(args.verbosity) - - if args.take_over: - return main_takeover(m, args, 'DeepState_TakeOver') - elif args.klee: - return main_takeover(m, args, 'main') - else: - return main_unit_test(m, args) - - -if "__main__" == __name__: - exit(main()) +#!/usr/bin/env python3.6 +# Copyright (c) 2019 Trail of Bits, Inc. +# +# 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 logging + +import sys +try: + import manticore + import manticore.native +except Exception as e: + if "Z3NotFoundError" in repr(type(e)): + print("Manticore requires Z3 to be installed.") + sys.exit(255) + else: + raise +import traceback + +from manticore.utils import config +from manticore.utils import log +from manticore.core.state import TerminateState +from manticore.core.smtlib.solver import SolverType # type: ignore +from manticore.native.manticore import _make_initial_state + +from deepstate.core import SymexFrontend, TestInfo + +L = logging.getLogger(__name__) + +OUR_TERMINATION_REASON = "I DeepState'd it" + +consts = config.get_group("core") + +# make sure we use yices +consts_smt = config.get_group("smt") +consts_smt.solver=SolverType.yices + +class DeepManticore(SymexFrontend): + + NAME = "Manticore" + + def __init__(self, state): + super(DeepManticore, self).__init__() + self.state = state + + def __del__(self): + self.state = None + + def get_context(self): + return self.state.context + + def is_symbolic(self, val): + return manticore.issymbolic(val) + + def create_symbol(self, name, size_in_bits): + return self.state.new_symbolic_value(size_in_bits) + + def read_uintptr_t(self, ea, concretize=True, constrain=False): + addr_size_bits = self.state.cpu.address_bit_size + next_ea = ea + (addr_size_bits // 8) + val = self.state.cpu.read_int(ea, size=addr_size_bits) + if concretize: + val = self.concretize(val, constrain=constrain) + return val, next_ea + + def read_uint64_t(self, ea, concretize=True, constrain=False): + val = self.state.cpu.read_int(ea, size=64) + if concretize: + val = self.concretize(val, constrain=constrain) + return val, ea + 8 + + def read_uint32_t(self, ea, concretize=True, constrain=False): + val = self.state.cpu.read_int(ea, size=32) + if concretize: + val = self.concretize(val, constrain=constrain) + return val, ea + 4 + + def read_uint8_t(self, ea, concretize=True, constrain=False): + val = self.state.cpu.read_int(ea, size=8) + if concretize: + val = self.concretize(val, constrain=constrain) + if isinstance(val, str): + assert len(val) == 1 + val = ord(val) + return val, ea + 1 + + def write_uint8_t(self, ea, val): + self.state.cpu.write_int(ea, val, size=8) + return ea + 1 + + def write_uint32_t(self, ea, val): + self.state.cpu.write_int(ea, val, size=32) + return ea + 4 + + def concretize(self, val, constrain=False): + if isinstance(val, (int)): + return val + elif isinstance(val, str): + assert len(val) == 1 + return ord(val[0]) + + assert self.is_symbolic(val) + concrete_val = self.state.solve_one(val) + if isinstance(concrete_val, str): + assert len(concrete_val) == 1 + concrete_val = ord(concrete_val[0]) + if constrain: + self.add_constraint(val == concrete_val) + return concrete_val + + def concretize_min(self, val, constrain=False): + if isinstance(val, (int)): + return val + concrete_val = min(self.state.concretize(val, policy='MINMAX')) + if constrain: + self.add_constraint(val == concrete_val) + return concrete_val + + def concretize_max(self, val, constrain=False): + if isinstance(val, (int)): + return val + concrete_val = max(self.state.concretize(val, policy='MINMAX')) + if constrain: + self.add_constraint(val == concrete_val) + return concrete_val + + def concretize_many(self, val, max_num): + assert 0 < max_num + if isinstance(val, (int)): + return [val] + return self.state.solve_n(val, max_num) + + def add_constraint(self, expr): + if self.is_symbolic(expr): + self.state.constrain(expr) + return self.state.is_feasible() + return True + + def pass_test(self): + super(DeepManticore, self).pass_test() + raise TerminateState(OUR_TERMINATION_REASON, testcase=False) + + def crash_test(self): + super(DeepManticore, self).crash_test() + raise TerminateState(OUR_TERMINATION_REASON, testcase=False) + + def fail_test(self): + super(DeepManticore, self).fail_test() + raise TerminateState(OUR_TERMINATION_REASON, testcase=False) + + def abandon_test(self): + super(DeepManticore, self).abandon_test() + raise TerminateState(OUR_TERMINATION_REASON, testcase=False) + + +def hook_IsSymbolicUInt(state, arg): + """Implements DeepState_IsSymblicUInt, which returns 1 if its input argument + has more then one solutions, and zero otherwise.""" + return DeepManticore(state).api_is_symbolic_uint(arg) + + +def hook_Assume(state, arg, expr_ea, file_ea, line): + """Implements _DeepState_Assume, which tries to inject a constraint.""" + DeepManticore(state).api_assume(arg, expr_ea, file_ea, line) + + +def hook_StreamInt(state, level, format_ea, unpack_ea, uint64_ea): + """Implements _DeepState_StreamInt, which gives us an integer to stream, and + the format to use for streaming.""" + DeepManticore(state).api_stream_int(level, format_ea, unpack_ea, uint64_ea) + + +def hook_StreamFloat(state, level, format_ea, unpack_ea, double_ea): + """Implements _DeepState_StreamFloat, which gives us an double to stream, and + the format to use for streaming.""" + DeepManticore(state).api_stream_float(level, format_ea, unpack_ea, double_ea) + + +def hook_StreamString(state, level, format_ea, str_ea): + """Implements _DeepState_StreamString, which gives us an double to stream, and + the format to use for streaming.""" + DeepManticore(state).api_stream_string(level, format_ea, str_ea) + + +def hook_ClearStream(state, level): + """Implements DeepState_ClearStream, which clears the contents of a stream + for level `level`.""" + DeepManticore(state).api_clear_stream(level) + + +def hook_LogStream(state, level): + """Implements DeepState_LogStream, which converts the contents of a stream for + level `level` into a log for level `level`.""" + DeepManticore(state).api_log_stream(level) + + +def hook_Pass(state): + """Implements DeepState_Pass, which notifies us of a passing test.""" + DeepManticore(state).api_pass() + +def hook_Crash(state): + """Implements DeepState_Crash, which notifies us of a crashing test.""" + DeepManticore(state).api_crash() + +def hook_Fail(state): + """Implements DeepState_Fail, which notifies us of a failing test.""" + DeepManticore(state).api_fail() + + +def hook_Abandon(state, reason): + """Implements DeepState_Abandon, which notifies us that a problem happened + in DeepState.""" + DeepManticore(state).api_abandon(reason) + + +def hook_SoftFail(state): + """Implements DeepState_Fail, which notifies us of a passing test.""" + DeepManticore(state).api_soft_fail() + + +def hook_ConcretizeData(state, begin_ea, end_ea): + """Implements the `Deepstate_ConcretizeData` API function, which lets the + programmer concretize some data in the exclusive range + `[begin_ea, end_ea)`.""" + return DeepManticore(state).api_concretize_data(begin_ea, end_ea) + + +def hook_ConcretizeCStr(state, begin_ea): + """Implements the `Deepstate_ConcretizeCStr` API function, which lets the + programmer concretize a NUL-terminated string starting at `begin_ea`.""" + return DeepManticore(state).api_concretize_cstr(begin_ea) + + +def hook_MinUInt(state, val): + """Implements the `Deepstate_MinUInt` API function, which lets the + programmer ask for the minimum satisfiable value of an unsigned integer.""" + return DeepManticore(state).api_min_uint(val) + + +def hook_MaxUInt(state, val): + """Implements the `Deepstate_MaxUInt` API function, which lets the + programmer ask for the minimum satisfiable value of a signed integer.""" + return DeepManticore(state).api_max_uint(val) + + +def hook_Log(state, level, ea): + """Implements DeepState_Log, which lets Manticore intercept and handle the + printing of log messages from the simulated tests.""" + DeepManticore(state).api_log(level, ea) + + +def hook_TakeOver(state): + """Implements `DeepState_TakeOver`, returning 1 to indicate that it was + hooked for symbolic execution.""" + return 1 + + +def hook(func): + return lambda state: state.invoke_model(func) + + +def _is_program_crash(reason): + """Using the `reason` for the termination of a Manticore `will_terminate_state` + event, decide if we want to treat the termination as a "crash" of the program + being analyzed.""" + + if not isinstance(reason, TerminateState): + return False + + return 'Invalid memory access' in str(reason) + + +def _is_program_exit(reason): + """Using the `reason` for the termination of a Manticore `will_terminate_state` + event, decide if we want to treat the termination as a simple exit of the program + being analyzed.""" + + if not isinstance(reason, TerminateState): + return False + + return 'Program finished with exit status' in str(reason) + + +def done_test(_, state, reason): + """Called when a state is terminated.""" + mc = DeepManticore(state) + + # Note that `reason` is either an `Exception` or a `str`. If it is the special + # `OUR_TERMINATION_REASON`, then the state was terminated via a hook into the + # DeepState API, so we can just report it as is. Otherwise, we check to see if + # it was due to behavior that would typically crash the program being analyzed. + # If so, we save it as a crash. If not, we abandon it. + + if str(OUR_TERMINATION_REASON) != str(reason): + if _is_program_crash(reason): + L.info("State %s terminated due to crashing program behavior: %s", state._id, reason) + + # Don't raise new `TerminateState` exception + super(DeepManticore, mc).crash_test() + elif _is_program_exit(reason): + L.info("State %s terminated due to program exit: %s", state._id, reason) + super(DeepManticore, mc).pass_test() + #super(DeepManticore, mc).abandon_test() + else: + L.error("State %s terminated due to internal error: %s", state._id, reason) + + # Don't raise new `TerminateState` exception + super(DeepManticore, mc).abandon_test() + + mc.report() + + +def find_symbol_ea(m, name): + try: + ea = m.resolve(name) + if ea: + return ea + except: + pass + + try: + return m.resolve("_{}".format(name)) + except: + pass + + return 0 + + +def do_run_test(state, apis, test, workspace, hook_test=False): + """Run an individual test case.""" + state.cpu.PC = test.ea + + mc = DeepManticore(state) + mc.context['apis'] = apis + + # Tell the system that we're using symbolic execution. + mc.write_uint32_t(apis["UsingSymExec"], 8589934591) + + mc.begin_test(test) + + del mc + + # NOTE(alan): cannot init State with new native.Manticore in 0.3.0 as it + # will try to delete the non-existent stored state from new workspace + m = manticore.native.Manticore(state, sys.argv[1:], workspace_url=workspace) + log.set_verbosity(1) + + m.add_hook(apis['IsSymbolicUInt'], hook(hook_IsSymbolicUInt)) + m.add_hook(apis['ConcretizeData'], hook(hook_ConcretizeData)) + m.add_hook(apis['ConcretizeCStr'], hook(hook_ConcretizeCStr)) + m.add_hook(apis['MinUInt'], hook(hook_MinUInt)) + m.add_hook(apis['MaxUInt'], hook(hook_MaxUInt)) + m.add_hook(apis['Assume'], hook(hook_Assume)) + m.add_hook(apis['Pass'], hook(hook_Pass)) + m.add_hook(apis['Crash'], hook(hook_Crash)) + m.add_hook(apis['Fail'], hook(hook_Fail)) + m.add_hook(apis['SoftFail'], hook(hook_SoftFail)) + m.add_hook(apis['Abandon'], hook(hook_Abandon)) + m.add_hook(apis['Log'], hook(hook_Log)) + m.add_hook(apis['StreamInt'], hook(hook_StreamInt)) + m.add_hook(apis['StreamFloat'], hook(hook_StreamFloat)) + m.add_hook(apis['StreamString'], hook(hook_StreamString)) + m.add_hook(apis['ClearStream'], hook(hook_ClearStream)) + m.add_hook(apis['LogStream'], hook(hook_LogStream)) + + if hook_test: + m.add_hook(test.ea, hook(hook_TakeOver)) + + m.subscribe('will_terminate_state', done_test) + + # attempts to kill after consts.timeout + with m.kill_timeout(consts.timeout): + m.run() + + # if Manticore is stuck, forcefully kill all workers + m.kill() + + +def run_test(state, apis, test, workspace, hook_test=False): + try: + do_run_test(state, apis, test, workspace, hook_test) + except: + L.error("Uncaught exception: %s\n%s", sys.exc_info()[0], traceback.format_exc()) + + +def run_tests(args, state, apis, workspace): + mc = DeepManticore(state) + mc.context['apis'] = apis + tests = mc.find_test_cases() + + if not args.which_test: + L.info("Running %d tests across %d workers", len(tests), args.num_workers) + + for test in tests: + run_test(state, apis, test, workspace) + + else: + test = [t for t in tests if t.name == args.which_test] + if len(test) == 0: + L.error("No test found with specified name.") + exit(1) + elif len(test) > 1: + L.error("Multiple tests found with same name.") + exit(1) + + L.info("Running `%d` test across %d workers", args.which_test, args.num_workers) + run_test(state, apis, test[0], workspace) + + +def get_base(m): + initial_state = _make_initial_state(m.binary_path) + e_type = initial_state.platform.elf['e_type'] + if e_type == 'ET_EXEC': + return 0x0 + elif e_type == 'ET_DYN': + if initial_state.cpu.address_bit_size == 32: + return 0x56555000 + else: + return 0x555555554000 + else: + L.critical("Invalid binary type `%s`", e_type) + exit(1) + +def main_takeover(m, args, takeover_symbol): + takeover_ea = find_symbol_ea(m, takeover_symbol) + if not takeover_ea: + L.critical("Cannot find symbol `%s` in binary `%s`", + takeover_symbol, args.binary) + return 1 + + takeover_state = _make_initial_state(m.binary_path) + + mc = DeepManticore(takeover_state) + + ea_of_api_table = find_symbol_ea(m, 'DeepState_API') + if not ea_of_api_table: + L.critical("Could not find API table in binary `%s`", args.binary) + return 1 + + base = get_base(m) + apis = mc.read_api_table(ea_of_api_table, base) + + del mc + + fake_test = TestInfo(takeover_ea, '_takeover_test', '_takeover_file', 0) + + hook_test = not args.klee + takeover_hook = lambda state: run_test(state, apis, fake_test, m._workspace.uri, hook_test) + m.add_hook(takeover_ea, takeover_hook) + + with m.kill_timeout(consts.timeout): + m.run() + + m.kill() + + +def main_unit_test(m, args): + setup_ea = find_symbol_ea(m, 'DeepState_Setup') + if not setup_ea: + L.critical("Cannot find symbol `DeepState_Setup` in binary `%s`", args.binary) + return 1 + + setup_state = _make_initial_state(m.binary_path) + + mc = DeepManticore(setup_state) + + ea_of_api_table = find_symbol_ea(m, 'DeepState_API') + if not ea_of_api_table: + L.critical("Could not find API table in binary `%s`", args.binary) + return 1 + + base = get_base(m) + apis = mc.read_api_table(ea_of_api_table, base) + del mc + + m.add_hook(setup_ea, lambda state: run_tests(args, state, apis, m._workspace.uri)) + + with m.kill_timeout(consts.timeout): + m.run() + + m.kill() + + +def main(): + args = DeepManticore.parse_args() + + consts.procs = args.num_workers + consts.timeout = args.timeout + consts.mprocessing = consts.mprocessing.single + + try: + m = manticore.native.Manticore(args.binary) + except Exception as e: + L.critical("Cannot create Manticore instance on binary %s: %s", args.binary, e) + return 1 + + log.set_verbosity(args.verbosity) + + if args.take_over: + return main_takeover(m, args, 'DeepState_TakeOver') + elif args.klee: + return main_takeover(m, args, 'main') + else: + return main_unit_test(m, args) + + +if "__main__" == __name__: + exit(main()) diff --git a/bin/setup.py.in b/bin/setup.py.in index 6840e140..774bac3f 100644 --- a/bin/setup.py.in +++ b/bin/setup.py.in @@ -1,53 +1,53 @@ -#!/usr/bin/env python -# Copyright (c) 2019 Trail of Bits, Inc. -# -# 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 distutils.core -import os -import setuptools - -DEEPSTATE_DIR = os.path.dirname(os.path.realpath(__file__)) - -setuptools.setup( - name="deepstate", - version="0.1", - package_dir={"": "${CMAKE_SOURCE_DIR}/bin"}, - packages=['deepstate', 'deepstate.core', 'deepstate.executors', - 'deepstate.executors.fuzz', 'deepstate.executors.symex', 'deepstate.executors.auxiliary'], - description="DeepState augments C/C++ Test-Driven Development with Fuzzing and Symbolic Execution", - url="https://github.com/trailofbits/deepstate", - author="Peter Goodman", - author_email="peter@trailofbits.com", - license="Apache-2.0", - keywords="tdd testing symbolic execution", - install_requires=["psutil"], - extras_require={ - 'dev': ['mypy'] - }, - entry_points={ - 'console_scripts': [ - 'deepstate = deepstate.executors.symex.manticore:main', - 'deepstate-angr = deepstate.executors.symex.angr:main', - 'deepstate-manticore = deepstate.executors.symex.manticore:main', - - 'deepstate-afl = deepstate.executors.fuzz.afl:main', - 'deepstate-libfuzzer = deepstate.executors.fuzz.libfuzzer:main', - 'deepstate-eclipser = deepstate.executors.fuzz.eclipser:main', - 'deepstate-angora = deepstate.executors.fuzz.angora:main', - 'deepstate-honggfuzz = deepstate.executors.fuzz.honggfuzz:main', - - 'deepstate-reduce = deepstate.executors.auxiliary.reducer:main', - 'deepstate-ensembler = deepstate.executors.auxiliary.ensembler:main' - ] - }) +#!/usr/bin/env python +# Copyright (c) 2019 Trail of Bits, Inc. +# +# 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 distutils.core +import os +import setuptools + +DEEPSTATE_DIR = os.path.dirname(os.path.realpath(__file__)) + +setuptools.setup( + name="deepstate", + version="0.1", + package_dir={"": "${CMAKE_SOURCE_DIR}/bin"}, + packages=['deepstate', 'deepstate.core', 'deepstate.executors', + 'deepstate.executors.fuzz', 'deepstate.executors.symex', 'deepstate.executors.auxiliary'], + description="DeepState augments C/C++ Test-Driven Development with Fuzzing and Symbolic Execution", + url="https://github.com/trailofbits/deepstate", + author="Peter Goodman", + author_email="peter@trailofbits.com", + license="Apache-2.0", + keywords="tdd testing symbolic execution", + install_requires=["psutil"], + extras_require={ + 'dev': ['mypy'] + }, + entry_points={ + 'console_scripts': [ + 'deepstate = deepstate.executors.symex.manticore:main', + 'deepstate-angr = deepstate.executors.symex.angr:main', + 'deepstate-manticore = deepstate.executors.symex.manticore:main', + + 'deepstate-afl = deepstate.executors.fuzz.afl:main', + 'deepstate-libfuzzer = deepstate.executors.fuzz.libfuzzer:main', + 'deepstate-eclipser = deepstate.executors.fuzz.eclipser:main', + 'deepstate-angora = deepstate.executors.fuzz.angora:main', + 'deepstate-honggfuzz = deepstate.executors.fuzz.honggfuzz:main', + + 'deepstate-reduce = deepstate.executors.auxiliary.reducer:main', + 'deepstate-ensembler = deepstate.executors.auxiliary.ensembler:main' + ] + }) diff --git a/docker/Dockerfile b/docker/Dockerfile index e63d23ec..ff00f4a6 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,115 +1,115 @@ -# -- AFL -FROM deepstate-base AS AFL -COPY ./docker/install_afl.sh ./ -ARG make_j -RUN bash install_afl.sh $make_j - -# -- Honggfuzz -# FROM deepstate-base AS Honggfuzz -# COPY ./docker/install_honggfuzz.sh ./ -# ARG make_j -# RUN bash install_honggfuzz.sh $make_j - -# -- Eclipser -FROM deepstate-base AS Eclipser -COPY ./docker/install_eclipser.sh ./ -ARG make_j -RUN bash install_eclipser.sh $make_j - -# -- Angora -# FROM deepstate-base AS Angora -# COPY ./docker/install_angora.sh ./ -# ARG make_j -# RUN bash install_angora.sh $make_j - -# -- DeepState -FROM deepstate-base -ENV DEPS_DIR /home/user/deps -ARG make_j - -# Angr, Manticore -# RUN echo 'Installing angr and manticore' \ -# && sudo add-apt-repository -y ppa:sri-csl/formal-methods \ -# && sudo apt-get -y update \ -# && sudo apt-get -y install yices2 \ -# && pip3 install z3-solver angr git+git://github.com/trailofbits/manticore.git --user - -# Eclipser - not deepstate dependent -COPY --from=Eclipser /home/user/Eclipser/build $DEPS_DIR/eclipser -RUN echo 'Eclipser - installing dotnet' \ - && wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb \ - && sudo dpkg -i packages-microsoft-prod.deb \ - && sudo apt-get update \ - && sudo apt-get install -y dotnet-sdk-2.2 -COPY --from=Eclipser /home/user/.nuget /home/user/.nuget - -# Angora part 1 - not deepstate dependent -# COPY --from=Angora /home/user/Angora $DEPS_DIR/angora -# COPY --from=Angora /home/user/clang+llvm $DEPS_DIR/angora/clang+llvm - -# copy deepstate code here to use cache as much as possible -COPY . ./deepstate -RUN sudo chown user:user -R ./deepstate -WORKDIR ./deepstate - -# Angora part 2 -# ignore errors in `make`, because Angora doesn't support 32bit builds -# RUN echo 'Building deepstate with Angora - taint' \ -# && mkdir -p build_angora_taint && cd build_angora_taint \ -# && export PATH="$DEPS_DIR/angora/clang+llvm/bin:$PATH" \ -# && export LD_LIBRARY_PATH="$DEPS_DIR/angora/clang+llvm/lib:$LD_LIBRARY_PATH" \ -# && export USE_TRACK=1 \ -# && export ANGORA_HOME="$DEPS_DIR/angora" \ -# && CXX="$ANGORA_HOME/bin/angora-clang++" CC="$ANGORA_HOME/bin/angora-clang" cmake -DDEEPSTATE_ANGORA=ON ../ \ -# && make -j $make_j -i \ -# && sudo cp ./libdeepstate_taint.a /usr/local/lib/ - -# RUN echo 'Building deepstate with Angora - fast' \ -# && mkdir -p build_angora_fast && cd build_angora_fast \ -# && export PATH="$DEPS_DIR/angora/clang+llvm/bin:$PATH" \ -# && export LD_LIBRARY_PATH="$DEPS_DIR/angora/clang+llvm/lib:$LD_LIBRARY_PATH" \ -# && export USE_FAST=1 \ -# && export ANGORA_HOME="$DEPS_DIR/angora" \ -# && CXX="$ANGORA_HOME/bin/angora-clang++" CC="$ANGORA_HOME/bin/angora-clang" cmake -DDEEPSTATE_ANGORA=ON ../ \ -# && make -j $make_j -i \ -# && sudo cp ./libdeepstate_fast.a /usr/local/lib/ - -# general -RUN echo 'Building deepstate' \ - && mkdir -p ./build_deepstate && cd ./build_deepstate \ - && CXX=clang++ CC=clang cmake ../ \ - && make -j $make_j \ - && sudo make install - -# libFuzzer -RUN echo 'Building deepstate with libFuzzer' \ - && mkdir -p build_libfuzzer && cd build_libfuzzer \ - && CXX=clang++ CC=clang cmake -DDEEPSTATE_LIBFUZZER=ON ../ \ - && make -j $make_j \ - && sudo cp ./libdeepstate_LF.a /usr/local/lib/ - -# AFL -COPY --from=AFL /home/user/afl-2.52b $DEPS_DIR/afl -RUN echo 'Building deepstate with AFL' \ - && mkdir -p build_afl && cd build_afl \ - && export AFL_HOME="$DEPS_DIR/afl" \ - && CXX="$AFL_HOME/afl-clang++" CC="$AFL_HOME/afl-clang" cmake -DDEEPSTATE_AFL=ON ../ \ - && make -j $make_j \ - && sudo cp ./libdeepstate_AFL.a /usr/local/lib/ - -# Honggfuzz -# COPY --from=Honggfuzz /home/user/honggfuzz $DEPS_DIR/honggfuzz -# RUN sudo apt-get update && sudo apt-get -y install libunwind-dev -# RUN echo "HELLO SAILOR" \ -# RUN echo 'Building deepstate with Honggfuzz' \ -# && mkdir -p build_honggfuzz && cd build_honggfuzz \ -# && export HONGGFUZZ_HOME="$DEPS_DIR/honggfuzz" \ -# && CXX="$HONGGFUZZ_HOME/hfuzz_cc/hfuzz-clang++" CC="$HONGGFUZZ_HOME/hfuzz_cc/hfuzz-clang" cmake -DDEEPSTATE_HONGGFUZZ=ON ../ \ -# && make -j $make_j \ -# && sudo cp ./libdeepstate_HFUZZ.a /usr/local/lib/ - -ENV CXX=clang++ CC=clang -ENV AFL_HOME="$DEPS_DIR/afl" HONGGFUZZ_HOME="$DEPS_DIR/honggfuzz" \ - ANGORA_HOME="$DEPS_DIR/angora" ECLIPSER_HOME="$DEPS_DIR/eclipser" - +# -- AFL +FROM deepstate-base AS AFL +COPY ./docker/install_afl.sh ./ +ARG make_j +RUN bash install_afl.sh $make_j + +# -- Honggfuzz +# FROM deepstate-base AS Honggfuzz +# COPY ./docker/install_honggfuzz.sh ./ +# ARG make_j +# RUN bash install_honggfuzz.sh $make_j + +# -- Eclipser +FROM deepstate-base AS Eclipser +COPY ./docker/install_eclipser.sh ./ +ARG make_j +RUN bash install_eclipser.sh $make_j + +# -- Angora +# FROM deepstate-base AS Angora +# COPY ./docker/install_angora.sh ./ +# ARG make_j +# RUN bash install_angora.sh $make_j + +# -- DeepState +FROM deepstate-base +ENV DEPS_DIR /home/user/deps +ARG make_j + +# Angr, Manticore +# RUN echo 'Installing angr and manticore' \ +# && sudo add-apt-repository -y ppa:sri-csl/formal-methods \ +# && sudo apt-get -y update \ +# && sudo apt-get -y install yices2 \ +# && pip3 install z3-solver angr git+git://github.com/trailofbits/manticore.git --user + +# Eclipser - not deepstate dependent +COPY --from=Eclipser /home/user/Eclipser/build $DEPS_DIR/eclipser +RUN echo 'Eclipser - installing dotnet' \ + && wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb \ + && sudo dpkg -i packages-microsoft-prod.deb \ + && sudo apt-get update \ + && sudo apt-get install -y dotnet-sdk-2.2 +COPY --from=Eclipser /home/user/.nuget /home/user/.nuget + +# Angora part 1 - not deepstate dependent +# COPY --from=Angora /home/user/Angora $DEPS_DIR/angora +# COPY --from=Angora /home/user/clang+llvm $DEPS_DIR/angora/clang+llvm + +# copy deepstate code here to use cache as much as possible +COPY . ./deepstate +RUN sudo chown user:user -R ./deepstate +WORKDIR ./deepstate + +# Angora part 2 +# ignore errors in `make`, because Angora doesn't support 32bit builds +# RUN echo 'Building deepstate with Angora - taint' \ +# && mkdir -p build_angora_taint && cd build_angora_taint \ +# && export PATH="$DEPS_DIR/angora/clang+llvm/bin:$PATH" \ +# && export LD_LIBRARY_PATH="$DEPS_DIR/angora/clang+llvm/lib:$LD_LIBRARY_PATH" \ +# && export USE_TRACK=1 \ +# && export ANGORA_HOME="$DEPS_DIR/angora" \ +# && CXX="$ANGORA_HOME/bin/angora-clang++" CC="$ANGORA_HOME/bin/angora-clang" cmake -DDEEPSTATE_ANGORA=ON ../ \ +# && make -j $make_j -i \ +# && sudo cp ./libdeepstate_taint.a /usr/local/lib/ + +# RUN echo 'Building deepstate with Angora - fast' \ +# && mkdir -p build_angora_fast && cd build_angora_fast \ +# && export PATH="$DEPS_DIR/angora/clang+llvm/bin:$PATH" \ +# && export LD_LIBRARY_PATH="$DEPS_DIR/angora/clang+llvm/lib:$LD_LIBRARY_PATH" \ +# && export USE_FAST=1 \ +# && export ANGORA_HOME="$DEPS_DIR/angora" \ +# && CXX="$ANGORA_HOME/bin/angora-clang++" CC="$ANGORA_HOME/bin/angora-clang" cmake -DDEEPSTATE_ANGORA=ON ../ \ +# && make -j $make_j -i \ +# && sudo cp ./libdeepstate_fast.a /usr/local/lib/ + +# general +RUN echo 'Building deepstate' \ + && mkdir -p ./build_deepstate && cd ./build_deepstate \ + && CXX=clang++ CC=clang cmake ../ \ + && make -j $make_j \ + && sudo make install + +# libFuzzer +RUN echo 'Building deepstate with libFuzzer' \ + && mkdir -p build_libfuzzer && cd build_libfuzzer \ + && CXX=clang++ CC=clang cmake -DDEEPSTATE_LIBFUZZER=ON ../ \ + && make -j $make_j \ + && sudo cp ./libdeepstate_LF.a /usr/local/lib/ + +# AFL +COPY --from=AFL /home/user/afl-2.52b $DEPS_DIR/afl +RUN echo 'Building deepstate with AFL' \ + && mkdir -p build_afl && cd build_afl \ + && export AFL_HOME="$DEPS_DIR/afl" \ + && CXX="$AFL_HOME/afl-clang++" CC="$AFL_HOME/afl-clang" cmake -DDEEPSTATE_AFL=ON ../ \ + && make -j $make_j \ + && sudo cp ./libdeepstate_AFL.a /usr/local/lib/ + +# Honggfuzz +# COPY --from=Honggfuzz /home/user/honggfuzz $DEPS_DIR/honggfuzz +# RUN sudo apt-get update && sudo apt-get -y install libunwind-dev +# RUN echo "HELLO SAILOR" \ +# RUN echo 'Building deepstate with Honggfuzz' \ +# && mkdir -p build_honggfuzz && cd build_honggfuzz \ +# && export HONGGFUZZ_HOME="$DEPS_DIR/honggfuzz" \ +# && CXX="$HONGGFUZZ_HOME/hfuzz_cc/hfuzz-clang++" CC="$HONGGFUZZ_HOME/hfuzz_cc/hfuzz-clang" cmake -DDEEPSTATE_HONGGFUZZ=ON ../ \ +# && make -j $make_j \ +# && sudo cp ./libdeepstate_HFUZZ.a /usr/local/lib/ + +ENV CXX=clang++ CC=clang +ENV AFL_HOME="$DEPS_DIR/afl" HONGGFUZZ_HOME="$DEPS_DIR/honggfuzz" \ + ANGORA_HOME="$DEPS_DIR/angora" ECLIPSER_HOME="$DEPS_DIR/eclipser" + CMD ["/bin/bash"] \ No newline at end of file diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 54596c46..2db554ed 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -1,38 +1,38 @@ -FROM ubuntu:18.04 - -# Set up the non-root user -RUN apt-get update \ - && apt-get -y install sudo \ - && useradd -ms /bin/bash user && echo "user:user" | chpasswd && adduser user sudo -COPY /sudoers.txt /etc/sudoers - -# Switch to permissioned user -WORKDIR /home/user -RUN chown -R user:user /home/user -USER user - -# Install general dependencies -RUN sudo apt update && sudo apt-get install -y build-essential \ - gcc-multilib g++-multilib cmake \ - python3-setuptools libffi-dev z3 python3-pip \ - git wget lsb-release software-properties-common \ - && sudo rm -rf /var/lib/apt/lists/* - -ENV LLVM_VER=9 - -# Install LLVM -RUN wget https://apt.llvm.org/llvm.sh \ - && chmod +x llvm.sh \ - && sudo ./llvm.sh $LLVM_VER - -RUN sudo apt-get update && sudo apt-get -y install libllvm-$LLVM_VER-ocaml-dev \ - libllvm$LLVM_VER llvm-$LLVM_VER llvm-$LLVM_VER-dev \ - llvm-$LLVM_VER-doc llvm-$LLVM_VER-examples llvm-$LLVM_VER-runtime \ - clang-$LLVM_VER clang-tools-$LLVM_VER clang-$LLVM_VER-doc \ - libclang-common-$LLVM_VER-dev libclang-$LLVM_VER-dev libclang1-$LLVM_VER \ - clang-format-$LLVM_VER python-clang-$LLVM_VER clangd-$LLVM_VER \ - libfuzzer-$LLVM_VER-dev libc++-$LLVM_VER-dev libc++abi-$LLVM_VER-dev \ - lld-$LLVM_VER lldb-$LLVM_VER - -RUN sudo ln -s $(which clang-$LLVM_VER) /usr/bin/clang -RUN sudo ln -s $(which clang++-$LLVM_VER) /usr/bin/clang++ +FROM ubuntu:18.04 + +# Set up the non-root user +RUN apt-get update \ + && apt-get -y install sudo \ + && useradd -ms /bin/bash user && echo "user:user" | chpasswd && adduser user sudo +COPY /sudoers.txt /etc/sudoers + +# Switch to permissioned user +WORKDIR /home/user +RUN chown -R user:user /home/user +USER user + +# Install general dependencies +RUN sudo apt update && sudo apt-get install -y build-essential \ + gcc-multilib g++-multilib cmake \ + python3-setuptools libffi-dev z3 python3-pip \ + git wget lsb-release software-properties-common \ + && sudo rm -rf /var/lib/apt/lists/* + +ENV LLVM_VER=9 + +# Install LLVM +RUN wget https://apt.llvm.org/llvm.sh \ + && chmod +x llvm.sh \ + && sudo ./llvm.sh $LLVM_VER + +RUN sudo apt-get update && sudo apt-get -y install libllvm-$LLVM_VER-ocaml-dev \ + libllvm$LLVM_VER llvm-$LLVM_VER llvm-$LLVM_VER-dev \ + llvm-$LLVM_VER-doc llvm-$LLVM_VER-examples llvm-$LLVM_VER-runtime \ + clang-$LLVM_VER clang-tools-$LLVM_VER clang-$LLVM_VER-doc \ + libclang-common-$LLVM_VER-dev libclang-$LLVM_VER-dev libclang1-$LLVM_VER \ + clang-format-$LLVM_VER python-clang-$LLVM_VER clangd-$LLVM_VER \ + libfuzzer-$LLVM_VER-dev libc++-$LLVM_VER-dev libc++abi-$LLVM_VER-dev \ + lld-$LLVM_VER lldb-$LLVM_VER + +RUN sudo ln -s $(which clang-$LLVM_VER) /usr/bin/clang +RUN sudo ln -s $(which clang++-$LLVM_VER) /usr/bin/clang++ diff --git a/docker/base/sudoers.txt b/docker/base/sudoers.txt index c4787109..7c0ede9d 100644 --- a/docker/base/sudoers.txt +++ b/docker/base/sudoers.txt @@ -1,4 +1,4 @@ -root ALL=(ALL) ALL -user ALL=(ALL) NOPASSWD: ALL -Defaults env_reset -Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" +root ALL=(ALL) ALL +user ALL=(ALL) NOPASSWD: ALL +Defaults env_reset +Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" diff --git a/docker/install_afl.sh b/docker/install_afl.sh index ffc1dc6d..50e7e72a 100644 --- a/docker/install_afl.sh +++ b/docker/install_afl.sh @@ -1,29 +1,29 @@ -#!/bin/sh -set -e - -# Install dependencies -sudo apt-get install -y \ - --no-install-suggests --no-install-recommends \ - automake \ - bison \ - build-essential \ - flex \ - git \ - python3.7 \ - python3.7-dev \ - libtool \ - libtool-bin \ - libglib2.0-dev \ - python-setuptools \ - python2.7-dev \ - wget \ - ca-certificates \ - libpixman-1-dev \ - && sudo rm -rf /var/lib/apt/lists/* - -# Install AFL -wget http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz \ - && tar -xzvf afl-latest.tgz \ - && rm -rf afl-latest.tgz \ - && cd afl-2.52b \ - && make -j $1 +#!/bin/sh +set -e + +# Install dependencies +sudo apt-get install -y \ + --no-install-suggests --no-install-recommends \ + automake \ + bison \ + build-essential \ + flex \ + git \ + python3.7 \ + python3.7-dev \ + libtool \ + libtool-bin \ + libglib2.0-dev \ + python-setuptools \ + python2.7-dev \ + wget \ + ca-certificates \ + libpixman-1-dev \ + && sudo rm -rf /var/lib/apt/lists/* + +# Install AFL +wget http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz \ + && tar -xzvf afl-latest.tgz \ + && rm -rf afl-latest.tgz \ + && cd afl-2.52b \ + && make -j $1 diff --git a/docker/install_angora.sh b/docker/install_angora.sh index 74ceb50f..b6494493 100644 --- a/docker/install_angora.sh +++ b/docker/install_angora.sh @@ -1,22 +1,22 @@ -#!/bin/sh -set -e - -# Enable deb-sec -sudo sed -i -- 's/#deb-src/deb-src/g' /etc/apt/sources.list -sudo sed -i -- 's/# deb-src/deb-src/g' /etc/apt/sources.list - -# Install dependencies -sudo apt-get update -sudo apt-get install -y rustc \ - cargo libstdc++-7-dev zlib1g-dev \ - && sudo rm -rf /var/lib/apt/lists/* - -# set proper LLVM version -export LLVM_VER=7.0.0 -export PATH="$(pwd)/clang+llvm/bin:$PATH" -export LD_LIBRARY_PATH="$(pwd)/clang+llvm/lib:$LD_LIBRARY_PATH" - -# Install Angora -git clone https://github.com/AngoraFuzzer/Angora \ - && cd Angora \ - && ./build/build.sh +#!/bin/sh +set -e + +# Enable deb-sec +sudo sed -i -- 's/#deb-src/deb-src/g' /etc/apt/sources.list +sudo sed -i -- 's/# deb-src/deb-src/g' /etc/apt/sources.list + +# Install dependencies +sudo apt-get update +sudo apt-get install -y rustc \ + cargo libstdc++-7-dev zlib1g-dev \ + && sudo rm -rf /var/lib/apt/lists/* + +# set proper LLVM version +export LLVM_VER=7.0.0 +export PATH="$(pwd)/clang+llvm/bin:$PATH" +export LD_LIBRARY_PATH="$(pwd)/clang+llvm/lib:$LD_LIBRARY_PATH" + +# Install Angora +git clone https://github.com/AngoraFuzzer/Angora \ + && cd Angora \ + && ./build/build.sh diff --git a/docker/install_eclipser.sh b/docker/install_eclipser.sh index 49abde08..69facb8e 100644 --- a/docker/install_eclipser.sh +++ b/docker/install_eclipser.sh @@ -1,25 +1,25 @@ -#!/bin/sh -set -e - -# Enable deb-sec -sudo sed -i -- 's/#deb-src/deb-src/g' /etc/apt/sources.list -sudo sed -i -- 's/# deb-src/deb-src/g' /etc/apt/sources.list - -# Install dependencies -sudo apt-get update -sudo apt-get -y build-dep qemu -sudo apt-get install -y libtool \ - libtool-bin wget automake autoconf \ - bison gdb git apt-transport-https - -wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -sudo dpkg -i packages-microsoft-prod.deb -sudo apt-get update -sudo apt-get install -y dotnet-sdk-2.2 -sudo rm -rf /var/lib/apt/lists/* - -# Install Eclipser -git clone https://github.com/SoftSec-KAIST/Eclipser \ - && cd Eclipser \ - && git checkout tags/v1.1 \ - && make -j $1 +#!/bin/sh +set -e + +# Enable deb-sec +sudo sed -i -- 's/#deb-src/deb-src/g' /etc/apt/sources.list +sudo sed -i -- 's/# deb-src/deb-src/g' /etc/apt/sources.list + +# Install dependencies +sudo apt-get update +sudo apt-get -y build-dep qemu +sudo apt-get install -y libtool \ + libtool-bin wget automake autoconf \ + bison gdb git apt-transport-https + +wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb +sudo dpkg -i packages-microsoft-prod.deb +sudo apt-get update +sudo apt-get install -y dotnet-sdk-2.2 +sudo rm -rf /var/lib/apt/lists/* + +# Install Eclipser +git clone https://github.com/SoftSec-KAIST/Eclipser \ + && cd Eclipser \ + && git checkout tags/v1.1 \ + && make -j $1 diff --git a/docker/install_honggfuzz.sh b/docker/install_honggfuzz.sh index d3e6f025..d2518439 100644 --- a/docker/install_honggfuzz.sh +++ b/docker/install_honggfuzz.sh @@ -1,12 +1,12 @@ -#!/bin/sh -set -e - -# Install dependencies -sudo apt-get update && sudo apt-get install -y binutils-dev \ - libunwind-dev \ - && sudo rm -rf /var/lib/apt/lists/* - -# Install Honggfuzz -git clone https://github.com/google/honggfuzz \ - && cd honggfuzz \ - && make -j $1 +#!/bin/sh +set -e + +# Install dependencies +sudo apt-get update && sudo apt-get install -y binutils-dev \ + libunwind-dev \ + && sudo rm -rf /var/lib/apt/lists/* + +# Install Honggfuzz +git clone https://github.com/google/honggfuzz \ + && cd honggfuzz \ + && make -j $1 diff --git a/docs/README.md b/docs/README.md index 85cbebaa..9df0ebdb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ -# Documentation - -* [Basic usage](/docs/basic_usage.md) -* [Writing test harness](/docs/test_harness.md) -* [Fuzzing](/docs/fuzzing.md) +# Documentation + +* [Basic usage](/docs/basic_usage.md) +* [Writing test harness](/docs/test_harness.md) +* [Fuzzing](/docs/fuzzing.md) * [Swarm testing](/docs/swarm_testing.md) \ No newline at end of file diff --git a/docs/basic_usage.md b/docs/basic_usage.md index d74b625d..e324ea4f 100644 --- a/docs/basic_usage.md +++ b/docs/basic_usage.md @@ -1,266 +1,266 @@ -# Basic usage - -DeepState consists of a static library, used to write test harnesses, -and command-line _executors_ written in Python. At this time, the best -documentation is in the [examples](/examples) and in our -[paper](https://agroce.github.io/bar18.pdf). A more extensive -example, using DeepState and libFuzzer to test a user-mode file -system, is available [here](https://github.com/agroce/testfs); in -particular the -[Tests.cpp](https://github.com/agroce/testfs/blob/master/Tests.cpp) -file and CMakeLists.txt show DeepState usage. Another extensive -example is a [differential tester that compares Google's leveldb and -Facebook's rocksdb](https://github.com/agroce/testleveldb). - - -Table of Contents -================= - - * [Writing test harness](#writing-test-harness) - * [Running the test](#running-the-test) - * [Tests replay](#tests-replay) - * [Test case reduction](#test-case-reduction) - * [Log Levels](#log-levels) - - -## Writing a test harness - -A simple example test harness is included in the `examples` directory, -to test a (rather silly) run length encoding implementation: - -```cpp -#include - -using namespace deepstate; - -/* Simple, buggy, run-length encoding that creates "human readable" - * encodings by adding 'A'-1 to the count, and splitting at 26. - * e.g., encode("aaabbbbbc") = "aCbEcA" since C=3 and E=5 */ - -char* encode(const char* input) { - unsigned int len = strlen(input); - char* encoded = (char*)malloc((len*2)+1); - int pos = 0; - if (len > 0) { - unsigned char last = input[0]; - int count = 1; - for (int i = 1; i < len; i++) { - if (((unsigned char)input[i] == last) && (count < 26)) - count++; - else { - encoded[pos++] = last; - encoded[pos++] = 64 + count; - last = (unsigned char)input[i]; - count = 1; - } - } - encoded[pos++] = last; - encoded[pos++] = 65; // Should be 64 + count - } - encoded[pos] = '\0'; - return encoded; -} - -char* decode(const char* output) { - unsigned int len = strlen(output); - char* decoded = (char*)malloc((len/2)*26); - int pos = 0; - for (int i = 0; i < len; i += 2) { - for (int j = 0; j < (output[i+1] - 64); j++) { - decoded[pos++] = output[i]; - } - } - decoded[pos] = '\0'; - return decoded; -} - -// Can be (much) higher (e.g., > 1024) if we're using fuzzing, not symbolic execution -#define MAX_STR_LEN 6 - -TEST(Runlength, BoringUnitTest) { - ASSERT_EQ(strcmp(encode(""), ""), 0); - ASSERT_EQ(strcmp(encode("a"), "aA"), 0); - ASSERT_EQ(strcmp(encode("aaabbbbbc"), "aCbEcA"), 0); -} - -TEST(Runlength, EncodeDecode) { - char* original = DeepState_CStrUpToLen(MAX_STR_LEN, "abcdef0123456789"); - char* encoded = encode(original); - ASSERT_LE(strlen(encoded), strlen(original)*2) << "Encoding is > length*2!"; - char* roundtrip = decode(encoded); - ASSERT_EQ(strncmp(roundtrip, original, MAX_STR_LEN), 0) << - "ORIGINAL: '" << original << "', ENCODED: '" << encoded << - "', ROUNDTRIP: '" << roundtrip << "'"; -} -``` - -The code above (which can be found -[here](https://github.com/trailofbits/deepstate/blob/master/examples/Runlen.cpp)) -is a fairly typical DeepState test harness. Most of the code is -just the functions to be tested. Using DeepState to test them requires: - -- Including the DeepState C++ header and using the DeepState namespace - -- Defining at least one TEST, with names - -- Calling some DeepState APIs that produce data - - In this example, we see the `DeepState_CStrUpToLen` call tells - DeepState to produce a string that has up to `MAX_STR_LEN` - characters, chosen from those present in hex strings. - -- Optionally making some assertions about the correctness of the -results - - In `Runlen.cpp` this is the `ASSERT_LE` and `ASSERT_EQ` checks. - - In the absence of any properties to check, DeepState can still - look for memory safety violations, crashes, and other general - categories of undesirable behavior, like any fuzzer. - - -## Running the test - -``` -~/deepstate/build/examples$ ./Runlen -TRACE: Running: Runlength_EncodeDecode from /Users/alex/deepstate/examples/Runlen.cpp(55) -TRACE: Passed: Runlength_EncodeDecode -TRACE: Running: Runlength_BoringUnitTest from /Users/alex/deepstate/examples/Runlen.cpp(49) -TRACE: Passed: Runlength_BoringUnitTest -``` - -Executing the DeepState executable will run the "BoringUnitTest" and -"EncodeDecode" tests. -The first one is a traditional hand-written unit test and simply tests -fixed inputs chosen by a programmer. The second one uses default (all zero bytes) -values. These inputs do not expose the bug in `encode`. - -Using DeepState's built-in brute-force fuzzer, however, it is easy to find the bug. Just try: - -```shell -deepstate-angr ./Runlen --output_test_dir out -``` - -or - -```shell -./Runlen --fuzz --exit_on_fail --output_test_dir out -``` - -The fuzzer will output something like: - -``` -INFO: Starting fuzzing -WARNING: No seed provided; using 1546631311 -WARNING: No test specified, defaulting to last test defined (Runlength_EncodeDecode) -CRITICAL: /Users/alex/deepstate/examples/Runlen.cpp(60): ORIGINAL: '91c499', ENCODED: '9A1AcA4A9A', ROUNDTRIP: '91c49' -ERROR: Failed: Runlength_EncodeDecode -``` - - -## Test replay - -To run saved inputs against the test, just run the executable with appropriate arguments: -```shell -./Runlen --input_test_dir ./out -INFO: Ran 0 tests for Runlength_BoringUnitTest; 0 tests failed -CRITICAL: /home/gros/studia/mgr/fuzzing/tools/deepstate/examples/Runlen.cpp(60): ORIGINAL: 'abbbbb', ENCODED: 'aAbA', ROUNDTRIP: 'ab' -ERROR: Failed: Runlength_EncodeDecode -... -INFO: Ran 64 tests for Runlength_EncodeDecode; 31 tests failed -``` - -Running tests not in a directory structure created by DeepState -requires using the `--input_test_files_dir` option instead. And, of -course, a single test can be run using `--input_test_file`. - -## Test case reduction - -While tests generated by symbolic execution are likely to be highly -concise already, fuzzer-generated tests may be much larger than they -need to be. - -DeepState provides a (state-of-the-art) test case reducer to shrink tests intelligently, -using knowledge of the structure of a DeepState test. For example, if your -executable is named `TestFileSystem` and the test you want to reduce -is named `rmdirfail.test` you would use it like this: - -```shell -deepstate-reduce ./TestFileSystem rmdirfail.test minrmdirfail.test -``` - -In many cases, this will result in finding a different failure or -crash that allows smaller test cases, so you can also provide a string -that controls the criterion for which test outputs are considered valid -reductions (by default, the reducer looks for any test that fails or -crashes). Only outputs containing the `--criterion` are considered to -be valid reductions (`--regexpCriterion` lets you use a Python regexp -for more complex checks): - -```shell -deepstate-reduce ./TestFileSystem create.test mincreate.test --criterion "Assertion failed: ((testfs_inode_get_type(in) == I_FILE)" -``` - -The output will look something like: - -``` -Original test has 8192 bytes -Applied 128 range conversions -Last byte read: 527 -Shrinking to ignore unread bytes -Writing reduced test with 528 bytes to rnew -================================================================================ -Iteration #1 0.39 secs / 2 execs / 0.0% reduction -Structured deletion reduced test to 520 bytes -Writing reduced test with 520 bytes to rnew -0.77 secs / 3 execs / 1.52% reduction - -... - -Structured swap: PASS FINISHED IN 0.01 SECONDS, RUN: 5.1 secs / 151 execs / 97.54% reduction -Reduced byte 12 from 4 to 1 -Writing reduced test with 13 bytes to rnew -5.35 secs / 169 execs / 97.54% reduction -================================================================================ -Byte reduce: PASS FINISHED IN 0.5 SECONDS, RUN: 5.6 secs / 186 execs / 97.54% reduction -================================================================================ -Iteration #2 5.6 secs / 186 execs / 97.54% reduction -Structured deletion: PASS FINISHED IN 0.03 SECONDS, RUN: 5.62 secs / 188 execs / 97.54% reduction -Structured edge deletion: PASS FINISHED IN 0.03 SECONDS, RUN: 5.65 secs / 190 execs / 97.54% reduction -1-byte chunk removal: PASS FINISHED IN 0.19 SECONDS, RUN: 5.84 secs / 203 execs / 97.54% reduction -4-byte chunk removal: PASS FINISHED IN 0.19 SECONDS, RUN: 6.03 secs / 216 execs / 97.54% reduction -8-byte chunk removal: PASS FINISHED IN 0.19 SECONDS, RUN: 6.22 secs / 229 execs / 97.54% reduction -1-byte reduce and delete: PASS FINISHED IN 0.04 SECONDS, RUN: 6.26 secs / 232 execs / 97.54% reduction -4-byte reduce and delete: PASS FINISHED IN 0.03 SECONDS, RUN: 6.29 secs / 234 execs / 97.54% reduction -8-byte reduce and delete: PASS FINISHED IN 0.01 SECONDS, RUN: 6.31 secs / 235 execs / 97.54% reduction -Byte range removal: PASS FINISHED IN 0.76 SECONDS, RUN: 7.06 secs / 287 execs / 97.54% reduction -Structured swap: PASS FINISHED IN 0.01 SECONDS, RUN: 7.08 secs / 288 execs / 97.54% reduction -================================================================================ -Completed 2 iterations: 7.08 secs / 288 execs / 97.54% reduction -Padding test with 23 zeroes -Writing reduced test with 36 bytes to mincreate.test -``` - -You can use `--which_test ` to specify which test to -run, as with the `--input_which_test` options to test replay. If you -find that test reduction is taking too long, you can try the `--fast` -option to get a quick-and-dirty reduction, and later use the default -settings, or even `--slowest` setting to try to reduce it further. - -Test case reduction should work on any OS. - - -## Log Levels - -By default, DeepState is not very verbose about testing activity, -other than failing tests. The `DEEPSTATE_LOG` environment variable -or the `--min_log_level` argument lowers the threshold for output, -with: -* 0 = `DEBUG`, -* 1 = `TRACE` (output from the tests, including from `printf`), -* 2 = `INFO` (DeepState messages, the default), -* 3 = `WARNING`, -* 4 = `ERROR`, -* 5 = `EXTERNAL` (output from other programs such as libFuzzer), -* 6 = `CRITICAL` messages. - -Lowering the `min_log_level` can be very useful for understanding what a DeepState harness is actually doing. - -Often, setting `--min_log_level 1` in either fuzzing or symbolic execution will give sufficient information to debug your test harness. +# Basic usage + +DeepState consists of a static library, used to write test harnesses, +and command-line _executors_ written in Python. At this time, the best +documentation is in the [examples](/examples) and in our +[paper](https://agroce.github.io/bar18.pdf). A more extensive +example, using DeepState and libFuzzer to test a user-mode file +system, is available [here](https://github.com/agroce/testfs); in +particular the +[Tests.cpp](https://github.com/agroce/testfs/blob/master/Tests.cpp) +file and CMakeLists.txt show DeepState usage. Another extensive +example is a [differential tester that compares Google's leveldb and +Facebook's rocksdb](https://github.com/agroce/testleveldb). + + +Table of Contents +================= + + * [Writing test harness](#writing-test-harness) + * [Running the test](#running-the-test) + * [Tests replay](#tests-replay) + * [Test case reduction](#test-case-reduction) + * [Log Levels](#log-levels) + + +## Writing a test harness + +A simple example test harness is included in the `examples` directory, +to test a (rather silly) run length encoding implementation: + +```cpp +#include + +using namespace deepstate; + +/* Simple, buggy, run-length encoding that creates "human readable" + * encodings by adding 'A'-1 to the count, and splitting at 26. + * e.g., encode("aaabbbbbc") = "aCbEcA" since C=3 and E=5 */ + +char* encode(const char* input) { + unsigned int len = strlen(input); + char* encoded = (char*)malloc((len*2)+1); + int pos = 0; + if (len > 0) { + unsigned char last = input[0]; + int count = 1; + for (int i = 1; i < len; i++) { + if (((unsigned char)input[i] == last) && (count < 26)) + count++; + else { + encoded[pos++] = last; + encoded[pos++] = 64 + count; + last = (unsigned char)input[i]; + count = 1; + } + } + encoded[pos++] = last; + encoded[pos++] = 65; // Should be 64 + count + } + encoded[pos] = '\0'; + return encoded; +} + +char* decode(const char* output) { + unsigned int len = strlen(output); + char* decoded = (char*)malloc((len/2)*26); + int pos = 0; + for (int i = 0; i < len; i += 2) { + for (int j = 0; j < (output[i+1] - 64); j++) { + decoded[pos++] = output[i]; + } + } + decoded[pos] = '\0'; + return decoded; +} + +// Can be (much) higher (e.g., > 1024) if we're using fuzzing, not symbolic execution +#define MAX_STR_LEN 6 + +TEST(Runlength, BoringUnitTest) { + ASSERT_EQ(strcmp(encode(""), ""), 0); + ASSERT_EQ(strcmp(encode("a"), "aA"), 0); + ASSERT_EQ(strcmp(encode("aaabbbbbc"), "aCbEcA"), 0); +} + +TEST(Runlength, EncodeDecode) { + char* original = DeepState_CStrUpToLen(MAX_STR_LEN, "abcdef0123456789"); + char* encoded = encode(original); + ASSERT_LE(strlen(encoded), strlen(original)*2) << "Encoding is > length*2!"; + char* roundtrip = decode(encoded); + ASSERT_EQ(strncmp(roundtrip, original, MAX_STR_LEN), 0) << + "ORIGINAL: '" << original << "', ENCODED: '" << encoded << + "', ROUNDTRIP: '" << roundtrip << "'"; +} +``` + +The code above (which can be found +[here](https://github.com/trailofbits/deepstate/blob/master/examples/Runlen.cpp)) +is a fairly typical DeepState test harness. Most of the code is +just the functions to be tested. Using DeepState to test them requires: + +- Including the DeepState C++ header and using the DeepState namespace + +- Defining at least one TEST, with names + +- Calling some DeepState APIs that produce data + - In this example, we see the `DeepState_CStrUpToLen` call tells + DeepState to produce a string that has up to `MAX_STR_LEN` + characters, chosen from those present in hex strings. + +- Optionally making some assertions about the correctness of the +results + - In `Runlen.cpp` this is the `ASSERT_LE` and `ASSERT_EQ` checks. + - In the absence of any properties to check, DeepState can still + look for memory safety violations, crashes, and other general + categories of undesirable behavior, like any fuzzer. + + +## Running the test + +``` +~/deepstate/build/examples$ ./Runlen +TRACE: Running: Runlength_EncodeDecode from /Users/alex/deepstate/examples/Runlen.cpp(55) +TRACE: Passed: Runlength_EncodeDecode +TRACE: Running: Runlength_BoringUnitTest from /Users/alex/deepstate/examples/Runlen.cpp(49) +TRACE: Passed: Runlength_BoringUnitTest +``` + +Executing the DeepState executable will run the "BoringUnitTest" and +"EncodeDecode" tests. +The first one is a traditional hand-written unit test and simply tests +fixed inputs chosen by a programmer. The second one uses default (all zero bytes) +values. These inputs do not expose the bug in `encode`. + +Using DeepState's built-in brute-force fuzzer, however, it is easy to find the bug. Just try: + +```shell +deepstate-angr ./Runlen --output_test_dir out +``` + +or + +```shell +./Runlen --fuzz --exit_on_fail --output_test_dir out +``` + +The fuzzer will output something like: + +``` +INFO: Starting fuzzing +WARNING: No seed provided; using 1546631311 +WARNING: No test specified, defaulting to last test defined (Runlength_EncodeDecode) +CRITICAL: /Users/alex/deepstate/examples/Runlen.cpp(60): ORIGINAL: '91c499', ENCODED: '9A1AcA4A9A', ROUNDTRIP: '91c49' +ERROR: Failed: Runlength_EncodeDecode +``` + + +## Test replay + +To run saved inputs against the test, just run the executable with appropriate arguments: +```shell +./Runlen --input_test_dir ./out +INFO: Ran 0 tests for Runlength_BoringUnitTest; 0 tests failed +CRITICAL: /home/gros/studia/mgr/fuzzing/tools/deepstate/examples/Runlen.cpp(60): ORIGINAL: 'abbbbb', ENCODED: 'aAbA', ROUNDTRIP: 'ab' +ERROR: Failed: Runlength_EncodeDecode +... +INFO: Ran 64 tests for Runlength_EncodeDecode; 31 tests failed +``` + +Running tests not in a directory structure created by DeepState +requires using the `--input_test_files_dir` option instead. And, of +course, a single test can be run using `--input_test_file`. + +## Test case reduction + +While tests generated by symbolic execution are likely to be highly +concise already, fuzzer-generated tests may be much larger than they +need to be. + +DeepState provides a (state-of-the-art) test case reducer to shrink tests intelligently, +using knowledge of the structure of a DeepState test. For example, if your +executable is named `TestFileSystem` and the test you want to reduce +is named `rmdirfail.test` you would use it like this: + +```shell +deepstate-reduce ./TestFileSystem rmdirfail.test minrmdirfail.test +``` + +In many cases, this will result in finding a different failure or +crash that allows smaller test cases, so you can also provide a string +that controls the criterion for which test outputs are considered valid +reductions (by default, the reducer looks for any test that fails or +crashes). Only outputs containing the `--criterion` are considered to +be valid reductions (`--regexpCriterion` lets you use a Python regexp +for more complex checks): + +```shell +deepstate-reduce ./TestFileSystem create.test mincreate.test --criterion "Assertion failed: ((testfs_inode_get_type(in) == I_FILE)" +``` + +The output will look something like: + +``` +Original test has 8192 bytes +Applied 128 range conversions +Last byte read: 527 +Shrinking to ignore unread bytes +Writing reduced test with 528 bytes to rnew +================================================================================ +Iteration #1 0.39 secs / 2 execs / 0.0% reduction +Structured deletion reduced test to 520 bytes +Writing reduced test with 520 bytes to rnew +0.77 secs / 3 execs / 1.52% reduction + +... + +Structured swap: PASS FINISHED IN 0.01 SECONDS, RUN: 5.1 secs / 151 execs / 97.54% reduction +Reduced byte 12 from 4 to 1 +Writing reduced test with 13 bytes to rnew +5.35 secs / 169 execs / 97.54% reduction +================================================================================ +Byte reduce: PASS FINISHED IN 0.5 SECONDS, RUN: 5.6 secs / 186 execs / 97.54% reduction +================================================================================ +Iteration #2 5.6 secs / 186 execs / 97.54% reduction +Structured deletion: PASS FINISHED IN 0.03 SECONDS, RUN: 5.62 secs / 188 execs / 97.54% reduction +Structured edge deletion: PASS FINISHED IN 0.03 SECONDS, RUN: 5.65 secs / 190 execs / 97.54% reduction +1-byte chunk removal: PASS FINISHED IN 0.19 SECONDS, RUN: 5.84 secs / 203 execs / 97.54% reduction +4-byte chunk removal: PASS FINISHED IN 0.19 SECONDS, RUN: 6.03 secs / 216 execs / 97.54% reduction +8-byte chunk removal: PASS FINISHED IN 0.19 SECONDS, RUN: 6.22 secs / 229 execs / 97.54% reduction +1-byte reduce and delete: PASS FINISHED IN 0.04 SECONDS, RUN: 6.26 secs / 232 execs / 97.54% reduction +4-byte reduce and delete: PASS FINISHED IN 0.03 SECONDS, RUN: 6.29 secs / 234 execs / 97.54% reduction +8-byte reduce and delete: PASS FINISHED IN 0.01 SECONDS, RUN: 6.31 secs / 235 execs / 97.54% reduction +Byte range removal: PASS FINISHED IN 0.76 SECONDS, RUN: 7.06 secs / 287 execs / 97.54% reduction +Structured swap: PASS FINISHED IN 0.01 SECONDS, RUN: 7.08 secs / 288 execs / 97.54% reduction +================================================================================ +Completed 2 iterations: 7.08 secs / 288 execs / 97.54% reduction +Padding test with 23 zeroes +Writing reduced test with 36 bytes to mincreate.test +``` + +You can use `--which_test ` to specify which test to +run, as with the `--input_which_test` options to test replay. If you +find that test reduction is taking too long, you can try the `--fast` +option to get a quick-and-dirty reduction, and later use the default +settings, or even `--slowest` setting to try to reduce it further. + +Test case reduction should work on any OS. + + +## Log Levels + +By default, DeepState is not very verbose about testing activity, +other than failing tests. The `DEEPSTATE_LOG` environment variable +or the `--min_log_level` argument lowers the threshold for output, +with: +* 0 = `DEBUG`, +* 1 = `TRACE` (output from the tests, including from `printf`), +* 2 = `INFO` (DeepState messages, the default), +* 3 = `WARNING`, +* 4 = `ERROR`, +* 5 = `EXTERNAL` (output from other programs such as libFuzzer), +* 6 = `CRITICAL` messages. + +Lowering the `min_log_level` can be very useful for understanding what a DeepState harness is actually doing. + +Often, setting `--min_log_level 1` in either fuzzing or symbolic execution will give sufficient information to debug your test harness. diff --git a/docs/fuzzing.md b/docs/fuzzing.md index 61749ec2..2bd1d0c9 100644 --- a/docs/fuzzing.md +++ b/docs/fuzzing.md @@ -1,415 +1,415 @@ -# Fuzzing - -Table of Contents -================= - - * [Built-In Fuzzer](#built-in-fuzzer) - * [A Note on MacOS and Forking](#a-note-on-macos-and-forking) - * [External fuzzers](#external-fuzzers) - * [Fuzzer executors usage](#fuzzer-executors-usage) - * [AFL](#afl) - * [libFuzzer](#libfuzzer) - * [HonggFuzz](#honggfuzz) - * [Eclipser](#eclipser) - * [Angora](#angora) - * [Ensembler (fuzzers synchronization)](#ensembler-fuzzers-synchronization) - * [Tests replay](#tests-replay) - * [Which Fuzzer Should I Use?](#which-fuzzer-should-i-use) - - -## Built-In Fuzzer - -Every DeepState executable provides a simple built-in fuzzer that -generates tests using completely random data. Using this fuzzer is as -simple as calling the native executable with the `--fuzz` argument. -The fuzzer also takes a `seed` and `timeout` (default of two minutes) -to control the fuzzing. By default fuzzing saves -only failing and crashing tests, and these only when given an output -directory. If you want to actually save the test cases -generated, you need to add the `--output_test_dir` argument to tell -DeepState where to put the generated tests, and if you want the -(totally random and unlikely to be high-quality) passing tests, you -need to add `--fuzz_save_passing`. - -Note that while symbolic execution only works on Linux, without a -fairly complex cross-compilation process, the brute force fuzzer works -on macOS or (as far as we know) any Unix-like system. - -## A Note on MacOS and Forking - -Normally, when running a test for replay or fuzzing, DeepState forks -in order to cleanly handle crashes of a test. Unfortunately, `fork()` -on macOS is _extremely_ slow. When using the built-in fuzzer or -replaying more than a few tests, it is highly recommended to add the `--no_fork` -option on macOS, unless you need the added crash handling (that is, -only when things aren't working without that option). Using -`--no_fork` can provide a modest speedup on other OS platforms as -well, in our experience. - -## External fuzzers - -DeepState currently explicitly supports five external fuzzers with -full-fledged custom front-ends: -[libFuzzer](https://llvm.org/docs/LibFuzzer.html), -[AFL](http://lcamtuf.coredump.cx/afl), -[HonggFuzz](https://github.com/google/honggfuzz), -[Eclipser](https://github.com/SoftSec-KAIST/Eclipser), and -[Angora](https://github.com/AngoraFuzzer/Angora). - -Additionally, DeepState can probably be used with most fuzzers that can interact with -a program through a file interface, via `--input_test_file @@` or something similar. -E.g., we have successfully used DeepState with -[Google's Jackalope](https://github.com/googleprojectzero/Jackalope). -Doing this may take some work: for Jackalope, we had to run the process -via sudo to allow the debugger to attach, and turn off forking. - -To use one of the fully-supported fuzzers as a DeepState backend, you need to: -* install it -* compile DeepState with it -* compile the target library/codebase with it (this is probably the - hardest part) -* compile the target test harness with it -* run executor with location of installed files provided - -To install a fuzzer follow the instructions on its website or -run Deepstate via Docker, as described in [README.md](/README.md) - -To compile DeepState with the fuzzer, run `cmake` with -`-DDEEPSTATE_FUZZERNAME=on` (like `-DDEEPSTATE_AFL=on`) option and -`CC/CXX` variables set to the fuzzer's compiler. This will produce -library called `libdeepstate_FUZZERNAME.a`, which you may put in -a standard location (`/usr/local/lib/`). - -To compile a target test, use the fuzzer's compiler and link with the appropriate -DeepState library (`-ldeepstate_FUZZERNAME`). - -To provide the location of a fuzzer's executables to the Python executor you may: -* put the executables in some `$PATH` location -* export a `FUZZERNAME_HOME` environment variable (like `ANGORA_HOME`) -with value set to the location of fuzzer's executables -* specify the `--home_path` argument when running the executor - -All this rather complicated setup may be considerably simplified by using Docker. -Just build the image (changing OS in `./docker/base/Dockerfile` if needed) -and use it with your project. All the fuzzers and environment variables will be there. - -### Fuzzer executor usage - -Fuzzer executors (`deepstate-honggfuzz` etc.) are meant to be as uniform -as possible, thus making it easy to compile and run tests. - -Compilation: `deepstate-afl --compile_test ./SimpleCrash.cpp --out_test_name SimpleCrash` - -Run: `mkdir out && deepstate-afl --output_test_dir out ./SimpleCrash.afl` - -The only required arguments are the location of output directory and the test. -Optional arguments: -``` ---input_seeds - location of directory with initial inputs ---max_input_size - maximal length of inputs ---exec_timeout - timeout for run on one input file ---timeout - timeout for whole fuzzing process ---fuzzer_out - use fuzzer output rather that deepstate (uniform) one ---mem_limit - memory limit for the fuzzer ---min_log_level - how much to log (0=DEBUG, 6=CRITICAL) ---blackbox - fuzz non-instrumented binary ---dictionary - file with words that may enhance fuzzing (fuzzer dependent format) -``` - -Each fuzzer creates following files/directories under output directory: -``` -* deepstate-stats.txt - some statistic parsed by executor -* fuzzer-output.txt - all stdout/stderr from the fuzzer -* PUSH_DIR - fuzzer will take (synchronize) additional inputs from here -* PULL_DIR - fuzzer will save produced inputs here (may be the same as PUSH_DIR) -* CRASH_DIR - fuzzer will save crashes here -``` - -Failed tests are treated as crashes when using fuzzer executors -(because of the `--abort_on_fail` flag). - -Note that some fuzzers (notably AFL) require input seeds. When not -provided, the executor will create a dumb one, which may be not very efficient for fuzzing. - -Input files need to be smaller than the DeepState input size limit (8192 bytes), -which is the default limit in executors. But not all fuzzers support a -file size -limitation, so if your test cases grown too large, you may need to stop fuzzing -and minimize them. - -Also, there should not be crash-producing files inside the input seeds directory. - -To resume a stopped fuzzing session, just run executor again with the same -output directory (the `--input_seeds` argument will be ignored). - -Because AFL and other file-based fuzzers only rely on the DeepState -native test executable, they should (like DeepState's built-in simple -fuzzer) work fine on macOS and other Unix-like OSes. On macOS, you -will want to consider doing the work to use [persistent mode](http://lcamtuf.blogspot.com/2015/06/new-in-afl-persistent-mode.html), or even -running inside a VM, due to AFL (unless in persistent mode) relying -extensively on forks, which are very slow on macOS. - - -### AFL - -```bash -$ cd ./deepstate -$ mkdir -p build_afl && cd build_afl -$ export AFL_HOME="/afl-2.52b" -$ CXX="$AFL_HOME/afl-clang++" CC="$AFL_HOME/afl-clang" cmake -DDEEPSTATE_AFL=ON ../ -$ make -j4 -$ sudo cp ./libdeepstate_AFL.a /usr/local/lib/ -``` - -Dirs: -* PUSH_DIR - out/sync_dir/queue -* PULL_DIR - out/the_fuzzer/queue -* CRASH_DIR - out/the_fuzzer/crashes - -Synchronization: -* AFL executor (`deepstate-afl`) runs the fuzzer in auto-sync mode (`-M`) -* Test cases pushed to `PUSH_DIR` will be automatically used by the AFL -* Files may need correct names (`id:000001` etc), not implemented by the executor -* AFL's docs suggest to share `fuzzer_stats`, not implemented by the executor - -Resuming: -* Executor sets `--input` option to `-`, which is AFL way to resume fuzzing -* AFL creates multiple `out/the_fuzzer/crashes*` dirs, which is not handled by -the executor at the moment - -Statistics: -* AFL provides colorful, curses TUI -* If stdout is redirected, then it automatically switches to -more compact output -* Creates `fuzzer_stats` file, which is updated from time to time -* Executor uses the `fuzzer_stats` file - - -### libFuzzer - -It is bundled into newer clang compilers. - -```bash -$ cd ./deepstate -$ mkdir -p build_libfuzzer && cd build_libfuzzer -$ CXX=clang++ CC=clang cmake -DDEEPSTATE_LIBFUZZER=ON ../ -$ make -j4 -$ sudo cp ./libdeepstate_LF.a /usr/local/lib/ -``` - -Dirs: -* PUSH_DIR - out/sync_dir/queue -* PULL_DIR - out/sync_dir/queue -* CRASH_DIR - out/the_fuzzer/crashes - -Synchronization: -* libFuzzer executor runs the fuzzer in auto-sync mode (`-reload=1`) -* Test cases pushed to `PUSH_DIR` will be automatically used by the libFuzzer -* Filenames in `PUSH_DIR` may be arbitrary - -Resuming: -* libFuzzer uses test cases from each specified dir, -so the executor just uses `PULL_DIR` to resume fuzzing - -Statistics: -* Informations are printed line-by-line -* `-print_final_stats=1` makes the fuzzer output summary once finished -* Executor redirects libFuzzer's stdout to file and parses it - -Use the `LIBFUZZER_WHICH_TEST` -environment variable to control which test libFuzzer runs, using a -fully qualified name (e.g., -`Arithmetic_InvertibleMultiplication_CanFail`). By default, you get -the first test defined (which works fine if there is only one test). - -One hint when using libFuzzer is to avoid dynamically allocating -memory during a test, if that memory would not be freed on a test -failure. This will leak memory and libFuzzer will run out of memory -very quickly in each fuzzing session. Using libFuzzer on macOS -requires compiling DeepState and your program with a clang that -supports libFuzzer (which the Apple built-in probably won't); this can be as simple as doing: - -```shell -brew install llvm@7 -CC=/usr/local/opt/llvm\@7/bin/clang CXX=/usr/local/opt/llvm\@7/bin/clang++ DEEPSTATE_LIBFUZZER=TRUE cmake .. -make install -``` - -Other ways of getting an appropriate LLVM may also work. - -On macOS, libFuzzer's normal output is not visible. Because libFuzzer -does not fork to execute tests, there is no issue with fork speed on -macOS for this kind of fuzzing. - -On any platform, -you can see more about what DeepState under libFuzzer is doing by -setting the `LIBFUZZER_LOUD` environment variable, and tell libFuzzer -to stop upon finding a failing test using `LIBFUZZER_EXIT_ON_FAIL`. - - -### HonggFuzz - -```bash -$ cd ./deepstate -$ mkdir -p build_honggfuzz && cd build_honggfuzz -$ export HONGGFUZZ_HOME="/honggfuzz" -$ CXX="$HONGGFUZZ_HOME/hfuzz_cc/hfuzz-clang++" CC="$HONGGFUZZ_HOME/hfuzz_cc/hfuzz-clang" cmake -DDEEPSTATE_HONGGFUZZ=ON ../ -$ make -j4 -$ sudo cp ./libdeepstate_HFUZZ.a /usr/local/lib/ -``` - -Dirs: -* PUSH_DIR - out/sync_dir/queue -* PULL_DIR - out/sync_dir/queue -* CRASH_DIR - out/the_fuzzer/crashes - -Synchronization: -* Is [not implemented](https://github.com/google/honggfuzz/issues/125) -* New test cases are moved from `sync_dir` to `PUSH_DIR` by the executor, -so stopping and resuming the fuzzer will make it use new seeds. This needs to -be done manually (executor doesn't implement that at the moment) - -Resuming: -* Executor sets `--input` to `PUSH_DIR` to resume fuzzing - -Statistics: -* HonggFuzz provides curses TUI -* `--verbose` disables the TUI -* Also prints some informations line-by-line -* Produces HONGGFUZZ.REPORT.TXT (not too much info) -* `--logfile` may redirect stdout to some file -* Executor doesn't parse any of above at the moment - - -### Eclipser - -Eclipser uses QEMU instrumentation and therefore doesn't require -special DeepState compilation. You should just use `libdeepstate.a` -(QEMU doesn't like special instrumentation). - -Eclipser stores new test cases and crashes in json and base64 encoding. -Decoding to raw files is done automatically by the executor at the end of -a fuzzing process. - -Dirs: -* PUSH_DIR - out/sync_dir/queue -* PULL_DIR - out/sync_dir/queue -* CRASH_DIR - out/the_fuzzer/crashes - -Synchronization: -* [Probably not implemented](https://github.com/SoftSec-KAIST/Eclipser/issues/12) -* New test cases are moved from `sync_dir` to `PUSH_DIR` by the executor, -so stopping and resuming the fuzzer will make it use new seeds. This needs to -be done manually (executor doesn't implement that at the moment) - -Resuming: -* The executor sets `--input` to `PUSH_DIR` to resume fuzzing - -Statistics: -* Prints some informations to stdout (rather mysterious) -* Produces some files like `.coverage` (also mysterious) -* Executor doesn't parse any of above at the moment - - -### Angora - -Angora uses two binaries for fuzzing, one with taint tracking information -and one without. So we need two deepstate libraries and will need to -compile each test two times. - -Angora also requires old version of llvm/clang (between 4.0.0 and 7.1.0). -Executor will need to find it, so you may want to put it under `$ANGORA_HOME/clang+llvm/`. - -```bash -# for deepstate compilation only -$ export PATH="/clang+llvm/bin:$PATH" -$ export LD_LIBRARY_PATH="/clang+llvm/lib:$LD_LIBRARY_PATH" - -$ cd ./deepstate -$ export ANGORA_HOME="/angora" -$ mkdir -p build_angora_taint && cd build_angora_taint -$ export USE_TRACK=1 -$ CXX="$ANGORA_HOME/bin/angora-clang++" CC="$ANGORA_HOME/bin/angora-clang" cmake -DDEEPSTATE_ANGORA=ON ../ -$ make -j4 -i # ignore errors, because Angora doesn't support 32bit builds \ -$ sudo cp ./libdeepstate_taint.a /usr/local/lib/ -$ cd ../ - -$ mkdir -p build_angora_fast && cd build_angora_fast -$ export USE_FAST=1 -$ CXX="$ANGORA_HOME/bin/angora-clang++" CC="$ANGORA_HOME/bin/angora-clang" cmake -DDEEPSTATE_ANGORA=ON ../ -$ make -j4 -i -$ sudo cp ./libdeepstate_fast.a /usr/local/lib/ -``` - -```bash -$ mv /clang+llvm $ANGORA_HOME/ -$ mkdir out -$ deepstate-angora --compile_test ./SimpleCrash.cpp --out_test_name SimpleCrash -$ deepstate-angora -o out ./SimpleCrash.taint.angora ./SimpleCrash.fast.angora -``` - -Dirs: -* PUSH_DIR - out/sync_dir/queue -* PULL_DIR - out/angora/queue -* CRASH_DIR - out/angora/crashes - -Synchronization: -* Synchronization with AFL is implemented by the Angora. The executor uses it -(with Angora's `--sync_afl` option) -* Angora requires filenames to be appropriate to synchronize (use) them. -Each file in `PUSH_DIR` before sync has checked filename for -correct format (`id:000001` etc.) and compared the id (the number) with maximal id -in local directory (`PULL_DIR`). If the id is higher than the maximal local one, then -the test case is incorporated - -Resuming: -* Executor sets `--input` option to `-`, which is Angora way to resume fuzzing -* Angora creates multiple `out/angora*` dirs, which is not handled by -the executor at the moment - -Statistics: -* Angora provides TUI -* Creates `chart_stat.json` and `angora.log` files with some stats -* Executor uses limited amount of information from `chart_stat.json` -at the moment - - -### Ensembler (fuzzers synchronization) - -You may run as many executors as you want (and have resources). But to synchronize -them, you need to specify `--sync_dir` option pointing to some shared directory. - -Each fuzzer will push produced test cases to that directory and pull from it as needed. - -Currently, there are some limitations in synchronization for the following fuzzers: -* Eclipser - needs to be restarted to use pulled test cases -* HonggFuzz - same as above -* Angora - pulled files need to have correct, AFL format (`id:00003`) and the id must -be greater that the biggest in Angora's local (pull) directory -* libFuzzer - stops fuzzing after first crash found, so there should be no crashes in `sync_dir` - - -## Tests replay - -To run saved inputs against some test, just run it with appropriate arguments: - -``` -./Runlen --abort_on_fail --input_test_files_dir ./out/output_afl/the_fuzzer/queue -``` - -No need to use fuzzer specific compilation (so don't use `SimpleCrash_AFL` etc. -They are slower due to instrumentation). - - -## Which Fuzzer Should I Use? - -In fact, since DeepState supports libFuzzer, AFL, HonggFuzz, Angora and Eclipser, -a natural question is "which is the best fuzzer?" In -general, it depends! We suggest using them all, which DeepState makes -easy. libFuzzer is very fast, and sometimes the CMP breakdown it -provides is very useful; however, it's often bad at finding longer -paths where just covering nodes isn't helpful. AFL is still an -excellent general-purpose fuzzer, and often beats "improved" versions -over a range of programs. Finally, Eclipser has some tricks that let -it get traction in some cases where you might think only symbolic -execution (which wouldn't scale) could help. +# Fuzzing + +Table of Contents +================= + + * [Built-In Fuzzer](#built-in-fuzzer) + * [A Note on MacOS and Forking](#a-note-on-macos-and-forking) + * [External fuzzers](#external-fuzzers) + * [Fuzzer executors usage](#fuzzer-executors-usage) + * [AFL](#afl) + * [libFuzzer](#libfuzzer) + * [HonggFuzz](#honggfuzz) + * [Eclipser](#eclipser) + * [Angora](#angora) + * [Ensembler (fuzzers synchronization)](#ensembler-fuzzers-synchronization) + * [Tests replay](#tests-replay) + * [Which Fuzzer Should I Use?](#which-fuzzer-should-i-use) + + +## Built-In Fuzzer + +Every DeepState executable provides a simple built-in fuzzer that +generates tests using completely random data. Using this fuzzer is as +simple as calling the native executable with the `--fuzz` argument. +The fuzzer also takes a `seed` and `timeout` (default of two minutes) +to control the fuzzing. By default fuzzing saves +only failing and crashing tests, and these only when given an output +directory. If you want to actually save the test cases +generated, you need to add the `--output_test_dir` argument to tell +DeepState where to put the generated tests, and if you want the +(totally random and unlikely to be high-quality) passing tests, you +need to add `--fuzz_save_passing`. + +Note that while symbolic execution only works on Linux, without a +fairly complex cross-compilation process, the brute force fuzzer works +on macOS or (as far as we know) any Unix-like system. + +## A Note on MacOS and Forking + +Normally, when running a test for replay or fuzzing, DeepState forks +in order to cleanly handle crashes of a test. Unfortunately, `fork()` +on macOS is _extremely_ slow. When using the built-in fuzzer or +replaying more than a few tests, it is highly recommended to add the `--no_fork` +option on macOS, unless you need the added crash handling (that is, +only when things aren't working without that option). Using +`--no_fork` can provide a modest speedup on other OS platforms as +well, in our experience. + +## External fuzzers + +DeepState currently explicitly supports five external fuzzers with +full-fledged custom front-ends: +[libFuzzer](https://llvm.org/docs/LibFuzzer.html), +[AFL](http://lcamtuf.coredump.cx/afl), +[HonggFuzz](https://github.com/google/honggfuzz), +[Eclipser](https://github.com/SoftSec-KAIST/Eclipser), and +[Angora](https://github.com/AngoraFuzzer/Angora). + +Additionally, DeepState can probably be used with most fuzzers that can interact with +a program through a file interface, via `--input_test_file @@` or something similar. +E.g., we have successfully used DeepState with +[Google's Jackalope](https://github.com/googleprojectzero/Jackalope). +Doing this may take some work: for Jackalope, we had to run the process +via sudo to allow the debugger to attach, and turn off forking. + +To use one of the fully-supported fuzzers as a DeepState backend, you need to: +* install it +* compile DeepState with it +* compile the target library/codebase with it (this is probably the + hardest part) +* compile the target test harness with it +* run executor with location of installed files provided + +To install a fuzzer follow the instructions on its website or +run Deepstate via Docker, as described in [README.md](/README.md) + +To compile DeepState with the fuzzer, run `cmake` with +`-DDEEPSTATE_FUZZERNAME=on` (like `-DDEEPSTATE_AFL=on`) option and +`CC/CXX` variables set to the fuzzer's compiler. This will produce +library called `libdeepstate_FUZZERNAME.a`, which you may put in +a standard location (`/usr/local/lib/`). + +To compile a target test, use the fuzzer's compiler and link with the appropriate +DeepState library (`-ldeepstate_FUZZERNAME`). + +To provide the location of a fuzzer's executables to the Python executor you may: +* put the executables in some `$PATH` location +* export a `FUZZERNAME_HOME` environment variable (like `ANGORA_HOME`) +with value set to the location of fuzzer's executables +* specify the `--home_path` argument when running the executor + +All this rather complicated setup may be considerably simplified by using Docker. +Just build the image (changing OS in `./docker/base/Dockerfile` if needed) +and use it with your project. All the fuzzers and environment variables will be there. + +### Fuzzer executor usage + +Fuzzer executors (`deepstate-honggfuzz` etc.) are meant to be as uniform +as possible, thus making it easy to compile and run tests. + +Compilation: `deepstate-afl --compile_test ./SimpleCrash.cpp --out_test_name SimpleCrash` + +Run: `mkdir out && deepstate-afl --output_test_dir out ./SimpleCrash.afl` + +The only required arguments are the location of output directory and the test. +Optional arguments: +``` +--input_seeds - location of directory with initial inputs +--max_input_size - maximal length of inputs +--exec_timeout - timeout for run on one input file +--timeout - timeout for whole fuzzing process +--fuzzer_out - use fuzzer output rather that deepstate (uniform) one +--mem_limit - memory limit for the fuzzer +--min_log_level - how much to log (0=DEBUG, 6=CRITICAL) +--blackbox - fuzz non-instrumented binary +--dictionary - file with words that may enhance fuzzing (fuzzer dependent format) +``` + +Each fuzzer creates following files/directories under output directory: +``` +* deepstate-stats.txt - some statistic parsed by executor +* fuzzer-output.txt - all stdout/stderr from the fuzzer +* PUSH_DIR - fuzzer will take (synchronize) additional inputs from here +* PULL_DIR - fuzzer will save produced inputs here (may be the same as PUSH_DIR) +* CRASH_DIR - fuzzer will save crashes here +``` + +Failed tests are treated as crashes when using fuzzer executors +(because of the `--abort_on_fail` flag). + +Note that some fuzzers (notably AFL) require input seeds. When not +provided, the executor will create a dumb one, which may be not very efficient for fuzzing. + +Input files need to be smaller than the DeepState input size limit (8192 bytes), +which is the default limit in executors. But not all fuzzers support a +file size +limitation, so if your test cases grown too large, you may need to stop fuzzing +and minimize them. + +Also, there should not be crash-producing files inside the input seeds directory. + +To resume a stopped fuzzing session, just run executor again with the same +output directory (the `--input_seeds` argument will be ignored). + +Because AFL and other file-based fuzzers only rely on the DeepState +native test executable, they should (like DeepState's built-in simple +fuzzer) work fine on macOS and other Unix-like OSes. On macOS, you +will want to consider doing the work to use [persistent mode](http://lcamtuf.blogspot.com/2015/06/new-in-afl-persistent-mode.html), or even +running inside a VM, due to AFL (unless in persistent mode) relying +extensively on forks, which are very slow on macOS. + + +### AFL + +```bash +$ cd ./deepstate +$ mkdir -p build_afl && cd build_afl +$ export AFL_HOME="/afl-2.52b" +$ CXX="$AFL_HOME/afl-clang++" CC="$AFL_HOME/afl-clang" cmake -DDEEPSTATE_AFL=ON ../ +$ make -j4 +$ sudo cp ./libdeepstate_AFL.a /usr/local/lib/ +``` + +Dirs: +* PUSH_DIR - out/sync_dir/queue +* PULL_DIR - out/the_fuzzer/queue +* CRASH_DIR - out/the_fuzzer/crashes + +Synchronization: +* AFL executor (`deepstate-afl`) runs the fuzzer in auto-sync mode (`-M`) +* Test cases pushed to `PUSH_DIR` will be automatically used by the AFL +* Files may need correct names (`id:000001` etc), not implemented by the executor +* AFL's docs suggest to share `fuzzer_stats`, not implemented by the executor + +Resuming: +* Executor sets `--input` option to `-`, which is AFL way to resume fuzzing +* AFL creates multiple `out/the_fuzzer/crashes*` dirs, which is not handled by +the executor at the moment + +Statistics: +* AFL provides colorful, curses TUI +* If stdout is redirected, then it automatically switches to +more compact output +* Creates `fuzzer_stats` file, which is updated from time to time +* Executor uses the `fuzzer_stats` file + + +### libFuzzer + +It is bundled into newer clang compilers. + +```bash +$ cd ./deepstate +$ mkdir -p build_libfuzzer && cd build_libfuzzer +$ CXX=clang++ CC=clang cmake -DDEEPSTATE_LIBFUZZER=ON ../ +$ make -j4 +$ sudo cp ./libdeepstate_LF.a /usr/local/lib/ +``` + +Dirs: +* PUSH_DIR - out/sync_dir/queue +* PULL_DIR - out/sync_dir/queue +* CRASH_DIR - out/the_fuzzer/crashes + +Synchronization: +* libFuzzer executor runs the fuzzer in auto-sync mode (`-reload=1`) +* Test cases pushed to `PUSH_DIR` will be automatically used by the libFuzzer +* Filenames in `PUSH_DIR` may be arbitrary + +Resuming: +* libFuzzer uses test cases from each specified dir, +so the executor just uses `PULL_DIR` to resume fuzzing + +Statistics: +* Informations are printed line-by-line +* `-print_final_stats=1` makes the fuzzer output summary once finished +* Executor redirects libFuzzer's stdout to file and parses it + +Use the `LIBFUZZER_WHICH_TEST` +environment variable to control which test libFuzzer runs, using a +fully qualified name (e.g., +`Arithmetic_InvertibleMultiplication_CanFail`). By default, you get +the first test defined (which works fine if there is only one test). + +One hint when using libFuzzer is to avoid dynamically allocating +memory during a test, if that memory would not be freed on a test +failure. This will leak memory and libFuzzer will run out of memory +very quickly in each fuzzing session. Using libFuzzer on macOS +requires compiling DeepState and your program with a clang that +supports libFuzzer (which the Apple built-in probably won't); this can be as simple as doing: + +```shell +brew install llvm@7 +CC=/usr/local/opt/llvm\@7/bin/clang CXX=/usr/local/opt/llvm\@7/bin/clang++ DEEPSTATE_LIBFUZZER=TRUE cmake .. +make install +``` + +Other ways of getting an appropriate LLVM may also work. + +On macOS, libFuzzer's normal output is not visible. Because libFuzzer +does not fork to execute tests, there is no issue with fork speed on +macOS for this kind of fuzzing. + +On any platform, +you can see more about what DeepState under libFuzzer is doing by +setting the `LIBFUZZER_LOUD` environment variable, and tell libFuzzer +to stop upon finding a failing test using `LIBFUZZER_EXIT_ON_FAIL`. + + +### HonggFuzz + +```bash +$ cd ./deepstate +$ mkdir -p build_honggfuzz && cd build_honggfuzz +$ export HONGGFUZZ_HOME="/honggfuzz" +$ CXX="$HONGGFUZZ_HOME/hfuzz_cc/hfuzz-clang++" CC="$HONGGFUZZ_HOME/hfuzz_cc/hfuzz-clang" cmake -DDEEPSTATE_HONGGFUZZ=ON ../ +$ make -j4 +$ sudo cp ./libdeepstate_HFUZZ.a /usr/local/lib/ +``` + +Dirs: +* PUSH_DIR - out/sync_dir/queue +* PULL_DIR - out/sync_dir/queue +* CRASH_DIR - out/the_fuzzer/crashes + +Synchronization: +* Is [not implemented](https://github.com/google/honggfuzz/issues/125) +* New test cases are moved from `sync_dir` to `PUSH_DIR` by the executor, +so stopping and resuming the fuzzer will make it use new seeds. This needs to +be done manually (executor doesn't implement that at the moment) + +Resuming: +* Executor sets `--input` to `PUSH_DIR` to resume fuzzing + +Statistics: +* HonggFuzz provides curses TUI +* `--verbose` disables the TUI +* Also prints some informations line-by-line +* Produces HONGGFUZZ.REPORT.TXT (not too much info) +* `--logfile` may redirect stdout to some file +* Executor doesn't parse any of above at the moment + + +### Eclipser + +Eclipser uses QEMU instrumentation and therefore doesn't require +special DeepState compilation. You should just use `libdeepstate.a` +(QEMU doesn't like special instrumentation). + +Eclipser stores new test cases and crashes in json and base64 encoding. +Decoding to raw files is done automatically by the executor at the end of +a fuzzing process. + +Dirs: +* PUSH_DIR - out/sync_dir/queue +* PULL_DIR - out/sync_dir/queue +* CRASH_DIR - out/the_fuzzer/crashes + +Synchronization: +* [Probably not implemented](https://github.com/SoftSec-KAIST/Eclipser/issues/12) +* New test cases are moved from `sync_dir` to `PUSH_DIR` by the executor, +so stopping and resuming the fuzzer will make it use new seeds. This needs to +be done manually (executor doesn't implement that at the moment) + +Resuming: +* The executor sets `--input` to `PUSH_DIR` to resume fuzzing + +Statistics: +* Prints some informations to stdout (rather mysterious) +* Produces some files like `.coverage` (also mysterious) +* Executor doesn't parse any of above at the moment + + +### Angora + +Angora uses two binaries for fuzzing, one with taint tracking information +and one without. So we need two deepstate libraries and will need to +compile each test two times. + +Angora also requires old version of llvm/clang (between 4.0.0 and 7.1.0). +Executor will need to find it, so you may want to put it under `$ANGORA_HOME/clang+llvm/`. + +```bash +# for deepstate compilation only +$ export PATH="/clang+llvm/bin:$PATH" +$ export LD_LIBRARY_PATH="/clang+llvm/lib:$LD_LIBRARY_PATH" + +$ cd ./deepstate +$ export ANGORA_HOME="/angora" +$ mkdir -p build_angora_taint && cd build_angora_taint +$ export USE_TRACK=1 +$ CXX="$ANGORA_HOME/bin/angora-clang++" CC="$ANGORA_HOME/bin/angora-clang" cmake -DDEEPSTATE_ANGORA=ON ../ +$ make -j4 -i # ignore errors, because Angora doesn't support 32bit builds \ +$ sudo cp ./libdeepstate_taint.a /usr/local/lib/ +$ cd ../ + +$ mkdir -p build_angora_fast && cd build_angora_fast +$ export USE_FAST=1 +$ CXX="$ANGORA_HOME/bin/angora-clang++" CC="$ANGORA_HOME/bin/angora-clang" cmake -DDEEPSTATE_ANGORA=ON ../ +$ make -j4 -i +$ sudo cp ./libdeepstate_fast.a /usr/local/lib/ +``` + +```bash +$ mv /clang+llvm $ANGORA_HOME/ +$ mkdir out +$ deepstate-angora --compile_test ./SimpleCrash.cpp --out_test_name SimpleCrash +$ deepstate-angora -o out ./SimpleCrash.taint.angora ./SimpleCrash.fast.angora +``` + +Dirs: +* PUSH_DIR - out/sync_dir/queue +* PULL_DIR - out/angora/queue +* CRASH_DIR - out/angora/crashes + +Synchronization: +* Synchronization with AFL is implemented by the Angora. The executor uses it +(with Angora's `--sync_afl` option) +* Angora requires filenames to be appropriate to synchronize (use) them. +Each file in `PUSH_DIR` before sync has checked filename for +correct format (`id:000001` etc.) and compared the id (the number) with maximal id +in local directory (`PULL_DIR`). If the id is higher than the maximal local one, then +the test case is incorporated + +Resuming: +* Executor sets `--input` option to `-`, which is Angora way to resume fuzzing +* Angora creates multiple `out/angora*` dirs, which is not handled by +the executor at the moment + +Statistics: +* Angora provides TUI +* Creates `chart_stat.json` and `angora.log` files with some stats +* Executor uses limited amount of information from `chart_stat.json` +at the moment + + +### Ensembler (fuzzers synchronization) + +You may run as many executors as you want (and have resources). But to synchronize +them, you need to specify `--sync_dir` option pointing to some shared directory. + +Each fuzzer will push produced test cases to that directory and pull from it as needed. + +Currently, there are some limitations in synchronization for the following fuzzers: +* Eclipser - needs to be restarted to use pulled test cases +* HonggFuzz - same as above +* Angora - pulled files need to have correct, AFL format (`id:00003`) and the id must +be greater that the biggest in Angora's local (pull) directory +* libFuzzer - stops fuzzing after first crash found, so there should be no crashes in `sync_dir` + + +## Tests replay + +To run saved inputs against some test, just run it with appropriate arguments: + +``` +./Runlen --abort_on_fail --input_test_files_dir ./out/output_afl/the_fuzzer/queue +``` + +No need to use fuzzer specific compilation (so don't use `SimpleCrash_AFL` etc. +They are slower due to instrumentation). + + +## Which Fuzzer Should I Use? + +In fact, since DeepState supports libFuzzer, AFL, HonggFuzz, Angora and Eclipser, +a natural question is "which is the best fuzzer?" In +general, it depends! We suggest using them all, which DeepState makes +easy. libFuzzer is very fast, and sometimes the CMP breakdown it +provides is very useful; however, it's often bad at finding longer +paths where just covering nodes isn't helpful. AFL is still an +excellent general-purpose fuzzer, and often beats "improved" versions +over a range of programs. Finally, Eclipser has some tricks that let +it get traction in some cases where you might think only symbolic +execution (which wouldn't scale) could help. diff --git a/docs/swarm_testing.md b/docs/swarm_testing.md index 3ada408f..3a159e18 100644 --- a/docs/swarm_testing.md +++ b/docs/swarm_testing.md @@ -1,46 +1,46 @@ -# Swarm Testing - -[Swarm testing](https://agroce.github.io/issta12.pdf) is an approach -to test generation that [modifies the distributions of finite choices](https://blog.regehr.org/archives/591) -(e.g., string generation and `OneOf` choices of which functions/lambdas to -call). It has a long history of improving compiler testing, and -usually (but not always) API testing. The Hypothesis Python testing -tool -[recently added swarm to its' stable of heuristics](https://github.com/HypothesisWorks/hypothesis/pull/2238). - -The basic idea is simple. Let's say we are generating tests of a -[stack that overflows when a 64th item is pushed on the stack](https://github.com/agroce/deepstate-stack), due to a -typo in the overflow check. Our tests are -128 calls to push/pop/top/clear. Obviously the odds of getting 64 -pushes in a row, without popping or clearing, are very low (for a dumb -fuzzer, the odds are astronomically low). -Coverage-feedback and various byte-copying heuristics in AFL and -libFuzzer etc. can sometimes work around such problems, but in other, -more complex cases, they are stumped. Swarm testing "flips a coin" -before each test, and only includes API calls in the test if the coin -came up heads for that test. That means we just need some test to run -with heads for push and tails for pop and clear. - -DeepState supports fully automated swarm testing. Just compile your -harness with `-DDEEPSTATE_PURE_SWARM` and all your `OneOf`s _and_ -DeepState string generation functions will use swarm testing. This is -a huge help for the built-in fuzzer (for example, it more than doubles -the fault detection rate for the `Runlen` example above). Eclipser -seems to sometimes get "stuck" with swarm testing, but AFL and libFuzzer, in our -experience, often benefit from swarm testing. There is also an option -`-DDEEPSTATE_MIXED_SWARM` that mixes swarm and regular generation. It -flips an additional coin for each potentially swarmable thing, and -decides to use swarm or not for that test. This can produce a mix of -swarm and regular generation that is unique to DeepState. If you -aren't finding any bugs using a harness that involves `OneOf` or -generating strings, it's a good idea to try both swarm methods before -declaring the code bug-free! There is another, more experimental, -swarm-like method, `-DDEEPSTATE_PROB_SWARM`, that is of possible interest. -Instead of pure binary inclusion/exclusion of choices, this varies the -actual distribution of choices. However, because this often ends up behaving -more like a non-swarm selection, it may not be as good at ferreting out -unusual behaviors due to extreme imbalance of choices. - -Note that tests produced under a particular swarm option are _not_ -binary compatible with other settings for swarm, due to the added coin -flips. Tests are tied to a particular choice of swarm mode. +# Swarm Testing + +[Swarm testing](https://agroce.github.io/issta12.pdf) is an approach +to test generation that [modifies the distributions of finite choices](https://blog.regehr.org/archives/591) +(e.g., string generation and `OneOf` choices of which functions/lambdas to +call). It has a long history of improving compiler testing, and +usually (but not always) API testing. The Hypothesis Python testing +tool +[recently added swarm to its' stable of heuristics](https://github.com/HypothesisWorks/hypothesis/pull/2238). + +The basic idea is simple. Let's say we are generating tests of a +[stack that overflows when a 64th item is pushed on the stack](https://github.com/agroce/deepstate-stack), due to a +typo in the overflow check. Our tests are +128 calls to push/pop/top/clear. Obviously the odds of getting 64 +pushes in a row, without popping or clearing, are very low (for a dumb +fuzzer, the odds are astronomically low). +Coverage-feedback and various byte-copying heuristics in AFL and +libFuzzer etc. can sometimes work around such problems, but in other, +more complex cases, they are stumped. Swarm testing "flips a coin" +before each test, and only includes API calls in the test if the coin +came up heads for that test. That means we just need some test to run +with heads for push and tails for pop and clear. + +DeepState supports fully automated swarm testing. Just compile your +harness with `-DDEEPSTATE_PURE_SWARM` and all your `OneOf`s _and_ +DeepState string generation functions will use swarm testing. This is +a huge help for the built-in fuzzer (for example, it more than doubles +the fault detection rate for the `Runlen` example above). Eclipser +seems to sometimes get "stuck" with swarm testing, but AFL and libFuzzer, in our +experience, often benefit from swarm testing. There is also an option +`-DDEEPSTATE_MIXED_SWARM` that mixes swarm and regular generation. It +flips an additional coin for each potentially swarmable thing, and +decides to use swarm or not for that test. This can produce a mix of +swarm and regular generation that is unique to DeepState. If you +aren't finding any bugs using a harness that involves `OneOf` or +generating strings, it's a good idea to try both swarm methods before +declaring the code bug-free! There is another, more experimental, +swarm-like method, `-DDEEPSTATE_PROB_SWARM`, that is of possible interest. +Instead of pure binary inclusion/exclusion of choices, this varies the +actual distribution of choices. However, because this often ends up behaving +more like a non-swarm selection, it may not be as good at ferreting out +unusual behaviors due to extreme imbalance of choices. + +Note that tests produced under a particular swarm option are _not_ +binary compatible with other settings for swarm, due to the added coin +flips. Tests are tied to a particular choice of swarm mode. diff --git a/docs/symbolic_execution.md b/docs/symbolic_execution.md index 8bd254d9..bdb8cd0c 100644 --- a/docs/symbolic_execution.md +++ b/docs/symbolic_execution.md @@ -1,6 +1,6 @@ -# Symbolic execution - -TODO: -- something general about SE -- something about angr and manticore -- how DeepState integrates SE (simplified stuff from the paper) +# Symbolic execution + +TODO: +- something general about SE +- something about angr and manticore +- how DeepState integrates SE (simplified stuff from the paper) diff --git a/docs/test_harness.md b/docs/test_harness.md index fe1c8079..20085818 100644 --- a/docs/test_harness.md +++ b/docs/test_harness.md @@ -1,423 +1,423 @@ -# Test harness - - -Table of Contents -================= - - * [General structure](#general-structure) - * [Symbolic variables - inputs](#symbolic-variables---inputs) - * [Symbolic prefix](#symbolic-prefix) - * [Strings and bytes](#strings-and-bytes) - * [ForAll](#forall) - * [Path to input file](#path-to-input-file) - * [OneOf](#oneof) - * [Preconditions - constraints](#preconditions---constraints) - * [Preconditions - assign and assume](#preconditions---assign-and-assume) - * [Postconditions - checks](#postconditions---checks) - * [Logs](#logs) - - -## General structure - - -Tests can be defined using the `TEST` macro. -This macro takes two arguments: a unit name (`PrimePolynomial`) -and a test name (`OnlyGeneratesPrimes`). - -```c -#include - -TEST(PrimePolynomial, OnlyGeneratesPrimes) { - ... -} - -TEST(PrimePolynomial, AnotherTest) { - ... -} -``` - -Each test is executed separately. If you need -a more complex setup and/or cleanup, `Test Fixtures` -can help. These are C++ classes inherited from -`deepstate:Test` that may implement two methods: -`SetUp` and `TearDown`. - -To use the class just pass it as the first argument to `TEST_F` macro. - -```cpp -class MyTest : public deepstate:Test { - public: - char* someVariable; - symbolic_int x; - - void SetUp(void) { - LOG(TRACE) << "Setting up!"; - someVariable = (char*)malloc(10); - } - - void TearDown(void) { - LOG(TRACE) << "Tearing down!"; - free(someVariable); - } - -}; - -TEST_F(MyTest, Something) { - ASSUME_EQ(x, 1); - ASSERT_NE(someVariable, 0); -} - -TEST_F(MyTest, SomethingElse) { - ASSUME_EQ(x, 3); -} -``` - - -## Symbolic variables: Inputs - -Executors need to know which variables are symbolic, -that is, which are controlled by a symbolic execution tool or fuzzer. Symbolic variables are used -as unknowns in equations during symbolic execution or populated -with "random" data by fuzzers. - -There are few ways to declare symbolic variables. - -#### Symbolic prefix - -For basic data types you may just add `symbolic_` prefix: - -```c -symbolic_unsigned x, y, z; -symbolic_char c; -symbolic_int8_t b; -``` - -Defined types: -* symbolic_char -* symbolic_short -* symbolic_int -* symbolic_unsigned -* symbolic_long -* symbolic_int8_t -* symbolic_uint8_t -* symbolic_int16_t -* symbolic_uint16_t -* symbolic_int32_t -* symbolic_uint32_t -* symbolic_int64_t -* symbolic_uint64_t - -#### Getting symbolic values - -Rather than declaring a special typed value, it is sometimes easier -(when interfacing other code, or harnesses already using `rand` etc.) -to just use the API to "ask" DeepState for a value. DeepState defines -functions returning most types of interest: - -* DeepState_Int() -* DeepState_UInt() -* DeepState_Size() (for `size_`) -* DeepState_Bool() -* DeepState_Char() -* DeepState_Float() -* DeepState_Double() - -The non-boolean types also allow you to request a value in a range, a -frequent need and one not as well supported by `symbolic ` decls -(you can use `ASSUME` to do it, but it's much more code, and will be -far less efficient with fuzzers), e.g., `DeepState_IntInRange(low, -high)`. DeepState ranges are inclusive. - -#### Strings and bytes - -To create a symbolic string you may use: - -`char* DeepState_CStr_C(size_t len, const char* allowed)` which -returns a pointer to an array of symbolic chars. The`strlen` of returned data -will always be `len`. If `allowed` is NULL, then all bytes except the -null terminator -will be allowed, otherwise strings will be generated from the given -character alphabet. - -`char* DeepState_CStrUpToLen(size_t maxLen, const char* allowed)` is the -same as `DeepState_CStr_C`, except that the length of returned string -may vary, up to `maxLen` (inclusive); the amount of memory allocated, -and the position of a null terminator, are chosen by the -fuzzer/symbolic execution tool. - -`void *DeepState_Malloc(size_t num_bytes)` just -allocates `num_bytes` symbolic bytes, with arbitrary value. **Failing to free -this pointer will lead to a memory leak, it's just a normal pointer.** - -`void *DeepState_GCMalloc(size_t num_bytes)` also -allocates `num_bytes` symbolic bytes, with arbitrary value, but -DeepState will free the pointer after the test is finished, even if -the test exits abnormally. **Freeing THIS pointer will lead to a -double-free error.** - -If you can be sure nothing you pass it to frees DeepState-allocated -memory, `DeepState_GCMalloc` is probably your best bet; it will work -much more nicely with libFuzzer and the `no_fork` option, where memory -leaks in tests are a big problem. - -#### ForAll -`ForAll` -creates temporary variables which may be used in lambda expressions. -It is declared thusly: -```cpp -template -inline static void ForAll(void (*func)(Args...)) { - func(Symbolic()...); -} - -template -inline static void ForAll(Closure func) { - func(Symbolic()...); -} -``` - -A usage example is: - -```cpp -ForAll([] (int x, int y) { - ASSERT_EQ(add(x, y), add(y, x)) - << "Addition of signed integers must commute."; -}); - -ForAll>([] (const std::vector &vec1) { - std::vector vec2 = vec1; - std::reverse(vec2.begin(), vec2.end()); - std::reverse(vec2.begin(), vec2.end()); - ASSERT_EQ(vec1, vec2) - << "Double reverse of vectors must be equal."; -}); -``` - -#### Path to input file -`const char *DeepState_InputPath(char *testcase_path)` - -returns a path to a generated file. The path may be used with standard C++ file handling -functions (like `open` and `read` etc). Useful for fuzzing (when -some API takes a path as an argument rather than raw data). This is -not useful for symbolic execution. - - -#### OneOf -`OneOf` is an -operator that takes as argument an arbitrary number of lambda -expressions. In each call to `OneOf`, a random lambda is chosen -and executed. This allows you to non-deterministically execute -chunks of code and apply [swarm testing](/docs/swarm_testing.md). - -Example: -```cpp -TEST(OneOfTest, Basic) { - symbolic_int data; - ctx *context = init_some_api(); - - for (int i = 0; i < 10; ++i) - { - OneOf( - [&context, &data, &i] { - some_api_call(context, data, i); - }, - [&context, &data] { - int ret = some_other_call(context, data); - ASSERT_EQ(ret, 0); - } - ); - } - - ASSERT_GT(context->smthing, 0); - - clear_context(context); - ASSERT_EQ(context, nullptr); -} -``` - -You can also supply explicit probabilities to `OneOfP`, e.g.: -```cpp -OneOfP( - 0.05, [&] {x = 1; foo();}, - -1, [&] {x = 32; bar();}, - 0.7, [&] {x = 128; baz();}, - -1, [&] {x = 64; foobaz();}); -``` - -This code sets `x` to 1 and calls `foo` 5% of the time, sets x to 128 and calls `baz` 70% of the time, -sets `x` to 32 and calls `bar` 37.5% of the time, and sets `x` to 64 and calls `foobaz` 37.5% of the time. -A probability of -1 tells DeepState to assign a uniform -probability, distributed over all left-over (-1) options. `OneOfP` does not yet support swarm testing; but you presumably -knew what you were doing when assigning probabilities! - -`OneOf` and `OneOfP` can also be applied to strings, arrays, or vectors to choose a random element, e.g.,: - -```cpp -const char *compressors[] = {"blosclz", "lz4", "lz4hc", "zlib", "zstd"}; -compress(in_buffer, out_buffer, OneOf(compressors)); -``` - -The `OneOfP` form can also be used for arrays or vectors, by providing the list of probabilities first, e.g.: - -```cpp -const char *compressors[] = {"blosclz", "lz4", "lz4hc", "zlib", "zstd"}; -compress(in_buffer, out_buffer, OneOfP({0.8, 0.001}, compressors)); -``` - -For this kind of `OneOfP`, in addition to the -1 method of specifying "omitted" probabilities, -you can just put the items you want to specify first in the array/vector, and only supply probabilities for those. -The example will almost always use "blocsclz" and almost never use "lz4". - -## Preconditions - constraints - -If you want to constrain a symbolic variable, i.e., tell the -executor that it should be less than some value, then use -`ASSUME_*` macros. These macros reduce the search space and -enhance test efficiency, and may be required to avoid invalid -inputs. Usage is simple: - -```c -ASSUME_GT(x, 37); -ASSUME_NE(strncmp(y, "hmm...", 7), 0); -``` - -Fuzzers will abort (but won't fail) if some assumption -happen to be false, which should guide fuzzing to the -expected values. - -DeepState provides following the precondition macros, in addition to -the generic `ASSUME` that takes a Boolean argument: - -* ASSUME_EQ -* ASSUME_NE -* ASSUME_LT -* ASSUME_LE -* ASSUME_GT -* ASSUME_GE - -## Preconditions - assign and assume - -Pure assumptions are potentially highly inefficient in fuzzing. In -fuzzing, a failed assumption simply aborts the test (there is no way -to constrain values or backtrack). This means that a pattern like: - -``` -int x = DeepState_Int(); -ASSUME (x % 2 == 0); // need an even value! -``` - -in a fuzzer, if there is much behavior prior to assigning `x`, can be -extremely inefficient, since half of all tests will abort. - -To work around this, DeepState provides a what is essentially an _assigning assume_, e.g.: - -``` -int x; -ASSIGN_SATISFYING(x, DeepState_Int(), x % 2 == 0); -``` - -In symbolic execution, this simply translates into an assignment and -an assumption. In concrete execution, however, it maps the chosen `x` -into the next (or previous) value that satisfies the predicate. There are a few -limitations to this usage, however: - -* The search is linear, since nothing else is reasonable for arbitrary - predicates, so it may be quite costly. -* Predicates with side effects are likely to be evaluated multiple -times (the generating expression is only evaluated once, however). -* Of course all this only works for essentially integral types, where - increment and decrement work as expected! -* The distribution is highly non-uniform. - -For the last point, consider code like: - -``` -int x = DeepState_Int(); -int y; -ASSIGN_SATISFYING(y, DeepState_Int(), y > x); -``` - -In fuzzing, it is highly likely that `y == x+1` and `y == MAX_INT` will result much more -often than any other relationships between `x` and `y` (one is the -result of incrementing `y`, the other of wrapping decrement). - -Additionally, if you use `ASSIGN_SATISFYING` with `DeepState_InRange` the -search may result in a value that is not in the range! You can -repeat your range restriction in the predicate if you want to avoid -this problem, but an easier way around it is to use -`ASSIGN_SATISFYING_IN_RANGE`, which works just as `ASSIGN_SATISFYING` -does, except -that it also takes a range, after the generation expression, e.g.: - -``` -int x = DeepState_IntInRange(-10, 10); -int y; -ASSIGN_SATISFYING_IN_RANGE(y, DeepState_IntInRange(-10, 10), -10, 10, y >= x); -``` - -Now `x` and `y` will both be guaranteed to fall in the range -10 to -10, inclusive. Notice that if you pass in an initial value that violates the range -constraint, this will just cause an assumption failure, so you want to include the -range in the assignment, also. - -## Postconditions - checks - -Once symbolic variables are declared, constrained, -and used in functions that are tested, you may want -to assert something about the results of testing, as in normal unit -tests. To do that use either the -`ASSERT_*` or `CHECK_*` family of macros. - -Macros from the first family will stop execution of the test if -the assertion is false. Macros from the second set will -mark the test as failed, but allow the test to continue. - -Fuzzers will treat false `ASSERT`/`CHECK` as crashes -if the `--abort_on_fail` option is set (which is by default -when using most executors). - -DeepState provides the following postcondition asserts: -* ASSERT_EQ -* ASSERT_NE -* ASSERT_LT -* ASSERT_LE -* ASSERT_GT -* ASSERT_GE -* ASSERT_TRUE -* ASSERT_FALSE - -and checks: - -* CHECK_EQ -* CHECK_NE -* CHECK_LT -* CHECK_LE -* CHECK_GT -* CHECK_GE -* CHECK_TRUE -* CHECK_FALSE - - -## Logs - -Printing debug information is easy, and you can use standard `printf`-like -functions. These are reimplemented by DeepState, so they won't -introduce space state explosion (see -[the paper](https://www.trailofbits.com/reports/deepstate-bar18.pdf)). You -may also use `LOG` macros for streaming output to various logging -levels (`printf` defaults to `TRACE` level). Setting `--min_log_level` -lets you control how much of this output DeepState shows when -replaying tests, or fuzzing. - -```c -LOG(INFO) << "Hello " << name; -``` - -Log levels: -* DEBUG -* TRACE -* INFO -* WARNING -* WARN -* ERROR -* FATAL -* CRITICAL +# Test harness + + +Table of Contents +================= + + * [General structure](#general-structure) + * [Symbolic variables - inputs](#symbolic-variables---inputs) + * [Symbolic prefix](#symbolic-prefix) + * [Strings and bytes](#strings-and-bytes) + * [ForAll](#forall) + * [Path to input file](#path-to-input-file) + * [OneOf](#oneof) + * [Preconditions - constraints](#preconditions---constraints) + * [Preconditions - assign and assume](#preconditions---assign-and-assume) + * [Postconditions - checks](#postconditions---checks) + * [Logs](#logs) + + +## General structure + + +Tests can be defined using the `TEST` macro. +This macro takes two arguments: a unit name (`PrimePolynomial`) +and a test name (`OnlyGeneratesPrimes`). + +```c +#include + +TEST(PrimePolynomial, OnlyGeneratesPrimes) { + ... +} + +TEST(PrimePolynomial, AnotherTest) { + ... +} +``` + +Each test is executed separately. If you need +a more complex setup and/or cleanup, `Test Fixtures` +can help. These are C++ classes inherited from +`deepstate:Test` that may implement two methods: +`SetUp` and `TearDown`. + +To use the class just pass it as the first argument to `TEST_F` macro. + +```cpp +class MyTest : public deepstate:Test { + public: + char* someVariable; + symbolic_int x; + + void SetUp(void) { + LOG(TRACE) << "Setting up!"; + someVariable = (char*)malloc(10); + } + + void TearDown(void) { + LOG(TRACE) << "Tearing down!"; + free(someVariable); + } + +}; + +TEST_F(MyTest, Something) { + ASSUME_EQ(x, 1); + ASSERT_NE(someVariable, 0); +} + +TEST_F(MyTest, SomethingElse) { + ASSUME_EQ(x, 3); +} +``` + + +## Symbolic variables: Inputs + +Executors need to know which variables are symbolic, +that is, which are controlled by a symbolic execution tool or fuzzer. Symbolic variables are used +as unknowns in equations during symbolic execution or populated +with "random" data by fuzzers. + +There are few ways to declare symbolic variables. + +#### Symbolic prefix + +For basic data types you may just add `symbolic_` prefix: + +```c +symbolic_unsigned x, y, z; +symbolic_char c; +symbolic_int8_t b; +``` + +Defined types: +* symbolic_char +* symbolic_short +* symbolic_int +* symbolic_unsigned +* symbolic_long +* symbolic_int8_t +* symbolic_uint8_t +* symbolic_int16_t +* symbolic_uint16_t +* symbolic_int32_t +* symbolic_uint32_t +* symbolic_int64_t +* symbolic_uint64_t + +#### Getting symbolic values + +Rather than declaring a special typed value, it is sometimes easier +(when interfacing other code, or harnesses already using `rand` etc.) +to just use the API to "ask" DeepState for a value. DeepState defines +functions returning most types of interest: + +* DeepState_Int() +* DeepState_UInt() +* DeepState_Size() (for `size_`) +* DeepState_Bool() +* DeepState_Char() +* DeepState_Float() +* DeepState_Double() + +The non-boolean types also allow you to request a value in a range, a +frequent need and one not as well supported by `symbolic ` decls +(you can use `ASSUME` to do it, but it's much more code, and will be +far less efficient with fuzzers), e.g., `DeepState_IntInRange(low, +high)`. DeepState ranges are inclusive. + +#### Strings and bytes + +To create a symbolic string you may use: + +`char* DeepState_CStr_C(size_t len, const char* allowed)` which +returns a pointer to an array of symbolic chars. The`strlen` of returned data +will always be `len`. If `allowed` is NULL, then all bytes except the +null terminator +will be allowed, otherwise strings will be generated from the given +character alphabet. + +`char* DeepState_CStrUpToLen(size_t maxLen, const char* allowed)` is the +same as `DeepState_CStr_C`, except that the length of returned string +may vary, up to `maxLen` (inclusive); the amount of memory allocated, +and the position of a null terminator, are chosen by the +fuzzer/symbolic execution tool. + +`void *DeepState_Malloc(size_t num_bytes)` just +allocates `num_bytes` symbolic bytes, with arbitrary value. **Failing to free +this pointer will lead to a memory leak, it's just a normal pointer.** + +`void *DeepState_GCMalloc(size_t num_bytes)` also +allocates `num_bytes` symbolic bytes, with arbitrary value, but +DeepState will free the pointer after the test is finished, even if +the test exits abnormally. **Freeing THIS pointer will lead to a +double-free error.** + +If you can be sure nothing you pass it to frees DeepState-allocated +memory, `DeepState_GCMalloc` is probably your best bet; it will work +much more nicely with libFuzzer and the `no_fork` option, where memory +leaks in tests are a big problem. + +#### ForAll +`ForAll` +creates temporary variables which may be used in lambda expressions. +It is declared thusly: +```cpp +template +inline static void ForAll(void (*func)(Args...)) { + func(Symbolic()...); +} + +template +inline static void ForAll(Closure func) { + func(Symbolic()...); +} +``` + +A usage example is: + +```cpp +ForAll([] (int x, int y) { + ASSERT_EQ(add(x, y), add(y, x)) + << "Addition of signed integers must commute."; +}); + +ForAll>([] (const std::vector &vec1) { + std::vector vec2 = vec1; + std::reverse(vec2.begin(), vec2.end()); + std::reverse(vec2.begin(), vec2.end()); + ASSERT_EQ(vec1, vec2) + << "Double reverse of vectors must be equal."; +}); +``` + +#### Path to input file +`const char *DeepState_InputPath(char *testcase_path)` - +returns a path to a generated file. The path may be used with standard C++ file handling +functions (like `open` and `read` etc). Useful for fuzzing (when +some API takes a path as an argument rather than raw data). This is +not useful for symbolic execution. + + +#### OneOf +`OneOf` is an +operator that takes as argument an arbitrary number of lambda +expressions. In each call to `OneOf`, a random lambda is chosen +and executed. This allows you to non-deterministically execute +chunks of code and apply [swarm testing](/docs/swarm_testing.md). + +Example: +```cpp +TEST(OneOfTest, Basic) { + symbolic_int data; + ctx *context = init_some_api(); + + for (int i = 0; i < 10; ++i) + { + OneOf( + [&context, &data, &i] { + some_api_call(context, data, i); + }, + [&context, &data] { + int ret = some_other_call(context, data); + ASSERT_EQ(ret, 0); + } + ); + } + + ASSERT_GT(context->smthing, 0); + + clear_context(context); + ASSERT_EQ(context, nullptr); +} +``` + +You can also supply explicit probabilities to `OneOfP`, e.g.: +```cpp +OneOfP( + 0.05, [&] {x = 1; foo();}, + -1, [&] {x = 32; bar();}, + 0.7, [&] {x = 128; baz();}, + -1, [&] {x = 64; foobaz();}); +``` + +This code sets `x` to 1 and calls `foo` 5% of the time, sets x to 128 and calls `baz` 70% of the time, +sets `x` to 32 and calls `bar` 37.5% of the time, and sets `x` to 64 and calls `foobaz` 37.5% of the time. +A probability of -1 tells DeepState to assign a uniform +probability, distributed over all left-over (-1) options. `OneOfP` does not yet support swarm testing; but you presumably +knew what you were doing when assigning probabilities! + +`OneOf` and `OneOfP` can also be applied to strings, arrays, or vectors to choose a random element, e.g.,: + +```cpp +const char *compressors[] = {"blosclz", "lz4", "lz4hc", "zlib", "zstd"}; +compress(in_buffer, out_buffer, OneOf(compressors)); +``` + +The `OneOfP` form can also be used for arrays or vectors, by providing the list of probabilities first, e.g.: + +```cpp +const char *compressors[] = {"blosclz", "lz4", "lz4hc", "zlib", "zstd"}; +compress(in_buffer, out_buffer, OneOfP({0.8, 0.001}, compressors)); +``` + +For this kind of `OneOfP`, in addition to the -1 method of specifying "omitted" probabilities, +you can just put the items you want to specify first in the array/vector, and only supply probabilities for those. +The example will almost always use "blocsclz" and almost never use "lz4". + +## Preconditions - constraints + +If you want to constrain a symbolic variable, i.e., tell the +executor that it should be less than some value, then use +`ASSUME_*` macros. These macros reduce the search space and +enhance test efficiency, and may be required to avoid invalid +inputs. Usage is simple: + +```c +ASSUME_GT(x, 37); +ASSUME_NE(strncmp(y, "hmm...", 7), 0); +``` + +Fuzzers will abort (but won't fail) if some assumption +happen to be false, which should guide fuzzing to the +expected values. + +DeepState provides following the precondition macros, in addition to +the generic `ASSUME` that takes a Boolean argument: + +* ASSUME_EQ +* ASSUME_NE +* ASSUME_LT +* ASSUME_LE +* ASSUME_GT +* ASSUME_GE + +## Preconditions - assign and assume + +Pure assumptions are potentially highly inefficient in fuzzing. In +fuzzing, a failed assumption simply aborts the test (there is no way +to constrain values or backtrack). This means that a pattern like: + +``` +int x = DeepState_Int(); +ASSUME (x % 2 == 0); // need an even value! +``` + +in a fuzzer, if there is much behavior prior to assigning `x`, can be +extremely inefficient, since half of all tests will abort. + +To work around this, DeepState provides a what is essentially an _assigning assume_, e.g.: + +``` +int x; +ASSIGN_SATISFYING(x, DeepState_Int(), x % 2 == 0); +``` + +In symbolic execution, this simply translates into an assignment and +an assumption. In concrete execution, however, it maps the chosen `x` +into the next (or previous) value that satisfies the predicate. There are a few +limitations to this usage, however: + +* The search is linear, since nothing else is reasonable for arbitrary + predicates, so it may be quite costly. +* Predicates with side effects are likely to be evaluated multiple +times (the generating expression is only evaluated once, however). +* Of course all this only works for essentially integral types, where + increment and decrement work as expected! +* The distribution is highly non-uniform. + +For the last point, consider code like: + +``` +int x = DeepState_Int(); +int y; +ASSIGN_SATISFYING(y, DeepState_Int(), y > x); +``` + +In fuzzing, it is highly likely that `y == x+1` and `y == MAX_INT` will result much more +often than any other relationships between `x` and `y` (one is the +result of incrementing `y`, the other of wrapping decrement). + +Additionally, if you use `ASSIGN_SATISFYING` with `DeepState_InRange` the +search may result in a value that is not in the range! You can +repeat your range restriction in the predicate if you want to avoid +this problem, but an easier way around it is to use +`ASSIGN_SATISFYING_IN_RANGE`, which works just as `ASSIGN_SATISFYING` +does, except +that it also takes a range, after the generation expression, e.g.: + +``` +int x = DeepState_IntInRange(-10, 10); +int y; +ASSIGN_SATISFYING_IN_RANGE(y, DeepState_IntInRange(-10, 10), -10, 10, y >= x); +``` + +Now `x` and `y` will both be guaranteed to fall in the range -10 to +10, inclusive. Notice that if you pass in an initial value that violates the range +constraint, this will just cause an assumption failure, so you want to include the +range in the assignment, also. + +## Postconditions - checks + +Once symbolic variables are declared, constrained, +and used in functions that are tested, you may want +to assert something about the results of testing, as in normal unit +tests. To do that use either the +`ASSERT_*` or `CHECK_*` family of macros. + +Macros from the first family will stop execution of the test if +the assertion is false. Macros from the second set will +mark the test as failed, but allow the test to continue. + +Fuzzers will treat false `ASSERT`/`CHECK` as crashes +if the `--abort_on_fail` option is set (which is by default +when using most executors). + +DeepState provides the following postcondition asserts: +* ASSERT_EQ +* ASSERT_NE +* ASSERT_LT +* ASSERT_LE +* ASSERT_GT +* ASSERT_GE +* ASSERT_TRUE +* ASSERT_FALSE + +and checks: + +* CHECK_EQ +* CHECK_NE +* CHECK_LT +* CHECK_LE +* CHECK_GT +* CHECK_GE +* CHECK_TRUE +* CHECK_FALSE + + +## Logs + +Printing debug information is easy, and you can use standard `printf`-like +functions. These are reimplemented by DeepState, so they won't +introduce space state explosion (see +[the paper](https://www.trailofbits.com/reports/deepstate-bar18.pdf)). You +may also use `LOG` macros for streaming output to various logging +levels (`printf` defaults to `TRACE` level). Setting `--min_log_level` +lets you control how much of this output DeepState shows when +replaying tests, or fuzzing. + +```c +LOG(INFO) << "Hello " << name; +``` + +Log levels: +* DEBUG +* TRACE +* INFO +* WARNING +* WARN +* ERROR +* FATAL +* CRITICAL diff --git a/examples/BoringDisabled.cpp b/examples/BoringDisabled.cpp index 2b27a15b..51538b70 100644 --- a/examples/BoringDisabled.cpp +++ b/examples/BoringDisabled.cpp @@ -1,66 +1,66 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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 - -using namespace deepstate; - - -/* simple check performed only to see if certain chars match */ -DEEPSTATE_INLINE int check_pass(const char * pass, size_t len) { - - // length should be 11 - if (len != 11) - return 1; - - // third char should be equal to `0` - uint8_t ch0 = (uint8_t) pass[2]; - if (ch0 != 48) - return 1; - - // ninth char should be equal to `o` - uint8_t ch1 = (uint8_t) pass[8]; - if (ch1 != 111) - return 1; - - return 0; -} - - -/* This test doesn't rely on any input methods from DeepState, so we prepend Boring* - to signify that it is a concrete test that does not rely on any fuzzing/symex */ -TEST(CharTest, BoringVerifyCheck) { - const char *in_pass = "sh0uld_work"; - ASSERT(check_pass(in_pass, strlen(in_pass)) == 0) - << "password check failed, which SHOULDN'T happen here."; -} - - -/* This test clearly doesn't work, so we prepend `Disabled*` to the name such that - during a run, it doesn't get called unless explicitly specified with `--run_disabled` */ -TEST(CharTest, DisabledVerifyCheck) { - const char *in_pass = "DOESNT_WORK_AT_ALL"; - ASSERT(check_pass(in_pass, strlen(in_pass)) == 0) - << "password check failed, as it SHOULD be failing."; -} - - -/* Regular test that executes during every run */ -TEST(CharTest, VerifyCheck) { - char *in_pass = DeepState_CStr_C(11, 0); - ASSERT(check_pass(in_pass, 11) == 0) - << "password check failed."; -} +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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 + +using namespace deepstate; + + +/* simple check performed only to see if certain chars match */ +DEEPSTATE_INLINE int check_pass(const char * pass, size_t len) { + + // length should be 11 + if (len != 11) + return 1; + + // third char should be equal to `0` + uint8_t ch0 = (uint8_t) pass[2]; + if (ch0 != 48) + return 1; + + // ninth char should be equal to `o` + uint8_t ch1 = (uint8_t) pass[8]; + if (ch1 != 111) + return 1; + + return 0; +} + + +/* This test doesn't rely on any input methods from DeepState, so we prepend Boring* + to signify that it is a concrete test that does not rely on any fuzzing/symex */ +TEST(CharTest, BoringVerifyCheck) { + const char *in_pass = "sh0uld_work"; + ASSERT(check_pass(in_pass, strlen(in_pass)) == 0) + << "password check failed, which SHOULDN'T happen here."; +} + + +/* This test clearly doesn't work, so we prepend `Disabled*` to the name such that + during a run, it doesn't get called unless explicitly specified with `--run_disabled` */ +TEST(CharTest, DisabledVerifyCheck) { + const char *in_pass = "DOESNT_WORK_AT_ALL"; + ASSERT(check_pass(in_pass, strlen(in_pass)) == 0) + << "password check failed, as it SHOULD be failing."; +} + + +/* Regular test that executes during every run */ +TEST(CharTest, VerifyCheck) { + char *in_pass = DeepState_CStr_C(11, 0); + ASSERT(check_pass(in_pass, 11) == 0) + << "password check failed."; +} diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index a8bca149..cec26d00 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,72 +1,72 @@ -# Copyright (c) 2019 Trail of Bits, Inc. -# -# 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. - -set(_not_for_fuzzing_files TakeOver Squares Klee) -file(GLOB files "*.cpp") - -foreach(file ${files}) - get_filename_component(file_no_ext ${file} NAME_WE) # should be NAME_WLE if newer cmake - - # skip some files that will be added manually later on - list (FIND _not_for_fuzzing_files ${file_no_ext} _index) - if (${_index} GREATER -1) - continue() - endif() - - add_executable(${file_no_ext} ${file}) - target_link_libraries(${file_no_ext} deepstate) - - if (DEEPSTATE_LIBFUZZER) - add_executable("${file_no_ext}_LF" "${file}") - target_link_libraries("${file_no_ext}_LF" deepstate_LF) - target_link_libraries("${file_no_ext}_LF" "-fsanitize=fuzzer,undefined") - set_target_properties("${file_no_ext}_LF" PROPERTIES COMPILE_DEFINITIONS "LIBFUZZER") - endif() - - if (DEEPSTATE_AFL) - add_executable("${file_no_ext}_AFL" "${file}") - target_link_libraries("${file_no_ext}_AFL" deepstate_AFL) - endif() - - if (DEEPSTATE_HONGGFUZZ) - add_executable("${file_no_ext}_HFUZZ" "${file}") - target_link_libraries("${file_no_ext}_HFUZZ" deepstate_HFUZZ) - endif() - - if (DEEPSTATE_ANGORA) - set(ANGORA_TAINT_RULE_LIST "./deepstate_abilist.txt") - - if(DEFINED ENV{USE_TRACK}) - add_executable("${file_no_ext}_angora_taint" "${file}") - target_link_libraries("${file_no_ext}_angora_taint" deepstate_taint) - else() - add_executable("${file_no_ext}_angora_fast" "${file}") - target_link_libraries("${file_no_ext}_angora_fast" deepstate_fast) - endif() - endif() -endforeach() - -if (NOT (DEEPSTATE_LIBFUZZER OR DEEPSTATE_AFL OR DEEPSTATE_HONGGFUZZ OR DEEPSTATE_ANGORA)) - if (NOT APPLE) - add_executable(Squares Squares.c) - target_link_libraries(Squares deepstate) - set_target_properties(Squares PROPERTIES COMPILE_DEFINITIONS "DEEPSTATE_TEST") - endif() - - add_executable(TakeOver TakeOver.cpp) - target_link_libraries(TakeOver deepstate) - - add_executable(Klee Klee.c) - target_link_libraries(Klee deepstate) +# Copyright (c) 2019 Trail of Bits, Inc. +# +# 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. + +set(_not_for_fuzzing_files TakeOver Squares Klee) +file(GLOB files "*.cpp") + +foreach(file ${files}) + get_filename_component(file_no_ext ${file} NAME_WE) # should be NAME_WLE if newer cmake + + # skip some files that will be added manually later on + list (FIND _not_for_fuzzing_files ${file_no_ext} _index) + if (${_index} GREATER -1) + continue() + endif() + + add_executable(${file_no_ext} ${file}) + target_link_libraries(${file_no_ext} deepstate) + + if (DEEPSTATE_LIBFUZZER) + add_executable("${file_no_ext}_LF" "${file}") + target_link_libraries("${file_no_ext}_LF" deepstate_LF) + target_link_libraries("${file_no_ext}_LF" "-fsanitize=fuzzer,undefined") + set_target_properties("${file_no_ext}_LF" PROPERTIES COMPILE_DEFINITIONS "LIBFUZZER") + endif() + + if (DEEPSTATE_AFL) + add_executable("${file_no_ext}_AFL" "${file}") + target_link_libraries("${file_no_ext}_AFL" deepstate_AFL) + endif() + + if (DEEPSTATE_HONGGFUZZ) + add_executable("${file_no_ext}_HFUZZ" "${file}") + target_link_libraries("${file_no_ext}_HFUZZ" deepstate_HFUZZ) + endif() + + if (DEEPSTATE_ANGORA) + set(ANGORA_TAINT_RULE_LIST "./deepstate_abilist.txt") + + if(DEFINED ENV{USE_TRACK}) + add_executable("${file_no_ext}_angora_taint" "${file}") + target_link_libraries("${file_no_ext}_angora_taint" deepstate_taint) + else() + add_executable("${file_no_ext}_angora_fast" "${file}") + target_link_libraries("${file_no_ext}_angora_fast" deepstate_fast) + endif() + endif() +endforeach() + +if (NOT (DEEPSTATE_LIBFUZZER OR DEEPSTATE_AFL OR DEEPSTATE_HONGGFUZZ OR DEEPSTATE_ANGORA)) + if (NOT APPLE) + add_executable(Squares Squares.c) + target_link_libraries(Squares deepstate) + set_target_properties(Squares PROPERTIES COMPILE_DEFINITIONS "DEEPSTATE_TEST") + endif() + + add_executable(TakeOver TakeOver.cpp) + target_link_libraries(TakeOver deepstate) + + add_executable(Klee Klee.c) + target_link_libraries(Klee deepstate) endif() \ No newline at end of file diff --git a/examples/Crash.cpp b/examples/Crash.cpp index ca63e697..20865652 100644 --- a/examples/Crash.cpp +++ b/examples/Crash.cpp @@ -1,37 +1,37 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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 - -using namespace deepstate; - -DEEPSTATE_NOINLINE static unsigned segfault(unsigned x) { - if (x == 0x1234) { // Magic number for engine to discover - unsigned *p = NULL; - *(p+1) = 0xdeadbeef; // Trigger segfault here - } - - return x; -} - -TEST(Crash, SegFault) { - symbolic_unsigned x; - - segfault(x); - - ASSERT_EQ(x, x); -} +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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 + +using namespace deepstate; + +DEEPSTATE_NOINLINE static unsigned segfault(unsigned x) { + if (x == 0x1234) { // Magic number for engine to discover + unsigned *p = NULL; + *(p+1) = 0xdeadbeef; // Trigger segfault here + } + + return x; +} + +TEST(Crash, SegFault) { + symbolic_unsigned x; + + segfault(x); + + ASSERT_EQ(x, x); +} diff --git a/examples/EnsembledCrash.cpp b/examples/EnsembledCrash.cpp index 4b78a561..1728fc34 100644 --- a/examples/EnsembledCrash.cpp +++ b/examples/EnsembledCrash.cpp @@ -1,48 +1,48 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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 - -using namespace deepstate; - -DEEPSTATE_NOINLINE static void segfault(char *first, char* second) { - std::size_t hashed = std::hash{}(first); - std::size_t hashed2 = std::hash{}(second); - unsigned *p = NULL; - if (hashed == 7169420828666634849U) { - if (hashed2 == 10753164746288518855U) { - *(p+2) = 0xdeadbeef; /* crash */ - } - printf("BOM\n"); - } -} - -TEST(SimpleCrash, SegFault) { - char *first = (char*)DeepState_CStr_C(9, 0); - char *second = (char*)DeepState_CStr_C(9, 0); - - for (int i = 0; i < 9; ++i) - printf("%02x", (unsigned char)first[i]); - printf("\n"); - for (int i = 0; i < 9; ++i) - printf("%02x", (unsigned char)second[i]); - - segfault(first, second); - - ASSERT_EQ(first, first); - ASSERT_NE(first, second); -} +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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 + +using namespace deepstate; + +DEEPSTATE_NOINLINE static void segfault(char *first, char* second) { + std::size_t hashed = std::hash{}(first); + std::size_t hashed2 = std::hash{}(second); + unsigned *p = NULL; + if (hashed == 7169420828666634849U) { + if (hashed2 == 10753164746288518855U) { + *(p+2) = 0xdeadbeef; /* crash */ + } + printf("BOM\n"); + } +} + +TEST(SimpleCrash, SegFault) { + char *first = (char*)DeepState_CStr_C(9, 0); + char *second = (char*)DeepState_CStr_C(9, 0); + + for (int i = 0; i < 9; ++i) + printf("%02x", (unsigned char)first[i]); + printf("\n"); + for (int i = 0; i < 9; ++i) + printf("%02x", (unsigned char)second[i]); + + segfault(first, second); + + ASSERT_EQ(first, first); + ASSERT_NE(first, second); +} diff --git a/examples/Euler.cpp b/examples/Euler.cpp index 4d0d2ccc..02500090 100644 --- a/examples/Euler.cpp +++ b/examples/Euler.cpp @@ -1,41 +1,41 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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 - -using namespace deepstate; - -static unsigned pow5(unsigned v) { - return v * v * v * v * v; -} - -TEST(Euler, SumsOfLikePowers) { - symbolic_unsigned a, b, c, d, e; - ASSERT_GT(a, 1); - ASSERT_GT(b, 1); - ASSERT_GT(c, 1); - ASSERT_GT(d, 1); - ASSERT_GT(e, 1); - ASSERT_NE(a, b); ASSERT_NE(a, c); ASSERT_NE(a, d); ASSERT_NE(a, e); - ASSERT_NE(b, c); ASSERT_NE(b, d); ASSERT_NE(b, e); - ASSERT_NE(c, d); ASSERT_NE(c, e); - ASSERT_NE(d, e); - ASSERT_NE(pow5(a) + pow5(b) + pow5(c) + pow5(d), pow5(e)) - << a << "^5 + " << b << "^5" << " + " << c - << "^5 + " << d << "^5 = " << e << "^5"; -} - +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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 + +using namespace deepstate; + +static unsigned pow5(unsigned v) { + return v * v * v * v * v; +} + +TEST(Euler, SumsOfLikePowers) { + symbolic_unsigned a, b, c, d, e; + ASSERT_GT(a, 1); + ASSERT_GT(b, 1); + ASSERT_GT(c, 1); + ASSERT_GT(d, 1); + ASSERT_GT(e, 1); + ASSERT_NE(a, b); ASSERT_NE(a, c); ASSERT_NE(a, d); ASSERT_NE(a, e); + ASSERT_NE(b, c); ASSERT_NE(b, d); ASSERT_NE(b, e); + ASSERT_NE(c, d); ASSERT_NE(c, e); + ASSERT_NE(d, e); + ASSERT_NE(pow5(a) + pow5(b) + pow5(c) + pow5(d), pow5(e)) + << a << "^5 + " << b << "^5" << " + " << c + << "^5 + " << d << "^5 = " << e << "^5"; +} + diff --git a/examples/Fixture.cpp b/examples/Fixture.cpp index e3272609..ad68d3a1 100644 --- a/examples/Fixture.cpp +++ b/examples/Fixture.cpp @@ -1,37 +1,37 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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 - -class MyTest : public deepstate::Test { - public: - - void SetUp(void) { - LOG(TRACE) - << "Setting up!"; - } - - void TearDown(void) { - LOG(TRACE) - << "Tearing down!"; - } - - deepstate::Symbolic x; -}; - -TEST_F(MyTest, Something) { - ASSUME_NE(x, 0); -} +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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 + +class MyTest : public deepstate::Test { + public: + + void SetUp(void) { + LOG(TRACE) + << "Setting up!"; + } + + void TearDown(void) { + LOG(TRACE) + << "Tearing down!"; + } + + deepstate::Symbolic x; +}; + +TEST_F(MyTest, Something) { + ASSUME_NE(x, 0); +} diff --git a/examples/FromEclipser.cpp b/examples/FromEclipser.cpp index 0f26ad1c..833db0e0 100644 --- a/examples/FromEclipser.cpp +++ b/examples/FromEclipser.cpp @@ -1,19 +1,19 @@ -#include - -using namespace deepstate; - -#include - -int vulnfunc(int32_t intInput, char * strInput) { - if (2 * intInput + 1 == 31337) - if (strcmp(strInput, "Bad!") == 0) - assert(0); - return 0; -} - -TEST(FromEclipser, CrashIt) { - char *buf = (char*)DeepState_Malloc(9); - buf[8] = 0; - vulnfunc(*((int32_t*) &buf[0]), &buf[4]); - free(buf); -} +#include + +using namespace deepstate; + +#include + +int vulnfunc(int32_t intInput, char * strInput) { + if (2 * intInput + 1 == 31337) + if (strcmp(strInput, "Bad!") == 0) + assert(0); + return 0; +} + +TEST(FromEclipser, CrashIt) { + char *buf = (char*)DeepState_Malloc(9); + buf[8] = 0; + vulnfunc(*((int32_t*) &buf[0]), &buf[4]); + free(buf); +} diff --git a/examples/InputPath.cpp b/examples/InputPath.cpp index 349d2aec..a18156fb 100644 --- a/examples/InputPath.cpp +++ b/examples/InputPath.cpp @@ -1,79 +1,79 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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 -#include -#include -#include - -using namespace deepstate; - - -/* Represents a simple pedagogical database-like structure */ -typedef struct Datastore { - int id; - char *data; -} Datastore; - - -void write_file(const char *path, Datastore& obj) { - std::ofstream out; - out.open(path, std::ios::binary); - out.write(reinterpret_cast(&obj), sizeof(obj)); - out.close(); -} - - -/* Represents our higher-level parser function being tested, which in this case takes a path rather - * than a primitive type, ie a char ptr, and deserializes it into a proper data structure. */ -Datastore parse_file(const char * path) { - Datastore obj; - std::ifstream read_file; - read_file.open(path, std::ios::binary); - read_file.read(reinterpret_cast(&obj), sizeof(obj)); - read_file.close(); - return obj; -} - - -/* A hacky concrete test that writes an instantiated data structure to the specified file. - * Run this test in order to generate an input seed for analyzing InputPath_Deserialize */ -TEST(InputPath, Serialize) { - - const char *path = DeepState_InputPath(NULL); - LOG(INFO) << "Input path to write to: " << path; - - Datastore obj; - - obj.id = 0; - obj.data = (char *) "Admin"; - - LOG(INFO) << "Serializing and writing to path"; - write_file(path, obj); -} - - -/* Actual test to analyze. Be sure to fuzz this test rather than a symex engine. */ -TEST(InputPath, Deserialize) { - - const char *path = DeepState_InputPath(NULL); - LOG(INFO) << "Input path to read from: " << path; - - Datastore store = parse_file(path); - - ASSERT(store.id == 0) << "initial user does not have a 0 id"; - ASSERT(strcmp("Admin", store.data) != 0) << "cannot have admin username"; -} +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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 +#include +#include +#include + +using namespace deepstate; + + +/* Represents a simple pedagogical database-like structure */ +typedef struct Datastore { + int id; + char *data; +} Datastore; + + +void write_file(const char *path, Datastore& obj) { + std::ofstream out; + out.open(path, std::ios::binary); + out.write(reinterpret_cast(&obj), sizeof(obj)); + out.close(); +} + + +/* Represents our higher-level parser function being tested, which in this case takes a path rather + * than a primitive type, ie a char ptr, and deserializes it into a proper data structure. */ +Datastore parse_file(const char * path) { + Datastore obj; + std::ifstream read_file; + read_file.open(path, std::ios::binary); + read_file.read(reinterpret_cast(&obj), sizeof(obj)); + read_file.close(); + return obj; +} + + +/* A hacky concrete test that writes an instantiated data structure to the specified file. + * Run this test in order to generate an input seed for analyzing InputPath_Deserialize */ +TEST(InputPath, Serialize) { + + const char *path = DeepState_InputPath(NULL); + LOG(INFO) << "Input path to write to: " << path; + + Datastore obj; + + obj.id = 0; + obj.data = (char *) "Admin"; + + LOG(INFO) << "Serializing and writing to path"; + write_file(path, obj); +} + + +/* Actual test to analyze. Be sure to fuzz this test rather than a symex engine. */ +TEST(InputPath, Deserialize) { + + const char *path = DeepState_InputPath(NULL); + LOG(INFO) << "Input path to read from: " << path; + + Datastore store = parse_file(path); + + ASSERT(store.id == 0) << "initial user does not have a 0 id"; + ASSERT(strcmp("Admin", store.data) != 0) << "cannot have admin username"; +} diff --git a/examples/IntegerArithmetic.cpp b/examples/IntegerArithmetic.cpp index 1276429e..28221ed1 100644 --- a/examples/IntegerArithmetic.cpp +++ b/examples/IntegerArithmetic.cpp @@ -1,46 +1,46 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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 - -using namespace deepstate; - -DEEPSTATE_NOINLINE int add(int x, int y) { - return x + y; -} - -TEST(Arithmetic, AdditionIsCommutative) { - ForAll([] (int x, int y) { - ASSERT_EQ(add(x, y), add(y, x)) - << "Addition of signed integers must commute."; - }); -} - -TEST(Arithmetic, AdditionIsAssociative) { - ForAll([] (int x, int y, int z) { - ASSERT_EQ(add(x, add(y, z)), add(add(x, y), z)) - << "Addition of signed integers must associate."; - }); -} - -TEST(Arithmetic, InvertibleMultiplication_CanFail) { - ForAll([] (int x, int y) { - ASSUME_NE(y, 0) - << "Assumed non-zero value for y: " << y; - ASSERT_EQ(x, (x / y) * y) - << x << " != (" << x << " / " << y << ") * " << y; - }); -} +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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 + +using namespace deepstate; + +DEEPSTATE_NOINLINE int add(int x, int y) { + return x + y; +} + +TEST(Arithmetic, AdditionIsCommutative) { + ForAll([] (int x, int y) { + ASSERT_EQ(add(x, y), add(y, x)) + << "Addition of signed integers must commute."; + }); +} + +TEST(Arithmetic, AdditionIsAssociative) { + ForAll([] (int x, int y, int z) { + ASSERT_EQ(add(x, add(y, z)), add(add(x, y), z)) + << "Addition of signed integers must associate."; + }); +} + +TEST(Arithmetic, InvertibleMultiplication_CanFail) { + ForAll([] (int x, int y) { + ASSUME_NE(y, 0) + << "Assumed non-zero value for y: " << y; + ASSERT_EQ(x, (x / y) * y) + << x << " != (" << x << " / " << y << ") * " << y; + }); +} diff --git a/examples/IntegerOverflow.cpp b/examples/IntegerOverflow.cpp index 17942bae..8a42cf69 100644 --- a/examples/IntegerOverflow.cpp +++ b/examples/IntegerOverflow.cpp @@ -1,42 +1,42 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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 -#include -using namespace deepstate; - -DEEPSTATE_NOINLINE int ident1(int x) { - return x; -} - -DEEPSTATE_NOINLINE int ident2(int x) { - return x; -} - -TEST(SignedInteger, AdditionOverflow) { - Symbolic x; - int y = ident1(x) + ident2(x); // Can overflow! - ASSERT_GE(y, 0) - << "Found y=" << y << " was not always positive."; -} - -TEST(SignedInteger, MultiplicationOverflow) { - Symbolic x; - int y = ident1(x) * ident2(x); // Can overflow! - ASSERT_GE(y, 0) - << x << " squared overflowed."; -} - +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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 +#include +using namespace deepstate; + +DEEPSTATE_NOINLINE int ident1(int x) { + return x; +} + +DEEPSTATE_NOINLINE int ident2(int x) { + return x; +} + +TEST(SignedInteger, AdditionOverflow) { + Symbolic x; + int y = ident1(x) + ident2(x); // Can overflow! + ASSERT_GE(y, 0) + << "Found y=" << y << " was not always positive."; +} + +TEST(SignedInteger, MultiplicationOverflow) { + Symbolic x; + int y = ident1(x) * ident2(x); // Can overflow! + ASSERT_GE(y, 0) + << x << " squared overflowed."; +} + diff --git a/examples/Klee.c b/examples/Klee.c index 6f8b4550..028624f2 100644 --- a/examples/Klee.c +++ b/examples/Klee.c @@ -1,39 +1,39 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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 - -DEEPSTATE_NOINLINE int get_sign(int x) { - if (x == 0) { - printf("zero\n"); - return 0; - } - - if (x < 0) { - printf("negative\n"); - return -1; - } else { - printf("positive\n"); - return 1; - } -} - -int main(int argc, char *argv[]) { - int a; - klee_make_symbolic(&a, sizeof(a), "a"); - - return get_sign(a); -} +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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 + +DEEPSTATE_NOINLINE int get_sign(int x) { + if (x == 0) { + printf("zero\n"); + return 0; + } + + if (x < 0) { + printf("negative\n"); + return -1; + } else { + printf("positive\n"); + return 1; + } +} + +int main(int argc, char *argv[]) { + int a; + klee_make_symbolic(&a, sizeof(a), "a"); + + return get_sign(a); +} diff --git a/examples/Lists.cpp b/examples/Lists.cpp index 23f03444..0aba4a77 100644 --- a/examples/Lists.cpp +++ b/examples/Lists.cpp @@ -1,32 +1,32 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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 - -#include -#include - -using namespace deepstate; - -TEST(Vector, DoubleReversal) { - ForAll>([] (const std::vector &vec1) { - std::vector vec2 = vec1; - std::reverse(vec2.begin(), vec2.end()); - std::reverse(vec2.begin(), vec2.end()); - ASSERT_EQ(vec1, vec2) - << "Double reverse of vectors must be equal."; - }); -} +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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 + +#include +#include + +using namespace deepstate; + +TEST(Vector, DoubleReversal) { + ForAll>([] (const std::vector &vec1) { + std::vector vec2 = vec1; + std::reverse(vec2.begin(), vec2.end()); + std::reverse(vec2.begin(), vec2.end()); + ASSERT_EQ(vec1, vec2) + << "Double reverse of vectors must be equal."; + }); +} diff --git a/examples/OneOf.cpp b/examples/OneOf.cpp index 2a7f8aaf..6a1f5db8 100644 --- a/examples/OneOf.cpp +++ b/examples/OneOf.cpp @@ -1,65 +1,65 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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 - -using namespace deepstate; - -#define LENGTH 3 - -TEST(OneOfExample, ProduceSixtyOrHigher) { - symbolic_int start; - ASSUME_LT(start, 5); - int x = start; - - char choices[LENGTH + 1] = {}; - - // Add this back in and uncomment out choices parts below, add - // & choices, N to captures, and it becomes quite difficult. - - for (int n = 0; n < LENGTH; n++) { - OneOf( - [&] { - x += 1; - printf("+=1\n"); - choices[n] = '+'; - }, - [&] { - x -= 1; - printf("-=1\n"); - choices[n] = '-'; - }, - [&] { - x *= 2; - printf("*2\n"); - choices[n] = '2'; - }, - [&] { - x += 10; - printf("+=10\n"); - choices[n] = 'x'; - }, - [&] { - x = 0; - printf("=0\n"); - choices[n] = '0'; - }); - - //choices[N+1] = 0; - ASSERT_LE(x, 60) - << x << " is >= 60: " << " did " << choices << " from " << start; - } -} +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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 + +using namespace deepstate; + +#define LENGTH 3 + +TEST(OneOfExample, ProduceSixtyOrHigher) { + symbolic_int start; + ASSUME_LT(start, 5); + int x = start; + + char choices[LENGTH + 1] = {}; + + // Add this back in and uncomment out choices parts below, add + // & choices, N to captures, and it becomes quite difficult. + + for (int n = 0; n < LENGTH; n++) { + OneOf( + [&] { + x += 1; + printf("+=1\n"); + choices[n] = '+'; + }, + [&] { + x -= 1; + printf("-=1\n"); + choices[n] = '-'; + }, + [&] { + x *= 2; + printf("*2\n"); + choices[n] = '2'; + }, + [&] { + x += 10; + printf("+=10\n"); + choices[n] = 'x'; + }, + [&] { + x = 0; + printf("=0\n"); + choices[n] = '0'; + }); + + //choices[N+1] = 0; + ASSERT_LE(x, 60) + << x << " is >= 60: " << " did " << choices << " from " << start; + } +} diff --git a/examples/OneOfP.cpp b/examples/OneOfP.cpp index d4f0c6cf..597a8ae5 100644 --- a/examples/OneOfP.cpp +++ b/examples/OneOfP.cpp @@ -1,39 +1,39 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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 - -using namespace deepstate; - -TEST(WithProbs, WP) { - char out[12]; - out[11] = '\0'; - for (int i = 0; i < 10; i++) { - OneOfP( - 0.1, [&]{out[i] = 'a';}, - // a probability of -1 indicates "just use even dist over non-specified" - -1, [&]{out[i] = 'b';}, - -1, [&]{out[i] = 'c';}, - 0.6, [&]{out[i] = 'd';}); - } - char a[3] = {'x', 'y', 'z'}; - out[10] = OneOfP({0.1, 0.1}, a); - std::vector pos = {0, 1, 2}; - int p = OneOfP({-1, -1, 0.9}, pos); - out[p] = '!'; - LOG(TRACE) << "RESULT: '" << out << "'"; -} +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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 + +using namespace deepstate; + +TEST(WithProbs, WP) { + char out[12]; + out[11] = '\0'; + for (int i = 0; i < 10; i++) { + OneOfP( + 0.1, [&]{out[i] = 'a';}, + // a probability of -1 indicates "just use even dist over non-specified" + -1, [&]{out[i] = 'b';}, + -1, [&]{out[i] = 'c';}, + 0.6, [&]{out[i] = 'd';}); + } + char a[3] = {'x', 'y', 'z'}; + out[10] = OneOfP({0.1, 0.1}, a); + std::vector pos = {0, 1, 2}; + int p = OneOfP({-1, -1, 0.9}, pos); + out[p] = '!'; + LOG(TRACE) << "RESULT: '" << out << "'"; +} diff --git a/examples/Primes.cpp b/examples/Primes.cpp index f53e4bf2..dad996ae 100644 --- a/examples/Primes.cpp +++ b/examples/Primes.cpp @@ -1,54 +1,54 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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 - -using namespace deepstate; - -bool IsPrime(const unsigned p) { - for (unsigned i = 2; i <= (p/2); ++i) { - if (!(p % i)) { - return i; - } - } - return true; -} - -TEST(PrimePolynomial, OnlyGeneratesPrimes) { - symbolic_unsigned x, y, z; - ASSUME_GT(x, 0); - unsigned poly = (x * x) + x + 41; - ASSUME_GT(y, 1); - ASSUME_GT(z, 1); - ASSUME_LT(y, poly); - ASSUME_LT(z, poly); - ASSERT(poly != y * z) - << x << "^2 + " << x << " + 41 is not prime"; - ASSERT(IsPrime(Pump(poly))) - << x << "^2 + " << x << " + 41 is not prime"; -} - -TEST(PrimePolynomial, OnlyGeneratesPrimes_NoStreaming) { - symbolic_unsigned x, y, z; - DeepState_Assume(x > 0); - unsigned poly = (x * x) + x + 41; - DeepState_Assume(y > 1); - DeepState_Assume(z > 1); - DeepState_Assume(y < poly); - DeepState_Assume(z < poly); - DeepState_Assert(poly != (y * z)); - DeepState_Assert(IsPrime(Pump(poly))); -} +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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 + +using namespace deepstate; + +bool IsPrime(const unsigned p) { + for (unsigned i = 2; i <= (p/2); ++i) { + if (!(p % i)) { + return i; + } + } + return true; +} + +TEST(PrimePolynomial, OnlyGeneratesPrimes) { + symbolic_unsigned x, y, z; + ASSUME_GT(x, 0); + unsigned poly = (x * x) + x + 41; + ASSUME_GT(y, 1); + ASSUME_GT(z, 1); + ASSUME_LT(y, poly); + ASSUME_LT(z, poly); + ASSERT(poly != y * z) + << x << "^2 + " << x << " + 41 is not prime"; + ASSERT(IsPrime(Pump(poly))) + << x << "^2 + " << x << " + 41 is not prime"; +} + +TEST(PrimePolynomial, OnlyGeneratesPrimes_NoStreaming) { + symbolic_unsigned x, y, z; + DeepState_Assume(x > 0); + unsigned poly = (x * x) + x + 41; + DeepState_Assume(y > 1); + DeepState_Assume(z > 1); + DeepState_Assume(y < poly); + DeepState_Assume(z < poly); + DeepState_Assert(poly != (y * z)); + DeepState_Assert(IsPrime(Pump(poly))); +} diff --git a/examples/Runlen.cpp b/examples/Runlen.cpp index b1d8332c..7cf2ccd9 100644 --- a/examples/Runlen.cpp +++ b/examples/Runlen.cpp @@ -1,63 +1,63 @@ -#include - -using namespace deepstate; - -/* Simple, buggy, run-length encoding that creates "human readable" - * encodings by adding 'A'-1 to the count, and splitting at 26. - * e.g., encode("aaabbbbbc") = "aCbEcA" since C=3 and E=5 */ - -char* encode(const char* input) { - unsigned int len = strlen(input); - char* encoded = (char*)malloc((len*2)+1); - int pos = 0; - if (len > 0) { - unsigned char last = input[0]; - int count = 1; - for (int i = 1; i < len; i++) { - if (((unsigned char)input[i] == last) && (count < 26)) - count++; - else { - encoded[pos++] = last; - encoded[pos++] = 64 + count; - last = (unsigned char)input[i]; - count = 1; - } - } - encoded[pos++] = last; - encoded[pos++] = 65; // Should be 64 + count - } - encoded[pos] = '\0'; - return encoded; -} - -char* decode(const char* output) { - unsigned int len = strlen(output); - char* decoded = (char*)malloc((len/2)*26); - int pos = 0; - for (int i = 0; i < len; i += 2) { - for (int j = 0; j < (output[i+1] - 64); j++) { - decoded[pos++] = output[i]; - } - } - decoded[pos] = '\0'; - return decoded; -} - -// Can be (much) higher (e.g., > 1024) if we're using fuzzing, not symbolic execution -#define MAX_STR_LEN 6 - -TEST(Runlength, BoringUnitTest) { - ASSERT_EQ(strcmp(encode(""), ""), 0); - ASSERT_EQ(strcmp(encode("a"), "aA"), 0); - ASSERT_EQ(strcmp(encode("aaabbbbbc"), "aCbEcA"), 0); -} - -TEST(Runlength, EncodeDecode) { - char* original = DeepState_CStrUpToLen(MAX_STR_LEN, "abcdef0123456789"); - char* encoded = encode(original); - ASSERT_LE(strlen(encoded), strlen(original)*2) << "Encoding is > length*2!"; - char* roundtrip = decode(encoded); - ASSERT_EQ(strncmp(roundtrip, original, MAX_STR_LEN), 0) << - "ORIGINAL: '" << original << "', ENCODED: '" << encoded << - "', ROUNDTRIP: '" << roundtrip << "'"; -} +#include + +using namespace deepstate; + +/* Simple, buggy, run-length encoding that creates "human readable" + * encodings by adding 'A'-1 to the count, and splitting at 26. + * e.g., encode("aaabbbbbc") = "aCbEcA" since C=3 and E=5 */ + +char* encode(const char* input) { + unsigned int len = strlen(input); + char* encoded = (char*)malloc((len*2)+1); + int pos = 0; + if (len > 0) { + unsigned char last = input[0]; + int count = 1; + for (int i = 1; i < len; i++) { + if (((unsigned char)input[i] == last) && (count < 26)) + count++; + else { + encoded[pos++] = last; + encoded[pos++] = 64 + count; + last = (unsigned char)input[i]; + count = 1; + } + } + encoded[pos++] = last; + encoded[pos++] = 65; // Should be 64 + count + } + encoded[pos] = '\0'; + return encoded; +} + +char* decode(const char* output) { + unsigned int len = strlen(output); + char* decoded = (char*)malloc((len/2)*26); + int pos = 0; + for (int i = 0; i < len; i += 2) { + for (int j = 0; j < (output[i+1] - 64); j++) { + decoded[pos++] = output[i]; + } + } + decoded[pos] = '\0'; + return decoded; +} + +// Can be (much) higher (e.g., > 1024) if we're using fuzzing, not symbolic execution +#define MAX_STR_LEN 6 + +TEST(Runlength, BoringUnitTest) { + ASSERT_EQ(strcmp(encode(""), ""), 0); + ASSERT_EQ(strcmp(encode("a"), "aA"), 0); + ASSERT_EQ(strcmp(encode("aaabbbbbc"), "aCbEcA"), 0); +} + +TEST(Runlength, EncodeDecode) { + char* original = DeepState_CStrUpToLen(MAX_STR_LEN, "abcdef0123456789"); + char* encoded = encode(original); + ASSERT_LE(strlen(encoded), strlen(original)*2) << "Encoding is > length*2!"; + char* roundtrip = decode(encoded); + ASSERT_EQ(strncmp(roundtrip, original, MAX_STR_LEN), 0) << + "ORIGINAL: '" << original << "', ENCODED: '" << encoded << + "', ROUNDTRIP: '" << roundtrip << "'"; +} diff --git a/examples/SimpleCrash.cpp b/examples/SimpleCrash.cpp index ac9e8169..3a4f7c83 100644 --- a/examples/SimpleCrash.cpp +++ b/examples/SimpleCrash.cpp @@ -1,38 +1,38 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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 - -using namespace deepstate; - -DEEPSTATE_NOINLINE static char* segfault(char *x) { - if (x[0] == '\x13') { - if (x[1] == '\x37') { - unsigned *p = NULL; - *(p+1) = 0xdeadbeef; // Trigger segfault here - } - } - return x; -} - -TEST(SimpleCrash, SegFault) { - char *x = DeepState_CStr_C(4, 0); - - segfault(x); - - ASSERT_EQ(x, x); -} +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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 + +using namespace deepstate; + +DEEPSTATE_NOINLINE static char* segfault(char *x) { + if (x[0] == '\x13') { + if (x[1] == '\x37') { + unsigned *p = NULL; + *(p+1) = 0xdeadbeef; // Trigger segfault here + } + } + return x; +} + +TEST(SimpleCrash, SegFault) { + char *x = DeepState_CStr_C(4, 0); + + segfault(x); + + ASSERT_EQ(x, x); +} diff --git a/examples/Squares.c b/examples/Squares.c index 54835123..af06cebf 100644 --- a/examples/Squares.c +++ b/examples/Squares.c @@ -1,42 +1,42 @@ -#include -#include - -int square(int x) { - return x*x; -} - -#ifdef DEEPSTATE_TEST -#include -DeepState_EntryPoint(test_main) { - const char *new_args[2]; - new_args[0] = "deepstate"; - new_args[1] = DeepState_CStr_C(8, 0); - - DeepState_Assert(0 == old_main(2, new_args)); -} - -int main(int argc, const char *argv[]) { - DeepState_InitOptions(argc, argv); - return 0 == DeepState_Run(); -} -// TODO(artem): yes this is awful but avoids another `ifdef`. -#define main old_main - -#endif - -int main(int argc, char *argv[]) { - if (argc != 2) { - printf("Usage: %s \n", argv[0]); - return -1; - } - int x = atoi(argv[1]); - int y = square(x); - - if (y + 4 == 29) { - printf("You found the secret number\n"); - return 0; - } else { - printf("Secret NOT found\n"); - return -1; - } -} +#include +#include + +int square(int x) { + return x*x; +} + +#ifdef DEEPSTATE_TEST +#include +DeepState_EntryPoint(test_main) { + const char *new_args[2]; + new_args[0] = "deepstate"; + new_args[1] = DeepState_CStr_C(8, 0); + + DeepState_Assert(0 == old_main(2, new_args)); +} + +int main(int argc, const char *argv[]) { + DeepState_InitOptions(argc, argv); + return 0 == DeepState_Run(); +} +// TODO(artem): yes this is awful but avoids another `ifdef`. +#define main old_main + +#endif + +int main(int argc, char *argv[]) { + if (argc != 2) { + printf("Usage: %s \n", argv[0]); + return -1; + } + int x = atoi(argv[1]); + int y = square(x); + + if (y + 4 == 29) { + printf("You found the secret number\n"); + return 0; + } else { + printf("Secret NOT found\n"); + return -1; + } +} diff --git a/examples/StreamingAndFormatting.cpp b/examples/StreamingAndFormatting.cpp index c127178b..bf9e5239 100644 --- a/examples/StreamingAndFormatting.cpp +++ b/examples/StreamingAndFormatting.cpp @@ -1,43 +1,43 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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 - -TEST(Streaming, BasicLevels) { - LOG(DEBUG) << "This is a debug message"; - LOG(TRACE) << "This is a trace message"; - LOG(INFO) << "This is an info message"; - LOG(WARNING) << "This is a warning message"; - LOG(ERROR) << "This is a error message"; - LOG(TRACE) << "This is a trace message again"; - ASSERT(true) << "This should not be printed."; -} - -TEST(Streaming, BasicTypes) { - LOG(TRACE) << 'a'; - LOG(TRACE) << 1; - LOG(TRACE) << 1.0; - LOG(TRACE) << "string"; - LOG(TRACE) << nullptr; -} - -TEST(Formatting, OverridePrintf) { - printf("hello string=%s hex_lower=%x hex_upper=%X octal=%o char=%c dec=%d" - "double=%f sci=%e SCI=%E pointer=%p", - "world", 999, 999, 999, 'a', 999, 999.0, 999.0, 999.0, "world"); - printf("hello again!"); -} - +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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 + +TEST(Streaming, BasicLevels) { + LOG(DEBUG) << "This is a debug message"; + LOG(TRACE) << "This is a trace message"; + LOG(INFO) << "This is an info message"; + LOG(WARNING) << "This is a warning message"; + LOG(ERROR) << "This is a error message"; + LOG(TRACE) << "This is a trace message again"; + ASSERT(true) << "This should not be printed."; +} + +TEST(Streaming, BasicTypes) { + LOG(TRACE) << 'a'; + LOG(TRACE) << 1; + LOG(TRACE) << 1.0; + LOG(TRACE) << "string"; + LOG(TRACE) << nullptr; +} + +TEST(Formatting, OverridePrintf) { + printf("hello string=%s hex_lower=%x hex_upper=%X octal=%o char=%c dec=%d" + "double=%f sci=%e SCI=%E pointer=%p", + "world", 999, 999, 999, 'a', 999, 999.0, 999.0, 999.0, "world"); + printf("hello again!"); +} + diff --git a/examples/TakeOver.cpp b/examples/TakeOver.cpp index 3338496a..693326b2 100644 --- a/examples/TakeOver.cpp +++ b/examples/TakeOver.cpp @@ -1,45 +1,45 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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 - -using namespace deepstate; - -DEEPSTATE_NOINLINE void func(uint32_t x) { - CHECK_LT(x, 0x1234) - << "Found x=" << x << " was not greater than 0x1234."; - - if (x < 0x1234) { - printf("hi\n"); - } else { - printf("bye\n"); - } -} - -int main(int argc, char *argv[]) { - DeepState_InitOptions(argc, argv); - - uint32_t x = 123; - func(x); // Unexplored - - DeepState_TakeOver(); - - Symbolic y; - Symbolic z; - func(y); // Explored - func(z); // Explored -} +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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 + +using namespace deepstate; + +DEEPSTATE_NOINLINE void func(uint32_t x) { + CHECK_LT(x, 0x1234) + << "Found x=" << x << " was not greater than 0x1234."; + + if (x < 0x1234) { + printf("hi\n"); + } else { + printf("bye\n"); + } +} + +int main(int argc, char *argv[]) { + DeepState_InitOptions(argc, argv); + + uint32_t x = 123; + func(x); // Unexplored + + DeepState_TakeOver(); + + Symbolic y; + Symbolic z; + func(y); // Explored + func(z); // Explored +} diff --git a/examples/TestInRange.cpp b/examples/TestInRange.cpp index 4f7bd461..828f616a 100644 --- a/examples/TestInRange.cpp +++ b/examples/TestInRange.cpp @@ -1,65 +1,65 @@ -#include -#include - -using namespace deepstate; -using namespace std; - -TEST(DeepState, RangeAPIs) { - int v, low, high, mv; - char cv, clow, chigh; - double dv, dlow, dhigh; - - low = DeepState_Int(); - high = DeepState_Int(); - if (low > high) { - int temp = low; - low = high; - high = temp; - } - v = DeepState_IntInRange(low, high); - LOG(TRACE) << "low = " << low << ", high = " << high << ", v = " << v; - ASSERT ((low <= v) && (v <= high)) << "low = " << low << ", high = " << high << ", v = " << v; - - clow = DeepState_Char(); - chigh = DeepState_Char(); - if (clow > chigh) { - char temp = clow; - clow = chigh; - chigh = temp; - } - cv = DeepState_CharInRange(clow, chigh); - LOG(TRACE) << "clow = " << clow << ", chigh = " << chigh << ", cv = " << cv; - ASSERT ((clow <= cv) && (cv <= chigh)) << "clow = " << clow << ", chigh = " << chigh << " cv = " << cv; - - mv = DeepState_IntInRange(2, 45); - ASSIGN_SATISFYING(v, DeepState_Int(), (v % mv) == 0); - ASSERT ((v % mv) == 0) << "mv = " << mv << ", v = " << v; - - dlow = DeepState_Double(); - assume(!isnan(dlow)); - dhigh = DeepState_Double(); - assume(!isnan(dhigh)); - if (dlow > dhigh) { - double temp = dlow; - dlow = dhigh; - dhigh = temp; - } - dv = DeepState_DoubleInRange(dlow, dhigh); - assume(!isnan(v)); - LOG(TRACE) << "dlow = " << dlow << ", dhigh = " << dhigh << ", dv = " << dv; - ASSERT ((dlow <= dv) && (dv <= dhigh)) << "dlow = " << dlow << ", dhigh = " << dhigh << " dv = " << dv; - - low = DeepState_Int(); - high = DeepState_Int(); - if (low > high) { - int temp = low; - low = high; - high = temp; - } - mv = DeepState_IntInRange(2, 45); - LOG(TRACE) << "low = " << low << ", high = " << high << ", mv = " << mv; - ASSIGN_SATISFYING_IN_RANGE(v, DeepState_IntInRange(low, high), low, high, (v % mv) == 0); - LOG(TRACE) << "v = " << v; - ASSERT ((v % mv) == 0) << "v = " << v << ", mv = " << mv; - ASSERT ((low <= v) && (v <= high)) << "low = " << low << ", high = " << high << " v = " << v; -} +#include +#include + +using namespace deepstate; +using namespace std; + +TEST(DeepState, RangeAPIs) { + int v, low, high, mv; + char cv, clow, chigh; + double dv, dlow, dhigh; + + low = DeepState_Int(); + high = DeepState_Int(); + if (low > high) { + int temp = low; + low = high; + high = temp; + } + v = DeepState_IntInRange(low, high); + LOG(TRACE) << "low = " << low << ", high = " << high << ", v = " << v; + ASSERT ((low <= v) && (v <= high)) << "low = " << low << ", high = " << high << ", v = " << v; + + clow = DeepState_Char(); + chigh = DeepState_Char(); + if (clow > chigh) { + char temp = clow; + clow = chigh; + chigh = temp; + } + cv = DeepState_CharInRange(clow, chigh); + LOG(TRACE) << "clow = " << clow << ", chigh = " << chigh << ", cv = " << cv; + ASSERT ((clow <= cv) && (cv <= chigh)) << "clow = " << clow << ", chigh = " << chigh << " cv = " << cv; + + mv = DeepState_IntInRange(2, 45); + ASSIGN_SATISFYING(v, DeepState_Int(), (v % mv) == 0); + ASSERT ((v % mv) == 0) << "mv = " << mv << ", v = " << v; + + dlow = DeepState_Double(); + assume(!isnan(dlow)); + dhigh = DeepState_Double(); + assume(!isnan(dhigh)); + if (dlow > dhigh) { + double temp = dlow; + dlow = dhigh; + dhigh = temp; + } + dv = DeepState_DoubleInRange(dlow, dhigh); + assume(!isnan(v)); + LOG(TRACE) << "dlow = " << dlow << ", dhigh = " << dhigh << ", dv = " << dv; + ASSERT ((dlow <= dv) && (dv <= dhigh)) << "dlow = " << dlow << ", dhigh = " << dhigh << " dv = " << dv; + + low = DeepState_Int(); + high = DeepState_Int(); + if (low > high) { + int temp = low; + low = high; + high = temp; + } + mv = DeepState_IntInRange(2, 45); + LOG(TRACE) << "low = " << low << ", high = " << high << ", mv = " << mv; + ASSIGN_SATISFYING_IN_RANGE(v, DeepState_IntInRange(low, high), low, high, (v % mv) == 0); + LOG(TRACE) << "v = " << v; + ASSERT ((v % mv) == 0) << "v = " << v << ", mv = " << mv; + ASSERT ((low <= v) && (v <= high)) << "low = " << low << ", high = " << high << " v = " << v; +} diff --git a/examples/deepstate_abilist.txt b/examples/deepstate_abilist.txt index 537dc5a1..9b413207 100644 --- a/examples/deepstate_abilist.txt +++ b/examples/deepstate_abilist.txt @@ -1,220 +1,220 @@ -fun:abort=uninstrumented -fun:__assert_fail=uninstrumented -fun:dfs$DeepState_Abandon=uninstrumented -fun:dfs$DeepState_AllocCurrentTestRun=uninstrumented -fun:dfs$DeepState_AssignCStr_C=uninstrumented -fun:dfs$_DeepState_Assume=uninstrumented -fun:dfs$DeepState_Begin=uninstrumented -fun:dfs$DeepState_BeginDrFuzz=uninstrumented -fun:dfs$DeepState_Bool=uninstrumented -fun:dfs$DeepState_CatchAbandoned=uninstrumented -fun:dfs$DeepState_CatchFail=uninstrumented -fun:dfs$DeepState_Char=uninstrumented -fun:dfs$DeepState_CleanUp=uninstrumented -fun:dfs$DeepState_ConcretizeCStr=uninstrumented -fun:dfs$DeepState_ConcretizeData=uninstrumented -fun:dfs$DeepState_Crash=uninstrumented -fun:dfs$DeepState_CStr_C=uninstrumented -fun:dfs$DeepState_Double=uninstrumented -fun:dfs$DeepState_DoubleInRange=uninstrumented -fun:dfs$DeepState_Fail=uninstrumented -fun:dfs$DeepState_FirstTest=uninstrumented -fun:dfs$DeepState_Float=uninstrumented -fun:dfs$DeepState_FloatInRange=uninstrumented -fun:dfs$DeepState_Fuzz=uninstrumented -fun:dfs$DeepState_FuzzOneTestCase=uninstrumented -fun:dfs$DeepState_GetSwarmConfig=uninstrumented -fun:dfs$DeepState_InputPath=uninstrumented -fun:dfs$DeepState_Int=uninstrumented -fun:dfs$DeepState_Int64=uninstrumented -fun:dfs$DeepState_IsSymbolicUInt=uninstrumented -fun:dfs$DeepState_IsTrue=uninstrumented -fun:dfs$DeepState_Long=uninstrumented -fun:dfs$DeepState_Malloc=uninstrumented -fun:dfs$DeepState_MaxInt=uninstrumented -fun:dfs$DeepState_MaxUInt=uninstrumented -fun:dfs$DeepState_MemScrub=uninstrumented -fun:dfs$DeepState_MinInt=uninstrumented -fun:dfs$DeepState_MinUInt=uninstrumented -fun:dfs$DeepState_NewSwarmConfig=uninstrumented -fun:dfs$DeepState_One=uninstrumented -fun:dfs$DeepState_Pass=uninstrumented -fun:dfs$DeepState_RandInt=uninstrumented -fun:dfs$DeepState_RunSavedTakeOverCases=uninstrumented -fun:dfs$DeepState_SaveCrashingTest=uninstrumented -fun:dfs$DeepState_SaveFailingTest=uninstrumented -fun:dfs$DeepState_SavePassingTest=uninstrumented -fun:dfs$DeepState_Setup=uninstrumented -fun:dfs$DeepState_Short=uninstrumented -fun:dfs$DeepState_Size=uninstrumented -fun:dfs$DeepState_SoftFail=uninstrumented -fun:dfs$DeepState_SwarmAssignCStr_C=uninstrumented -fun:dfs$DeepState_SwarmCStr_C=uninstrumented -fun:dfs$DeepState_SwarmSymbolizeCStr_C=uninstrumented -fun:dfs$DeepState_SymbolizeCStr_C=uninstrumented -fun:dfs$DeepState_SymbolizeData=uninstrumented -fun:dfs$DeepState_SymbolizeDataNoNull=uninstrumented -fun:dfs$DeepState_TakeOver=uninstrumented -fun:dfs$DeepState_Teardown=uninstrumented -fun:dfs$DeepState_UChar=uninstrumented -fun:dfs$DeepState_UInt=uninstrumented -fun:dfs$DeepState_UInt64=uninstrumented -fun:dfs$DeepState_UShort=uninstrumented -fun:dfs$DeepState_Warn_srand=uninstrumented -fun:dfs$DeepState_Zero=uninstrumented -fun:dfs$DeepState_ZeroSink=uninstrumented -fun:dfs$DrMemFuzzFunc=uninstrumented -fun:dfs$FuzzerEntrypoint=uninstrumented -fun:dfs$LLVMFuzzerTestOneInput=uninstrumented -fun:dfs$makeFilename=uninstrumented -fun:dfs$writeInputData=uninstrumented -fun:__stack_chk_fail=uninstrumented -fun:dfs$DeepState_Log=uninstrumented -fun:dfs$DeepState_LogFormat=uninstrumented -fun:dfs$DeepState_LogVFormat=uninstrumented -fun:dfs$DeepState_LogVFormatLLVM=uninstrumented -fun:fprintf=uninstrumented -fun:__fprintf_chk=uninstrumented -fun:printf=uninstrumented -fun:__printf_chk=uninstrumented -fun:puts=uninstrumented -fun:vfprintf=uninstrumented -fun:__vfprintf_chk=uninstrumented -fun:vprintf=uninstrumented -fun:__vprintf_chk=uninstrumented -fun:dfs$DeepState_AddOption=uninstrumented -fun:dfs$DeepState_InitOptions=uninstrumented -fun:dfs$DeepState_ParseBoolOption=uninstrumented -fun:dfs$DeepState_ParseIntOption=uninstrumented -fun:dfs$DeepState_ParseStringOption=uninstrumented -fun:dfs$DeepState_ParseUIntOption=uninstrumented -fun:dfs$DeepState_PrintAllOptions=uninstrumented -fun:dfs$DeepState_ClearStream=uninstrumented -fun:dfs$DeepState_LogStream=uninstrumented -fun:dfs$DeepState_StreamCStr=uninstrumented -fun:dfs$DeepState_StreamDouble=uninstrumented -fun:dfs$_DeepState_StreamFloat=uninstrumented -fun:dfs$DeepState_StreamFormat=uninstrumented -fun:dfs$_DeepState_StreamInt=uninstrumented -fun:dfs$DeepState_StreamInt16=uninstrumented -fun:dfs$DeepState_StreamInt32=uninstrumented -fun:dfs$DeepState_StreamInt64=uninstrumented -fun:dfs$DeepState_StreamInt8=uninstrumented -fun:dfs$DeepState_StreamPointer=uninstrumented -fun:dfs$DeepState_StreamResetFormatting=uninstrumented -fun:dfs$_DeepState_StreamString=uninstrumented -fun:dfs$DeepState_StreamUInt16=uninstrumented -fun:dfs$DeepState_StreamUInt32=uninstrumented -fun:dfs$DeepState_StreamUInt64=uninstrumented -fun:dfs$DeepState_StreamUInt8=uninstrumented -fun:dfs$DeepState_StreamVFormat=uninstrumented -fun:abort=discard -fun:__assert_fail=discard -fun:dfs$DeepState_Abandon=discard -fun:dfs$DeepState_AllocCurrentTestRun=discard -fun:dfs$DeepState_AssignCStr_C=discard -fun:dfs$_DeepState_Assume=discard -fun:dfs$DeepState_Begin=discard -fun:dfs$DeepState_BeginDrFuzz=discard -fun:dfs$DeepState_Bool=discard -fun:dfs$DeepState_CatchAbandoned=discard -fun:dfs$DeepState_CatchFail=discard -fun:dfs$DeepState_Char=discard -fun:dfs$DeepState_CleanUp=discard -fun:dfs$DeepState_ConcretizeCStr=discard -fun:dfs$DeepState_ConcretizeData=discard -fun:dfs$DeepState_Crash=discard -fun:dfs$DeepState_CStr_C=discard -fun:dfs$DeepState_Double=discard -fun:dfs$DeepState_DoubleInRange=discard -fun:dfs$DeepState_Fail=discard -fun:dfs$DeepState_FirstTest=discard -fun:dfs$DeepState_Float=discard -fun:dfs$DeepState_FloatInRange=discard -fun:dfs$DeepState_Fuzz=discard -fun:dfs$DeepState_FuzzOneTestCase=discard -fun:dfs$DeepState_GetSwarmConfig=discard -fun:dfs$DeepState_InputPath=discard -fun:dfs$DeepState_Int=discard -fun:dfs$DeepState_Int64=discard -fun:dfs$DeepState_IsSymbolicUInt=discard -fun:dfs$DeepState_IsTrue=discard -fun:dfs$DeepState_Long=discard -fun:dfs$DeepState_Malloc=discard -fun:dfs$DeepState_MaxInt=discard -fun:dfs$DeepState_MaxUInt=discard -fun:dfs$DeepState_MemScrub=discard -fun:dfs$DeepState_MinInt=discard -fun:dfs$DeepState_MinUInt=discard -fun:dfs$DeepState_NewSwarmConfig=discard -fun:dfs$DeepState_One=discard -fun:dfs$DeepState_Pass=discard -fun:dfs$DeepState_RandInt=discard -fun:dfs$DeepState_RunSavedTakeOverCases=discard -fun:dfs$DeepState_SaveCrashingTest=discard -fun:dfs$DeepState_SaveFailingTest=discard -fun:dfs$DeepState_SavePassingTest=discard -fun:dfs$DeepState_Setup=discard -fun:dfs$DeepState_Short=discard -fun:dfs$DeepState_Size=discard -fun:dfs$DeepState_SoftFail=discard -fun:dfs$DeepState_SwarmAssignCStr_C=discard -fun:dfs$DeepState_SwarmCStr_C=discard -fun:dfs$DeepState_SwarmSymbolizeCStr_C=discard -fun:dfs$DeepState_SymbolizeCStr_C=discard -fun:dfs$DeepState_SymbolizeData=discard -fun:dfs$DeepState_SymbolizeDataNoNull=discard -fun:dfs$DeepState_TakeOver=discard -fun:dfs$DeepState_Teardown=discard -fun:dfs$DeepState_UChar=discard -fun:dfs$DeepState_UInt=discard -fun:dfs$DeepState_UInt64=discard -fun:dfs$DeepState_UShort=discard -fun:dfs$DeepState_Warn_srand=discard -fun:dfs$DeepState_Zero=discard -fun:dfs$DeepState_ZeroSink=discard -fun:dfs$DrMemFuzzFunc=discard -fun:dfs$FuzzerEntrypoint=discard -fun:dfs$LLVMFuzzerTestOneInput=discard -fun:dfs$makeFilename=discard -fun:dfs$writeInputData=discard -fun:__stack_chk_fail=discard -fun:dfs$DeepState_Log=discard -fun:dfs$DeepState_LogFormat=discard -fun:dfs$DeepState_LogVFormat=discard -fun:dfs$DeepState_LogVFormatLLVM=discard -fun:fprintf=discard -fun:__fprintf_chk=discard -fun:printf=discard -fun:__printf_chk=discard -fun:puts=discard -fun:vfprintf=discard -fun:__vfprintf_chk=discard -fun:vprintf=discard -fun:__vprintf_chk=discard -fun:dfs$DeepState_AddOption=discard -fun:dfs$DeepState_InitOptions=discard -fun:dfs$DeepState_ParseBoolOption=discard -fun:dfs$DeepState_ParseIntOption=discard -fun:dfs$DeepState_ParseStringOption=discard -fun:dfs$DeepState_ParseUIntOption=discard -fun:dfs$DeepState_PrintAllOptions=discard -fun:dfs$DeepState_ClearStream=discard -fun:dfs$DeepState_LogStream=discard -fun:dfs$DeepState_StreamCStr=discard -fun:dfs$DeepState_StreamDouble=discard -fun:dfs$_DeepState_StreamFloat=discard -fun:dfs$DeepState_StreamFormat=discard -fun:dfs$_DeepState_StreamInt=discard -fun:dfs$DeepState_StreamInt16=discard -fun:dfs$DeepState_StreamInt32=discard -fun:dfs$DeepState_StreamInt64=discard -fun:dfs$DeepState_StreamInt8=discard -fun:dfs$DeepState_StreamPointer=discard -fun:dfs$DeepState_StreamResetFormatting=discard -fun:dfs$_DeepState_StreamString=discard -fun:dfs$DeepState_StreamUInt16=discard -fun:dfs$DeepState_StreamUInt32=discard -fun:dfs$DeepState_StreamUInt64=discard -fun:dfs$DeepState_StreamUInt8=discard -fun:dfs$DeepState_StreamVFormat=discard +fun:abort=uninstrumented +fun:__assert_fail=uninstrumented +fun:dfs$DeepState_Abandon=uninstrumented +fun:dfs$DeepState_AllocCurrentTestRun=uninstrumented +fun:dfs$DeepState_AssignCStr_C=uninstrumented +fun:dfs$_DeepState_Assume=uninstrumented +fun:dfs$DeepState_Begin=uninstrumented +fun:dfs$DeepState_BeginDrFuzz=uninstrumented +fun:dfs$DeepState_Bool=uninstrumented +fun:dfs$DeepState_CatchAbandoned=uninstrumented +fun:dfs$DeepState_CatchFail=uninstrumented +fun:dfs$DeepState_Char=uninstrumented +fun:dfs$DeepState_CleanUp=uninstrumented +fun:dfs$DeepState_ConcretizeCStr=uninstrumented +fun:dfs$DeepState_ConcretizeData=uninstrumented +fun:dfs$DeepState_Crash=uninstrumented +fun:dfs$DeepState_CStr_C=uninstrumented +fun:dfs$DeepState_Double=uninstrumented +fun:dfs$DeepState_DoubleInRange=uninstrumented +fun:dfs$DeepState_Fail=uninstrumented +fun:dfs$DeepState_FirstTest=uninstrumented +fun:dfs$DeepState_Float=uninstrumented +fun:dfs$DeepState_FloatInRange=uninstrumented +fun:dfs$DeepState_Fuzz=uninstrumented +fun:dfs$DeepState_FuzzOneTestCase=uninstrumented +fun:dfs$DeepState_GetSwarmConfig=uninstrumented +fun:dfs$DeepState_InputPath=uninstrumented +fun:dfs$DeepState_Int=uninstrumented +fun:dfs$DeepState_Int64=uninstrumented +fun:dfs$DeepState_IsSymbolicUInt=uninstrumented +fun:dfs$DeepState_IsTrue=uninstrumented +fun:dfs$DeepState_Long=uninstrumented +fun:dfs$DeepState_Malloc=uninstrumented +fun:dfs$DeepState_MaxInt=uninstrumented +fun:dfs$DeepState_MaxUInt=uninstrumented +fun:dfs$DeepState_MemScrub=uninstrumented +fun:dfs$DeepState_MinInt=uninstrumented +fun:dfs$DeepState_MinUInt=uninstrumented +fun:dfs$DeepState_NewSwarmConfig=uninstrumented +fun:dfs$DeepState_One=uninstrumented +fun:dfs$DeepState_Pass=uninstrumented +fun:dfs$DeepState_RandInt=uninstrumented +fun:dfs$DeepState_RunSavedTakeOverCases=uninstrumented +fun:dfs$DeepState_SaveCrashingTest=uninstrumented +fun:dfs$DeepState_SaveFailingTest=uninstrumented +fun:dfs$DeepState_SavePassingTest=uninstrumented +fun:dfs$DeepState_Setup=uninstrumented +fun:dfs$DeepState_Short=uninstrumented +fun:dfs$DeepState_Size=uninstrumented +fun:dfs$DeepState_SoftFail=uninstrumented +fun:dfs$DeepState_SwarmAssignCStr_C=uninstrumented +fun:dfs$DeepState_SwarmCStr_C=uninstrumented +fun:dfs$DeepState_SwarmSymbolizeCStr_C=uninstrumented +fun:dfs$DeepState_SymbolizeCStr_C=uninstrumented +fun:dfs$DeepState_SymbolizeData=uninstrumented +fun:dfs$DeepState_SymbolizeDataNoNull=uninstrumented +fun:dfs$DeepState_TakeOver=uninstrumented +fun:dfs$DeepState_Teardown=uninstrumented +fun:dfs$DeepState_UChar=uninstrumented +fun:dfs$DeepState_UInt=uninstrumented +fun:dfs$DeepState_UInt64=uninstrumented +fun:dfs$DeepState_UShort=uninstrumented +fun:dfs$DeepState_Warn_srand=uninstrumented +fun:dfs$DeepState_Zero=uninstrumented +fun:dfs$DeepState_ZeroSink=uninstrumented +fun:dfs$DrMemFuzzFunc=uninstrumented +fun:dfs$FuzzerEntrypoint=uninstrumented +fun:dfs$LLVMFuzzerTestOneInput=uninstrumented +fun:dfs$makeFilename=uninstrumented +fun:dfs$writeInputData=uninstrumented +fun:__stack_chk_fail=uninstrumented +fun:dfs$DeepState_Log=uninstrumented +fun:dfs$DeepState_LogFormat=uninstrumented +fun:dfs$DeepState_LogVFormat=uninstrumented +fun:dfs$DeepState_LogVFormatLLVM=uninstrumented +fun:fprintf=uninstrumented +fun:__fprintf_chk=uninstrumented +fun:printf=uninstrumented +fun:__printf_chk=uninstrumented +fun:puts=uninstrumented +fun:vfprintf=uninstrumented +fun:__vfprintf_chk=uninstrumented +fun:vprintf=uninstrumented +fun:__vprintf_chk=uninstrumented +fun:dfs$DeepState_AddOption=uninstrumented +fun:dfs$DeepState_InitOptions=uninstrumented +fun:dfs$DeepState_ParseBoolOption=uninstrumented +fun:dfs$DeepState_ParseIntOption=uninstrumented +fun:dfs$DeepState_ParseStringOption=uninstrumented +fun:dfs$DeepState_ParseUIntOption=uninstrumented +fun:dfs$DeepState_PrintAllOptions=uninstrumented +fun:dfs$DeepState_ClearStream=uninstrumented +fun:dfs$DeepState_LogStream=uninstrumented +fun:dfs$DeepState_StreamCStr=uninstrumented +fun:dfs$DeepState_StreamDouble=uninstrumented +fun:dfs$_DeepState_StreamFloat=uninstrumented +fun:dfs$DeepState_StreamFormat=uninstrumented +fun:dfs$_DeepState_StreamInt=uninstrumented +fun:dfs$DeepState_StreamInt16=uninstrumented +fun:dfs$DeepState_StreamInt32=uninstrumented +fun:dfs$DeepState_StreamInt64=uninstrumented +fun:dfs$DeepState_StreamInt8=uninstrumented +fun:dfs$DeepState_StreamPointer=uninstrumented +fun:dfs$DeepState_StreamResetFormatting=uninstrumented +fun:dfs$_DeepState_StreamString=uninstrumented +fun:dfs$DeepState_StreamUInt16=uninstrumented +fun:dfs$DeepState_StreamUInt32=uninstrumented +fun:dfs$DeepState_StreamUInt64=uninstrumented +fun:dfs$DeepState_StreamUInt8=uninstrumented +fun:dfs$DeepState_StreamVFormat=uninstrumented +fun:abort=discard +fun:__assert_fail=discard +fun:dfs$DeepState_Abandon=discard +fun:dfs$DeepState_AllocCurrentTestRun=discard +fun:dfs$DeepState_AssignCStr_C=discard +fun:dfs$_DeepState_Assume=discard +fun:dfs$DeepState_Begin=discard +fun:dfs$DeepState_BeginDrFuzz=discard +fun:dfs$DeepState_Bool=discard +fun:dfs$DeepState_CatchAbandoned=discard +fun:dfs$DeepState_CatchFail=discard +fun:dfs$DeepState_Char=discard +fun:dfs$DeepState_CleanUp=discard +fun:dfs$DeepState_ConcretizeCStr=discard +fun:dfs$DeepState_ConcretizeData=discard +fun:dfs$DeepState_Crash=discard +fun:dfs$DeepState_CStr_C=discard +fun:dfs$DeepState_Double=discard +fun:dfs$DeepState_DoubleInRange=discard +fun:dfs$DeepState_Fail=discard +fun:dfs$DeepState_FirstTest=discard +fun:dfs$DeepState_Float=discard +fun:dfs$DeepState_FloatInRange=discard +fun:dfs$DeepState_Fuzz=discard +fun:dfs$DeepState_FuzzOneTestCase=discard +fun:dfs$DeepState_GetSwarmConfig=discard +fun:dfs$DeepState_InputPath=discard +fun:dfs$DeepState_Int=discard +fun:dfs$DeepState_Int64=discard +fun:dfs$DeepState_IsSymbolicUInt=discard +fun:dfs$DeepState_IsTrue=discard +fun:dfs$DeepState_Long=discard +fun:dfs$DeepState_Malloc=discard +fun:dfs$DeepState_MaxInt=discard +fun:dfs$DeepState_MaxUInt=discard +fun:dfs$DeepState_MemScrub=discard +fun:dfs$DeepState_MinInt=discard +fun:dfs$DeepState_MinUInt=discard +fun:dfs$DeepState_NewSwarmConfig=discard +fun:dfs$DeepState_One=discard +fun:dfs$DeepState_Pass=discard +fun:dfs$DeepState_RandInt=discard +fun:dfs$DeepState_RunSavedTakeOverCases=discard +fun:dfs$DeepState_SaveCrashingTest=discard +fun:dfs$DeepState_SaveFailingTest=discard +fun:dfs$DeepState_SavePassingTest=discard +fun:dfs$DeepState_Setup=discard +fun:dfs$DeepState_Short=discard +fun:dfs$DeepState_Size=discard +fun:dfs$DeepState_SoftFail=discard +fun:dfs$DeepState_SwarmAssignCStr_C=discard +fun:dfs$DeepState_SwarmCStr_C=discard +fun:dfs$DeepState_SwarmSymbolizeCStr_C=discard +fun:dfs$DeepState_SymbolizeCStr_C=discard +fun:dfs$DeepState_SymbolizeData=discard +fun:dfs$DeepState_SymbolizeDataNoNull=discard +fun:dfs$DeepState_TakeOver=discard +fun:dfs$DeepState_Teardown=discard +fun:dfs$DeepState_UChar=discard +fun:dfs$DeepState_UInt=discard +fun:dfs$DeepState_UInt64=discard +fun:dfs$DeepState_UShort=discard +fun:dfs$DeepState_Warn_srand=discard +fun:dfs$DeepState_Zero=discard +fun:dfs$DeepState_ZeroSink=discard +fun:dfs$DrMemFuzzFunc=discard +fun:dfs$FuzzerEntrypoint=discard +fun:dfs$LLVMFuzzerTestOneInput=discard +fun:dfs$makeFilename=discard +fun:dfs$writeInputData=discard +fun:__stack_chk_fail=discard +fun:dfs$DeepState_Log=discard +fun:dfs$DeepState_LogFormat=discard +fun:dfs$DeepState_LogVFormat=discard +fun:dfs$DeepState_LogVFormatLLVM=discard +fun:fprintf=discard +fun:__fprintf_chk=discard +fun:printf=discard +fun:__printf_chk=discard +fun:puts=discard +fun:vfprintf=discard +fun:__vfprintf_chk=discard +fun:vprintf=discard +fun:__vprintf_chk=discard +fun:dfs$DeepState_AddOption=discard +fun:dfs$DeepState_InitOptions=discard +fun:dfs$DeepState_ParseBoolOption=discard +fun:dfs$DeepState_ParseIntOption=discard +fun:dfs$DeepState_ParseStringOption=discard +fun:dfs$DeepState_ParseUIntOption=discard +fun:dfs$DeepState_PrintAllOptions=discard +fun:dfs$DeepState_ClearStream=discard +fun:dfs$DeepState_LogStream=discard +fun:dfs$DeepState_StreamCStr=discard +fun:dfs$DeepState_StreamDouble=discard +fun:dfs$_DeepState_StreamFloat=discard +fun:dfs$DeepState_StreamFormat=discard +fun:dfs$_DeepState_StreamInt=discard +fun:dfs$DeepState_StreamInt16=discard +fun:dfs$DeepState_StreamInt32=discard +fun:dfs$DeepState_StreamInt64=discard +fun:dfs$DeepState_StreamInt8=discard +fun:dfs$DeepState_StreamPointer=discard +fun:dfs$DeepState_StreamResetFormatting=discard +fun:dfs$_DeepState_StreamString=discard +fun:dfs$DeepState_StreamUInt16=discard +fun:dfs$DeepState_StreamUInt32=discard +fun:dfs$DeepState_StreamUInt64=discard +fun:dfs$DeepState_StreamUInt8=discard +fun:dfs$DeepState_StreamVFormat=discard diff --git a/extras/example_config.ini b/extras/example_config.ini index 8191fbf3..98ef8266 100644 --- a/extras/example_config.ini +++ b/extras/example_config.ini @@ -1,19 +1,19 @@ -##################### -# example_config.ini -##################### - -# Manifest sections contain identifying metadata. These sections are ignored by -# fuzzer/symex frontends, but can be used by other auxiliary tools that bootstrap -# analysis tool APIs -[manifest] -name = My Example Configuration -author = Your Name -fuzzer = afl-fuzz - -[compile] -compile_test = Crash.cpp -compiler_args = [] - -[test] -input_seeds = input -output_test_dir = out_dir +##################### +# example_config.ini +##################### + +# Manifest sections contain identifying metadata. These sections are ignored by +# fuzzer/symex frontends, but can be used by other auxiliary tools that bootstrap +# analysis tool APIs +[manifest] +name = My Example Configuration +author = Your Name +fuzzer = afl-fuzz + +[compile] +compile_test = Crash.cpp +compiler_args = [] + +[test] +input_seeds = input +output_test_dir = out_dir diff --git a/mypy.ini b/mypy.ini index d5afe4a0..09a878ab 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,22 +1,22 @@ -[mypy] -python_version = 3.6 -warn_return_any = True -warn_unused_configs = True - -[mypy-angr] -ignore_missing_imports = True - -[mypy-manticore] -ignore_missing_imports = True - -[mypy-manticore.utils] -ignore_missing_imports = True - -[mypy-manticore.core.state] -ignore_missing_imports = True - -[mypy-manticore.native] -ignore_missing_imports = True - -[mypy-manticore.native.manticore] -ignore_missing_imports = True +[mypy] +python_version = 3.6 +warn_return_any = True +warn_unused_configs = True + +[mypy-angr] +ignore_missing_imports = True + +[mypy-manticore] +ignore_missing_imports = True + +[mypy-manticore.utils] +ignore_missing_imports = True + +[mypy-manticore.core.state] +ignore_missing_imports = True + +[mypy-manticore.native] +ignore_missing_imports = True + +[mypy-manticore.native.manticore] +ignore_missing_imports = True diff --git a/push/build_image b/push/build_image index 746184bb..a298421f 100644 --- a/push/build_image +++ b/push/build_image @@ -1,12 +1,12 @@ -#!/usr/bin/env bash - -set -eu - -IMAGE_NAME="deepstate" -echo "IMAGE_NAME $IMAGE_NAME" - -echo "Building Docker base image..." -docker build -t deepstate-base -f docker/base/Dockerfile docker/base || exit $? - -echo "Building Docker image..." -docker build -t $IMAGE_NAME -f docker/Dockerfile . || exit $? +#!/usr/bin/env bash + +set -eu + +IMAGE_NAME="deepstate" +echo "IMAGE_NAME $IMAGE_NAME" + +echo "Building Docker base image..." +docker build -t deepstate-base -f docker/base/Dockerfile docker/base || exit $? + +echo "Building Docker image..." +docker build -t $IMAGE_NAME -f docker/Dockerfile . || exit $? diff --git a/push/publish b/push/publish index c29c7d3d..eba7bb99 100644 --- a/push/publish +++ b/push/publish @@ -1,28 +1,28 @@ -#!/usr/bin/env bash - -# Publishes the most recent web container to docker hubs repo. -# This script assumes docker push works. -# You must set up docker push on your own. - -set -eu - - -DOCKER_REPO="trailofbits/deepstate" -IMAGE_NAME="deepstate" -echo "IMAGE_NAME $IMAGE_NAME" - -IMAGE_ID=$(docker images $IMAGE_NAME:latest --format "{{.ID}}") - -if [ -n "$DOCKER_USERNAME" ]; then echo "Found username"; fi -if [ -n "$DOCKER_PASSWORD" ]; then echo "Found password"; fi - -if [ -n "$DOCKER_USERNAME" ] && [ -n "$DOCKER_PASSWORD" ]; then - echo "Logging in using ENV creds" - docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" -fi - -echo "Pushing image $IMAGE_NAME:$TRAVIS_BRANCH" -docker tag $IMAGE_ID $DOCKER_REPO -docker tag $IMAGE_ID ${DOCKER_REPO}:${TRAVIS_BUILD_NUMBER} -docker push $DOCKER_REPO -docker push ${DOCKER_REPO}:${TRAVIS_BUILD_NUMBER} +#!/usr/bin/env bash + +# Publishes the most recent web container to docker hubs repo. +# This script assumes docker push works. +# You must set up docker push on your own. + +set -eu + + +DOCKER_REPO="trailofbits/deepstate" +IMAGE_NAME="deepstate" +echo "IMAGE_NAME $IMAGE_NAME" + +IMAGE_ID=$(docker images $IMAGE_NAME:latest --format "{{.ID}}") + +if [ -n "$DOCKER_USERNAME" ]; then echo "Found username"; fi +if [ -n "$DOCKER_PASSWORD" ]; then echo "Found password"; fi + +if [ -n "$DOCKER_USERNAME" ] && [ -n "$DOCKER_PASSWORD" ]; then + echo "Logging in using ENV creds" + docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" +fi + +echo "Pushing image $IMAGE_NAME:$TRAVIS_BRANCH" +docker tag $IMAGE_ID $DOCKER_REPO +docker tag $IMAGE_ID ${DOCKER_REPO}:${TRAVIS_BUILD_NUMBER} +docker push $DOCKER_REPO +docker push ${DOCKER_REPO}:${TRAVIS_BUILD_NUMBER} diff --git a/push/run.sh b/push/run.sh index 7710c1e5..f20cd979 100644 --- a/push/run.sh +++ b/push/run.sh @@ -1,46 +1,46 @@ -#!/usr/bin/env bash - -set -eu - -IMAGE_NAME="deepstate" -DEPLOY_BRANCHES="master" - -# Only process first job in matrix (TRAVIS_JOB_NUMBER ends with ".1") -if [[ ! $TRAVIS_JOB_NUMBER =~ \.1$ ]]; then - echo "Skipping deploy since it's not the first job in matrix" - exit 0 -fi - -# Don't process pull requests -# $TRAVIS_PULL_REQUEST will be the PR number or "false" if not a PR -if [[ -n "$TRAVIS_PULL_REQUEST" ]] && [[ "$TRAVIS_PULL_REQUEST" != "false" ]]; then - echo "Skipping deploy because it's a pull request" - exit 0 -fi - -# Only process branches listed in DEPLOY_BRANCHES -BRANCHES_TO_DEPLOY=($DEPLOY_BRANCHES) -if [[ ! " ${BRANCHES_TO_DEPLOY} " =~ " ${TRAVIS_BRANCH} " ]]; then - # whatever you want to do when arr contains value - echo "Branches to deploy: ${DEPLOY_BRANCHES}" - echo "Travis Branch: ${TRAVIS_BRANCH}" - - echo "Skipping deploy, not a branch to be deployed" - exit 0 -fi - -if [ $? = 0 ]; then - - # Get absolute path of dir where run.sh is located - SOURCE="${BASH_SOURCE[0]}" - while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink - DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" - SOURCE="$(readlink "$SOURCE")" - [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located - done - export SCRIPTDIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" - - bash ${SCRIPTDIR}/build_image && - bash ${SCRIPTDIR}/publish - -fi +#!/usr/bin/env bash + +set -eu + +IMAGE_NAME="deepstate" +DEPLOY_BRANCHES="master" + +# Only process first job in matrix (TRAVIS_JOB_NUMBER ends with ".1") +if [[ ! $TRAVIS_JOB_NUMBER =~ \.1$ ]]; then + echo "Skipping deploy since it's not the first job in matrix" + exit 0 +fi + +# Don't process pull requests +# $TRAVIS_PULL_REQUEST will be the PR number or "false" if not a PR +if [[ -n "$TRAVIS_PULL_REQUEST" ]] && [[ "$TRAVIS_PULL_REQUEST" != "false" ]]; then + echo "Skipping deploy because it's a pull request" + exit 0 +fi + +# Only process branches listed in DEPLOY_BRANCHES +BRANCHES_TO_DEPLOY=($DEPLOY_BRANCHES) +if [[ ! " ${BRANCHES_TO_DEPLOY} " =~ " ${TRAVIS_BRANCH} " ]]; then + # whatever you want to do when arr contains value + echo "Branches to deploy: ${DEPLOY_BRANCHES}" + echo "Travis Branch: ${TRAVIS_BRANCH}" + + echo "Skipping deploy, not a branch to be deployed" + exit 0 +fi + +if [ $? = 0 ]; then + + # Get absolute path of dir where run.sh is located + SOURCE="${BASH_SOURCE[0]}" + while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink + DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + SOURCE="$(readlink "$SOURCE")" + [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located + done + export SCRIPTDIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + + bash ${SCRIPTDIR}/build_image && + bash ${SCRIPTDIR}/publish + +fi diff --git a/src/include/deepstate/Compiler.h b/src/include/deepstate/Compiler.h index 75f74202..7a3bcc63 100644 --- a/src/include/deepstate/Compiler.h +++ b/src/include/deepstate/Compiler.h @@ -1,107 +1,107 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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. - */ - -#ifndef SRC_INCLUDE_DEEPSTATE_COMPILER_H_ -#define SRC_INCLUDE_DEEPSTATE_COMPILER_H_ - -#include -#include - -/* Concatenation macros. */ -#define DEEPSTATE_CAT__(x, y) x ## y -#define DEEPSTATE_CAT_(x, y) DEEPSTATE_CAT__(x, y) -#define DEEPSTATE_CAT(x, y) DEEPSTATE_CAT_(x, y) - -/* Stringify a macro parameter. */ -#define DEEPSTATE_TO_STR(a) _DEEPSTATE_TO_STR(a) -#define _DEEPSTATE_TO_STR(a) __DEEPSTATE_TO_STR(a) -#define __DEEPSTATE_TO_STR(a) #a - -/* Mark a function as not returning. */ -#if defined(_MSC_VER) -# define DEEPSTATE_NORETURN __declspec(noreturn) -#else -# define DEEPSTATE_NORETURN __attribute__((noreturn)) -#endif - -/* Mark a function for inlining. */ -#if defined(_MSC_VER) -# define DEEPSTATE_INLINE __forceinline -# define DEEPSTATE_NOINLINE __declspec(noinline) -#else -# define DEEPSTATE_INLINE inline __attribute__((always_inline)) -# define DEEPSTATE_NOINLINE __attribute__((noinline)) -#endif - -/* Introduce a trap instruction to halt execution. */ -#if defined(_MSC_VER) -# include -# define DeepState_Trap __debugbreak -#else -# define DeepState_Trap __builtin_trap -#endif - -/* Wrap a block of code in `extern "C"` if we are compiling with a C++ - * compiler. */ -#ifdef __cplusplus -# define DEEPSTATE_BEGIN_EXTERN_C extern "C" { -# define DEEPSTATE_END_EXTERN_C } -#else -# define DEEPSTATE_BEGIN_EXTERN_C -# define DEEPSTATE_END_EXTERN_C -#endif - -/* Initializer/finalizer sample for MSVC and GCC/Clang. - * 2010-2016 Joe Lowe. Released into the public domain. - * - * See: https://stackoverflow.com/a/2390626/247591 */ -#ifdef __cplusplus -# define DEEPSTATE_INITIALIZER(f) \ - static void f(void); \ - struct f ##_t_ { \ - f##_t_(void) { \ - f(); \ - } \ - }; \ - static f##_t_ f##_; \ - static void f(void) - -#elif defined(_MSC_VER) -# pragma section(".CRT$XCU",read) -# define DEEPSTATE_INITIALIZER2_(f, p) \ - static void f(void); \ - __declspec(allocate(".CRT$XCU")) void (*f##_)(void) = f; \ - __pragma(comment(linker,"/include:" p #f "_")) \ - static void f(void) - -# ifdef _WIN64 -# define DEEPSTATE_INITIALIZER(f) DEEPSTATE_INITIALIZER2_(f,"") -# else -# define DEEPSTATE_INITIALIZER(f) DEEPSTATE_INITIALIZER2_(f,"_") -# endif -#else -# define DEEPSTATE_INITIALIZER(f) \ - static void f(void) __attribute__((constructor)); \ - static void f(void) -#endif - -#define DEEPSTATE_BARRIER() \ - asm volatile ("":::"memory") - -#define DEEPSTATE_USED(x) \ - asm volatile (""::"m"(x):"memory") - -#endif /* SRC_INCLUDE_DEEPSTATE_COMPILER_H_ */ +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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. + */ + +#ifndef SRC_INCLUDE_DEEPSTATE_COMPILER_H_ +#define SRC_INCLUDE_DEEPSTATE_COMPILER_H_ + +#include +#include + +/* Concatenation macros. */ +#define DEEPSTATE_CAT__(x, y) x ## y +#define DEEPSTATE_CAT_(x, y) DEEPSTATE_CAT__(x, y) +#define DEEPSTATE_CAT(x, y) DEEPSTATE_CAT_(x, y) + +/* Stringify a macro parameter. */ +#define DEEPSTATE_TO_STR(a) _DEEPSTATE_TO_STR(a) +#define _DEEPSTATE_TO_STR(a) __DEEPSTATE_TO_STR(a) +#define __DEEPSTATE_TO_STR(a) #a + +/* Mark a function as not returning. */ +#if defined(_MSC_VER) +# define DEEPSTATE_NORETURN __declspec(noreturn) +#else +# define DEEPSTATE_NORETURN __attribute__((noreturn)) +#endif + +/* Mark a function for inlining. */ +#if defined(_MSC_VER) +# define DEEPSTATE_INLINE __forceinline +# define DEEPSTATE_NOINLINE __declspec(noinline) +#else +# define DEEPSTATE_INLINE inline __attribute__((always_inline)) +# define DEEPSTATE_NOINLINE __attribute__((noinline)) +#endif + +/* Introduce a trap instruction to halt execution. */ +#if defined(_MSC_VER) +# include +# define DeepState_Trap __debugbreak +#else +# define DeepState_Trap __builtin_trap +#endif + +/* Wrap a block of code in `extern "C"` if we are compiling with a C++ + * compiler. */ +#ifdef __cplusplus +# define DEEPSTATE_BEGIN_EXTERN_C extern "C" { +# define DEEPSTATE_END_EXTERN_C } +#else +# define DEEPSTATE_BEGIN_EXTERN_C +# define DEEPSTATE_END_EXTERN_C +#endif + +/* Initializer/finalizer sample for MSVC and GCC/Clang. + * 2010-2016 Joe Lowe. Released into the public domain. + * + * See: https://stackoverflow.com/a/2390626/247591 */ +#ifdef __cplusplus +# define DEEPSTATE_INITIALIZER(f) \ + static void f(void); \ + struct f ##_t_ { \ + f##_t_(void) { \ + f(); \ + } \ + }; \ + static f##_t_ f##_; \ + static void f(void) + +#elif defined(_MSC_VER) +# pragma section(".CRT$XCU",read) +# define DEEPSTATE_INITIALIZER2_(f, p) \ + static void f(void); \ + __declspec(allocate(".CRT$XCU")) void (*f##_)(void) = f; \ + __pragma(comment(linker,"/include:" p #f "_")) \ + static void f(void) + +# ifdef _WIN64 +# define DEEPSTATE_INITIALIZER(f) DEEPSTATE_INITIALIZER2_(f,"") +# else +# define DEEPSTATE_INITIALIZER(f) DEEPSTATE_INITIALIZER2_(f,"_") +# endif +#else +# define DEEPSTATE_INITIALIZER(f) \ + static void f(void) __attribute__((constructor)); \ + static void f(void) +#endif + +#define DEEPSTATE_BARRIER() \ + asm volatile ("":::"memory") + +#define DEEPSTATE_USED(x) \ + asm volatile (""::"m"(x):"memory") + +#endif /* SRC_INCLUDE_DEEPSTATE_COMPILER_H_ */ diff --git a/src/include/deepstate/DeepState.h b/src/include/deepstate/DeepState.h index 19eb56a1..eeb3eab6 100644 --- a/src/include/deepstate/DeepState.h +++ b/src/include/deepstate/DeepState.h @@ -1,1173 +1,1181 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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. - */ - -#ifndef SRC_INCLUDE_DEEPSTATE_DEEPSTATE_H_ -#define SRC_INCLUDE_DEEPSTATE_DEEPSTATE_H_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#ifdef assert -# undef assert -#endif - -#define assert DeepState_Assert -#define assume DeepState_Assume -#define check DeepState_Check - -#ifdef DEEPSTATE_TAKEOVER_RAND -#define rand DeepState_RandInt -#define srand DeepState_Warn_srand -#endif - -#ifndef DEEPSTATE_SIZE -#define DEEPSTATE_SIZE 32768 -#endif - -#ifndef DEEPSTATE_MAX_SWARM_CONFIGS -#define DEEPSTATE_MAX_SWARM_CONFIGS 1024 -#endif - -#ifndef DEEPSTATE_SWARM_MAX_PROB_RATIO -#define DEEPSTATE_SWARM_MAX_PROB_RATIO 16 -#endif - -#define MAYBE(...) \ - if (DeepState_Bool()) { \ - __VA_ARGS__ ; \ - } - -DEEPSTATE_BEGIN_EXTERN_C - -DECLARE_string(input_test_dir); -DECLARE_string(input_test_file); -DECLARE_string(input_test_files_dir); -DECLARE_string(input_which_test); -DECLARE_string(output_test_dir); -DECLARE_string(test_filter); - -DECLARE_bool(input_stdin); -DECLARE_bool(take_over); -DECLARE_bool(abort_on_fail); -DECLARE_bool(exit_on_fail); -DECLARE_bool(verbose_reads); -DECLARE_bool(fuzz); -DECLARE_bool(random); -DECLARE_bool(fuzz_save_passing); -DECLARE_bool(fork); -DECLARE_bool(list_tests); -DECLARE_bool(boring_only); -DECLARE_bool(run_disabled); - -DECLARE_int(min_log_level); -DECLARE_int(seed); -DECLARE_int(timeout); - -enum { - DeepState_InputSize = DEEPSTATE_SIZE -}; - - -/* Byte buffer that will contain symbolic data that is used to supply requests - * for symbolic values (e.g. `int`s). */ -extern volatile uint8_t DeepState_Input[DeepState_InputSize]; - -#define DEEPSTATE_READBYTE ((DeepState_UsingSymExec ? 1 : (DeepState_InputIndex < DeepState_InputInitialized ? 1 : (DeepState_InternalFuzzing ? (DeepState_Input[DeepState_InputIndex] = (char)rand()) : (DeepState_Input[DeepState_InputIndex] = 0)))), DeepState_Input[DeepState_InputIndex++]) - -/* Index into the `DeepState_Input` array that tracks how many input bytes have - * been consumed. */ -extern uint32_t DeepState_InputIndex; -extern uint32_t DeepState_InputInitialized; -extern uint32_t DeepState_InternalFuzzing; - -enum DeepState_SwarmType { - DeepState_SwarmTypePure = 0, - DeepState_SwarmTypeMixed = 1, - DeepState_SwarmTypeProb = 2 -}; - -/* Contains info about a swarm configuration */ -struct DeepState_SwarmConfig { - char* file; - unsigned line; - unsigned orig_fcount; - /* We identify a configuration by these first three elements of the struct */ - - /* These fields allow us to map choices to the restricted configuration */ - unsigned fcount; - unsigned* fmap; -}; - -/* Index into the set of swarm configurations. */ -extern uint32_t DeepState_SwarmConfigsIndex; - -/* Function to return a swarm configuration. */ -extern struct DeepState_SwarmConfig* DeepState_GetSwarmConfig(unsigned fcount, const char* file, unsigned line, enum DeepState_SwarmType stype); - - -#define DEEPSTATE_FOR_EACH_INTEGER(X) \ - X(Size, size_t, size_t) \ - X(Long, long, unsigned long) \ - X(Int64, int64_t, uint64_t) \ - X(UInt64, uint64_t, uint64_t) \ - X(Int, int, unsigned) \ - X(UInt, unsigned, unsigned) \ - X(Short, short, unsigned short) \ - X(UShort, unsigned short, unsigned short) \ - X(Char, char, unsigned char) \ - X(UChar, unsigned char, unsigned char) - -/* Return a symbolic value of a given type. */ -extern int DeepState_Bool(void); - -#define DEEPSTATE_DECLARE(Tname, tname, utname) \ - extern tname DeepState_ ## Tname (void); - -DEEPSTATE_DECLARE(Float, float, void) -DEEPSTATE_DECLARE(Double, double, void) -DEEPSTATE_FOR_EACH_INTEGER(DEEPSTATE_DECLARE) -#undef DEEPSTATE_DECLARE - -/* Returns the minimum satisfiable value for a given symbolic value, given - * the constraints present on that value. */ -extern uint32_t DeepState_MinUInt(uint32_t); -extern int32_t DeepState_MinInt(int32_t); - -extern uint32_t DeepState_MaxUInt(uint32_t); -extern int32_t DeepState_MaxInt(int32_t); - -DEEPSTATE_INLINE static uint16_t DeepState_MinUShort(uint16_t v) { - return DeepState_MinUInt(v); -} - -DEEPSTATE_INLINE static uint8_t DeepState_MinUChar(uint8_t v) { - return (uint8_t) DeepState_MinUInt(v); -} - -DEEPSTATE_INLINE static int16_t DeepState_MinShort(int16_t v) { - return (int16_t) DeepState_MinInt(v); -} - -DEEPSTATE_INLINE static int8_t DeepState_MinChar(int8_t v) { - return (int8_t) DeepState_MinInt(v); -} - -DEEPSTATE_INLINE static uint16_t DeepState_MaxUShort(uint16_t v) { - return (uint16_t) DeepState_MaxUInt(v); -} - -DEEPSTATE_INLINE static uint8_t DeepState_MaxUChar(uint8_t v) { - return (uint8_t) DeepState_MaxUInt(v); -} - -DEEPSTATE_INLINE static int16_t DeepState_MaxShort(int16_t v) { - return (int16_t) DeepState_MaxInt(v); -} - -DEEPSTATE_INLINE static int8_t DeepState_MaxChar(int8_t v) { - return (int8_t) DeepState_MaxInt(v); -} - - -/* Result of a single forked test run. - * Will be passed to the parent process as an exit code. */ -enum DeepState_TestRunResult { - DeepState_TestRunPass = 0, - DeepState_TestRunFail = 1, - DeepState_TestRunCrash = 2, - DeepState_TestRunAbandon = 3, -}; - -/* Contains information about a test case */ -struct DeepState_TestInfo { - struct DeepState_TestInfo *prev; - void (*test_func)(void); - const char *test_name; - const char *file_name; - unsigned line_number; -}; - -struct DeepState_TestRunInfo { - struct DeepState_TestInfo *test; - enum DeepState_TestRunResult result; - const char *reason; -}; - -/* Information about the current test run, if any. */ -extern struct DeepState_TestRunInfo *DeepState_CurrentTestRun; - -/* Function to clean up generated strings, and any other DeepState-managed data. */ -extern void DeepState_CleanUp(); - -/* Returns `1` if `expr` is true, and `0` otherwise. This is kind of an indirect - * way to take a symbolic value, introduce a fork, and on each size, replace its - * value with a concrete value. */ -extern int DeepState_IsTrue(int expr); - -/* Always returns `1`. */ -extern int DeepState_One(void); - -/* Always returns `0`. */ -extern int DeepState_Zero(void); - -/* Always returns `0`. */ -extern int DeepState_ZeroSink(int); - -/* Symbolize the data in the exclusive range `[begin, end)`. */ -extern void DeepState_SymbolizeData(void *begin, void *end); - -/* Symbolize the data in the exclusive range `[begin, end)` with no nulls. */ -extern void DeepState_SymbolizeDataNoNull(void *begin, void *end); - -/* Concretize some data in exclusive the range `[begin, end)`. Returns a - * concrete pointer to the beginning of the concretized data. */ -extern void *DeepState_ConcretizeData(void *begin, void *end); - -/* Assign a symbolic C string of _strlen_ `len` -- with only chars in allowed, - * if `allowed` is non-null; needs space for null + len bytes */ -extern void DeepState_AssignCStr_C(char* str, size_t len, const char* allowed); - -/* Assign a symbolic C string of _strlen_ `len` -- with only chars in allowed, - * if `allowed` is non-null; needs space for null + len bytes */ -extern void DeepState_SwarmAssignCStr_C(const char* file, unsigned line, int mix, - char* str, size_t len, const char* allowed); - -/* Return a symbolic C string of strlen `len`. */ -extern char *DeepState_CStr_C(size_t len, const char* allowed); - -/* Return a symbolic C string of strlen `len`. */ -extern char *DeepState_SwarmCStr_C(const char* file, unsigned line, int mix, - size_t len, const char* allowed); - -/* Symbolize a C string */ -void DeepState_SymbolizeCStr_C(char *begin, const char* allowed); - -/* Symbolize a C string */ -void DeepState_SwarmSymbolizeCStr_C(const char* file, unsigned line, int mix, - char *begin, const char* allowed); - -/* Concretize a C string. Returns a pointer to the beginning of the - * concretized C string. */ -extern const char *DeepState_ConcretizeCStr(const char *begin); - -/* Allocate and return a pointer to `num_bytes` symbolic bytes. */ -extern void *DeepState_Malloc(size_t num_bytes); - -/* Allocate and return a pointer to `num_bytes` symbolic bytes. - Ptr will be freed by DeepState at end of test. */ -extern void *DeepState_GCMalloc(size_t num_bytes); - -/* Initialize the current test run */ -extern void DeepState_InitCurrentTestRun(struct DeepState_TestInfo *test); - -/* Fork and run `test`. Platform specific function. */ -extern enum DeepState_TestRunResult DeepState_ForkAndRunTest(struct DeepState_TestInfo *test); - -/* Portable and architecture-independent memory scrub without dead store elimination. */ -extern void *DeepState_MemScrub(void *pointer, size_t data_size); - -/* Checks if the given path corresponds to a regular file. */ -extern bool DeepState_IsRegularFile(char *path); - -/* Returns the path to a testcase without parsing to any aforementioned types. - * Platform specific function. */ -extern char *DeepState_InputPath(const char* testcase_path); - -#define DEEPSTATE_MAKE_SYMBOLIC_ARRAY(Tname, tname, utname) \ - DEEPSTATE_INLINE static \ - tname *DeepState_Symbolic ## Tname ## Array(size_t num_elms) { \ - tname *arr = (tname *) malloc(sizeof(tname) * num_elms); \ - DeepState_SymbolizeData(arr, &(arr[num_elms])); \ - return arr; \ - } - -DEEPSTATE_FOR_EACH_INTEGER(DEEPSTATE_MAKE_SYMBOLIC_ARRAY) -#undef DEEPSTATE_MAKE_SYMBOLIC_ARRAY - -/* Creates an assumption about a symbolic value. Returns `1` if the assumption - * can hold and was asserted. */ -extern void _DeepState_Assume(int expr, const char *expr_str, const char *file, - unsigned line); - -#define DeepState_Assume(x) _DeepState_Assume(!!(x), #x, __FILE__, __LINE__) - -/* Abandon this test. We've hit some kind of internal problem. */ -DEEPSTATE_NORETURN -extern void DeepState_Abandon(const char *reason); - -/* Mark this test as having crashed. */ -extern void DeepState_Crash(void); - -DEEPSTATE_NORETURN -extern void DeepState_Fail(void); - -/* Mark this test as failing, but don't hard exit. */ -extern void DeepState_SoftFail(void); - -DEEPSTATE_NORETURN -extern void DeepState_Pass(void); - -/* Asserts that `expr` must hold. If it does not, then the test fails and - * immediately stops. */ -DEEPSTATE_INLINE static void DeepState_Assert(int expr) { - if (!expr) { - DeepState_Fail(); - } -} - -/* Used to make DeepState really crash for fuzzers, on any platform. */ -DEEPSTATE_INLINE static void DeepState_HardCrash() { - raise(SIGABRT); -} - -/* Asserts that `expr` must hold. If it does not, then the test fails, but - * nonetheless continues on. */ -DEEPSTATE_INLINE static void DeepState_Check(int expr) { - if (!expr) { - DeepState_SoftFail(); - } -} - -/* Return a symbolic value in a the range `[low_inc, high_inc]`. */ -#ifdef DEEPSTATE_RANGE_BOUNDARY_BIAS - #define DEEPSTATE_MAKE_SYMBOLIC_RANGE(Tname, tname, utname) \ - DEEPSTATE_INLINE static tname DeepState_ ## Tname ## InRange( \ - tname low, tname high) { \ - if (low == high) { \ - return low; \ - } else if (low > high) { \ - const tname copy = high; \ - high = low; \ - low = copy; \ - } \ - tname x = DeepState_ ## Tname(); \ - if (DeepState_UsingSymExec) { \ - (void) DeepState_Assume(low <= x && x <= high); \ - return x; \ - } \ - if (FLAGS_verbose_reads) { \ - printf("Range read low %" PRId64 " high %" PRId64 "\n", \ - (int64_t)low, (int64_t)high); \ - } \ - if (x < low) \ - return low; \ - if (x > high) \ - return high; \ - return x; \ - } -#else - #define DEEPSTATE_MAKE_SYMBOLIC_RANGE(Tname, tname, utname) \ - DEEPSTATE_INLINE static tname DeepState_ ## Tname ## InRange( \ - tname low, tname high) { \ - if (low == high) { \ - return low; \ - } else if (low > high) { \ - const tname copy = high; \ - high = low; \ - low = copy; \ - } \ - tname x = DeepState_ ## Tname(); \ - if (DeepState_UsingSymExec) { \ - (void) DeepState_Assume(low <= x && x <= high); \ - return x; \ - } \ - if (FLAGS_verbose_reads) { \ - printf("Range read low %" PRId64 " high %" PRId64 "\n", \ - (int64_t)low, (int64_t)high); \ - } \ - if ((x < low) || (x > high)) { \ - const utname ux = (utname) x; \ - utname usize; \ - if (__builtin_sub_overflow(high, low, &usize)) { \ - return low; /* Always legal */ \ - } \ - if (__builtin_add_overflow(usize, 1, &usize)) { \ - return high; /* Always legal */ \ - } \ - const utname ux_clamped = ux % usize; \ - const tname x_clamped = (tname) ux_clamped; \ - tname ret; \ - if (__builtin_add_overflow(low, x_clamped, &ret)) { \ - return high; /* Always legal */ \ - } \ - if (FLAGS_verbose_reads) { \ - printf("Converting out-of-range value to %" PRId64 "\n", \ - (int64_t)ret); \ - } \ - return ret; \ - } \ - return x; \ - } -#endif - - -DEEPSTATE_FOR_EACH_INTEGER(DEEPSTATE_MAKE_SYMBOLIC_RANGE) -#undef DEEPSTATE_MAKE_SYMBOLIC_RANGE - -extern float DeepState_FloatInRange(float low, float high); -extern double DeepState_DoubleInRange(double low, double high); - -/* Predicates to check whether or not a particular value is symbolic */ -extern int DeepState_IsSymbolicUInt(uint32_t x); - -/* The following predicates are implemented in terms of `DeepState_IsSymbolicUInt`. - * This simplifies the portability of hooking this predicate interface across - * architectures, because basically all hooking mechanisms know how to get at - * the first integer argument. Passing in floating point values, or 64-bit - * integers on 32-bit architectures, can be more subtle. */ - -DEEPSTATE_INLINE static int DeepState_IsSymbolicInt(int x) { - return DeepState_IsSymbolicUInt((uint32_t) x); -} - -DEEPSTATE_INLINE static int DeepState_IsSymbolicUShort(uint16_t x) { - return DeepState_IsSymbolicUInt((uint32_t) x); -} - -DEEPSTATE_INLINE static int DeepState_IsSymbolicShort(int16_t x) { - return DeepState_IsSymbolicUInt((uint32_t) (uint16_t) x); -} - -DEEPSTATE_INLINE static int DeepState_IsSymbolicUChar(unsigned char x) { - return DeepState_IsSymbolicUInt((uint32_t) x); -} - -DEEPSTATE_INLINE static int DeepState_IsSymbolicChar(char x) { - return DeepState_IsSymbolicUInt((uint32_t) (unsigned char) x); -} - -DEEPSTATE_INLINE static int DeepState_IsSymbolicUInt64(uint64_t x) { - return DeepState_IsSymbolicUInt((uint32_t) x) || - DeepState_IsSymbolicUInt((uint32_t) (x >> 32U)); -} - -DEEPSTATE_INLINE static int DeepState_IsSymbolicInt64(int64_t x) { - return DeepState_IsSymbolicUInt64((uint64_t) x); -} - -DEEPSTATE_INLINE static int DeepState_IsSymbolicBool(int x) { - return DeepState_IsSymbolicInt(x); -} - -DEEPSTATE_INLINE static int DeepState_IsSymbolicFloat(float x) { - return DeepState_IsSymbolicUInt(*((uint32_t *) &x)); -} - -DEEPSTATE_INLINE static int DeepState_IsSymbolicDouble(double x) { - return DeepState_IsSymbolicUInt64(*((uint64_t *) &x)); -} - -/* Basically an ASSUME that also assigns to v; P should be side-effect - free, and type of v should be integral. */ -#ifndef DEEPSTATE_MAX_SEARCH_ITERS -#define DEEPSTATE_MAX_SEARCH_ITERS 4294967296 // 2^32 is enough expense -#endif - -#define ASSIGN_SATISFYING(v, expr, P) \ - do { \ - v = (expr); \ - if (DeepState_UsingSymExec) { \ - (void) DeepState_Assume(P); \ - } else { \ - unsigned long long DeepState_assume_iters = 0; \ - unsigned long long DeepState_safe_incr_v = (unsigned long long) v; \ - unsigned long long DeepState_safe_decr_v = (unsigned long long) v; \ - while(!(P)) { \ - if (DeepState_assume_iters > DEEPSTATE_MAX_SEARCH_ITERS) { \ - (void) DeepState_Assume(0); \ - } \ - DeepState_assume_iters++; \ - DeepState_safe_incr_v++; \ - v = DeepState_safe_incr_v; \ - if (!(P)) { \ - DeepState_safe_decr_v--; \ - v = DeepState_safe_decr_v; \ - } \ - } \ - } \ - } while (0); - -/* Basically an ASSUME that also assigns to v in range low to high; - P should be side-effect free, and type of v should be integral. */ - -#define ASSIGN_SATISFYING_IN_RANGE(v, expr, low, high, P) \ - do { \ - v = (expr); \ - (void) DeepState_Assume(low <= v && v <= high); \ - if (DeepState_UsingSymExec) { \ - (void) DeepState_Assume(P);\ - } else { \ - unsigned long long DeepState_assume_iters = 0; \ - long long DeepState_safe_incr_v = (long long) v; \ - long long DeepState_safe_decr_v = (long long) v; \ - while(!(P)) { \ - if (DeepState_assume_iters > DEEPSTATE_MAX_SEARCH_ITERS) { \ - (void) DeepState_Assume(0); \ - } \ - DeepState_assume_iters++; \ - if (DeepState_safe_incr_v < high) { \ - DeepState_safe_incr_v++; \ - v = DeepState_safe_incr_v; \ - } else if (DeepState_safe_decr_v == low) { \ - (void) DeepState_Assume(0); \ - } \ - if (!(P) && (DeepState_safe_decr_v > low)) { \ - DeepState_safe_decr_v--; \ - v = DeepState_safe_decr_v; \ - } \ - } \ - } \ - } while (0); - -/* Used to define the entrypoint of a test case. */ -#define DeepState_EntryPoint(test_name) \ - _DeepState_EntryPoint(test_name, __FILE__, __LINE__) - - -/* Pointer to the last registered `TestInfo` structure. */ -extern struct DeepState_TestInfo *DeepState_LastTestInfo; - -/* Pointer to first structure of ordered `TestInfo` list (reverse of LastTestInfo). */ -extern struct DeepState_TestInfo *DeepState_FirstTestInfo; - -extern int DeepState_TakeOver(void); - -/* Defines the entrypoint of a test case. This creates a data structure that - * contains the information about the test, and then creates an initializer - * function that runs before `main` that registers the test entrypoint with - * DeepState. */ -#define _DeepState_EntryPoint(test_name, file, line) \ - static void DeepState_Test_ ## test_name (void); \ - static void DeepState_Run_ ## test_name (void) { \ - DeepState_Test_ ## test_name(); \ - DeepState_Pass(); \ - } \ - static struct DeepState_TestInfo DeepState_Info_ ## test_name = { \ - NULL, \ - DeepState_Run_ ## test_name, \ - DEEPSTATE_TO_STR(test_name), \ - file, \ - line, \ - }; \ - DEEPSTATE_INITIALIZER(DeepState_Register_ ## test_name) { \ - DeepState_Info_ ## test_name.prev = DeepState_LastTestInfo; \ - DeepState_LastTestInfo = &(DeepState_Info_ ## test_name); \ - } \ - void DeepState_Test_ ## test_name(void) - -/* Set up DeepState. */ -extern void DeepState_Setup(void); - -/* Tear down DeepState. */ -extern void DeepState_Teardown(void); - -/* Notify that we're about to begin a test. */ -extern void DeepState_Begin(struct DeepState_TestInfo *info); - -/* Return the first test case to run. */ -extern struct DeepState_TestInfo *DeepState_FirstTest(void); - -/* Returns `true` if a failure was caught for the current test case. */ -extern bool DeepState_CatchFail(void); - -/* Returns `true` if the current test case was abandoned. */ -extern bool DeepState_CatchAbandoned(void); - -/* Save a passing test to the output test directory. */ -extern void DeepState_SavePassingTest(void); - -/* Save a failing test to the output test directory. */ -extern void DeepState_SaveFailingTest(void); - -/* Save a crashing test to the output test directory. */ -extern void DeepState_SaveCrashingTest(void); - -/* Jump buffer for returning to `DeepState_Run`. */ -extern jmp_buf DeepState_ReturnToRun; - -/* Checks a filename to see if might be a saved test case. - * - * Valid saved test cases have the suffix `.pass` or `.fail`. */ -static bool DeepState_IsTestCaseFile(const char *name) { - const char *suffix = strchr(name, '.'); - if (suffix == NULL) { - return false; - } - - const char *extensions[] = { - ".pass", - ".fail", - ".crash", - }; - const size_t ext_count = sizeof(extensions) / sizeof(char *); - - for (size_t i = 0; i < ext_count; i++) { - if (!strcmp(suffix, extensions[i])) { - return true; - } - } - - return false; -} - -extern void DeepState_Warn_srand(unsigned int seed); - -/* Resets the global `DeepState_Input` buffer, then fills it with the - * data found in the file `path`. */ -extern void DeepState_InitInputFromFile(const char *path); - -/* Resets the global `DeepState_Input` buffer, then fills it with the - * data found in the file `path`. */ -static void DeepState_InitInputFromStdin() { - - /* Reset the index. */ - DeepState_InputIndex = 0; - DeepState_SwarmConfigsIndex = 0; - - size_t count = read(STDIN_FILENO, (void *) DeepState_Input, DeepState_InputSize); - - DeepState_InputInitialized = count; - - DeepState_LogFormat(DeepState_LogTrace, - "Initialized test input buffer with %zu bytes of data from stdin", - count); -} - -/* Run a test case, assuming we have forked from the test harness to do so. - * - * An exit code of 0 indicates that the test passed. Any other exit - * code, or termination by a signal, indicates a test failure. */ -static void DeepState_RunTest(struct DeepState_TestInfo *test) { - /* Run the test. */ - if (!setjmp(DeepState_ReturnToRun)) { - /* Convert uncaught C++ exceptions into a test failure. */ -#if defined(__cplusplus) && defined(__cpp_exceptions) - try { -#endif /* __cplusplus */ - - test->test_func(); /* Run the test function. */ - exit(DeepState_TestRunPass); - -#if defined(__cplusplus) && defined(__cpp_exceptions) - } catch(...) { - DeepState_Fail(); - } -#endif /* __cplusplus */ - - /* We caught a failure when running the test. */ - } else if (DeepState_CatchFail()) { - DeepState_LogFormat(DeepState_LogError, "Failed: %s", test->test_name); - if (HAS_FLAG_output_test_dir) { - DeepState_SaveFailingTest(); - } - exit(DeepState_TestRunFail); - - /* The test was abandoned. We may have gotten soft failures before - * abandoning, so we prefer to catch those first. */ - } else if (DeepState_CatchAbandoned()) { - DeepState_LogFormat(DeepState_LogTrace, "Abandoned: %s", test->test_name); - exit(DeepState_TestRunAbandon); - - /* The test passed. */ - } else { - DeepState_LogFormat(DeepState_LogTrace, "Passed: %s", test->test_name); - if (HAS_FLAG_output_test_dir) { - if (!FLAGS_fuzz || FLAGS_fuzz_save_passing || FLAGS_random) { - DeepState_SavePassingTest(); - } - } - exit(DeepState_TestRunPass); - } -} - -/* Run a test case, but in libFuzzer, so not inside a fork. */ -static int DeepState_RunTestNoFork(struct DeepState_TestInfo *test) { - /* Run the test. */ - if (!setjmp(DeepState_ReturnToRun)) { - /* Convert uncaught C++ exceptions into a test failure. */ -#if defined(__cplusplus) && defined(__cpp_exceptions) - try { -#endif /* __cplusplus */ - - test->test_func(); /* Run the test function. */ - return(DeepState_TestRunPass); - -#if defined(__cplusplus) && defined(__cpp_exceptions) - } catch(...) { - DeepState_Fail(); - } -#endif /* __cplusplus */ - - /* We caught a failure when running the test. */ - } else if (DeepState_CatchFail()) { - DeepState_LogFormat(DeepState_LogError, "Failed: %s", test->test_name); - if (HAS_FLAG_output_test_dir) { - DeepState_SaveFailingTest(); - } - if (HAS_FLAG_abort_on_fail) { - DeepState_HardCrash(); - } - return(DeepState_TestRunFail); - - /* The test was abandoned. We may have gotten soft failures before - * abandoning, so we prefer to catch those first. */ - } else if (DeepState_CatchAbandoned()) { - DeepState_LogFormat(DeepState_LogTrace, "Abandoned: %s", test->test_name); - return(DeepState_TestRunAbandon); - - /* The test passed. */ - } else { - DeepState_LogFormat(DeepState_LogTrace, "Passed: %s", test->test_name); - if (HAS_FLAG_output_test_dir) { - if (!FLAGS_fuzz || FLAGS_fuzz_save_passing) { - DeepState_SavePassingTest(); - } - } - return(DeepState_TestRunPass); - } -} - -extern enum DeepState_TestRunResult DeepState_FuzzOneTestCase(struct DeepState_TestInfo *test); - -/* Run a single saved test case with input initialized from the file - * `name` in directory `dir`. */ -static enum DeepState_TestRunResult -DeepState_RunSavedTestCase(struct DeepState_TestInfo *test, const char *dir, - const char *name) { - if (!setjmp(DeepState_ReturnToRun)) { - size_t path_len = 2 + sizeof(char) * (strlen(dir) + strlen(name)); - char *path = (char *) malloc(path_len); - if (path == NULL) { - DeepState_Abandon("Error allocating memory"); - } - if (strncmp(dir, "", strlen(dir)) != 0) { - snprintf(path, path_len, "%s/%s", dir, name); - } else { - snprintf(path, path_len, "%s", name); - } - - if (!(strncmp(name, "** STDIN **", strlen(name)) == 0)) { - DeepState_InitInputFromFile(path); - } else { - DeepState_InitInputFromStdin(); - } - - DeepState_Begin(test); - - enum DeepState_TestRunResult result = DeepState_ForkAndRunTest(test); - - if (result == DeepState_TestRunFail) { - DeepState_LogFormat(DeepState_LogError, "Test case %s failed", path); - free(path); - } - else if (result == DeepState_TestRunCrash) { - DeepState_LogFormat(DeepState_LogError, "Crashed: %s", test->test_name); - DeepState_LogFormat(DeepState_LogError, "Test case %s crashed", path); - free(path); - if (HAS_FLAG_output_test_dir) { - DeepState_SaveCrashingTest(); - } - - DeepState_Crash(); - } else { - free(path); - } - - return result; - } else { - DeepState_LogFormat(DeepState_LogError, "Something went wrong running the test case %s", name); - return DeepState_TestRunCrash; - } -} - -/* Run a single test many times, initialized against each saved test case in - * `FLAGS_input_test_dir`. */ -static int DeepState_RunSavedCasesForTest(struct DeepState_TestInfo *test) { - int num_failed_tests = 0; - const char *test_file_name = basename((char *) test->file_name); - - size_t test_case_dir_len = 3 + strlen(FLAGS_input_test_dir) - + strlen(test_file_name) + strlen(test->test_name); - char *test_case_dir = (char *) malloc(test_case_dir_len); - if (test_case_dir == NULL) { - DeepState_Abandon("Error allocating memory"); - } - snprintf(test_case_dir, test_case_dir_len, "%s/%s/%s", - FLAGS_input_test_dir, test_file_name, test->test_name); - - struct dirent *dp; - DIR *dir_fd; - - dir_fd = opendir(test_case_dir); - if (dir_fd == NULL) { - DeepState_LogFormat(DeepState_LogInfo, - "Skipping test `%s`, no saved test cases", - test->test_name); - free(test_case_dir); - return 0; - } - - unsigned int i = 0; - - /* Read generated test cases and run a test for each file found. */ - while ((dp = readdir(dir_fd)) != NULL) { - if (DeepState_IsTestCaseFile(dp->d_name)) { - i++; - enum DeepState_TestRunResult result = - DeepState_RunSavedTestCase(test, test_case_dir, dp->d_name); - - if (result != DeepState_TestRunPass) { - num_failed_tests++; - } - } - } - closedir(dir_fd); - free(test_case_dir); - - DeepState_LogFormat(DeepState_LogInfo, "Ran %u tests for %s; %d tests failed", - i, test->test_name, num_failed_tests); - - return num_failed_tests; -} - -/* Returns a sorted list of all available tests to run, and exits after */ -static int DeepState_RunListTests(void) { - char buff[4096]; - ssize_t write_len = 0; - - int total_test_count = 0; - int boring_count = 0; - int disabled_count = 0; - - struct DeepState_TestInfo *current_test = DeepState_FirstTestInfo; - - sprintf(buff, "Available Tests:\n\n"); - write_len = write(STDERR_FILENO, buff, strlen(buff)); - - /* Print each test and increment counter from linked list */ - for (; current_test != NULL; current_test = current_test->prev) { - - const char * curr_test = current_test->test_name; - - /* Classify tests */ - if (strstr(curr_test, "Boring") || strstr(curr_test, "BORING")) { - boring_count++; - } else if (strstr(curr_test, "Disabled") || strstr(curr_test, "DISABLED")) { - disabled_count++; - } - - /* TODO(alan): also output file name, luckily its sorted :) */ - sprintf(buff, " * %s (line %d)\n", curr_test, current_test->line_number); - write_len = write(STDERR_FILENO, buff, strlen(buff)); - total_test_count++; - } - - sprintf(buff, "\nBoring Tests: %d\nDisabled Tests: %d\n", boring_count, disabled_count); - write_len = write(STDERR_FILENO, buff, strlen(buff)); - - sprintf(buff, "\nTotal Number of Tests: %d\n", total_test_count); - write_len = write(STDERR_FILENO, buff, strlen(buff)); - return 0; -} - -/* Run test from `FLAGS_input_test_file`, under `FLAGS_input_which_test` - * or first test, if not defined. */ -static int DeepState_RunSingleSavedTestCase(void) { - int num_failed_tests = 0; - struct DeepState_TestInfo *test = NULL; - - for (test = DeepState_FirstTest(); test != NULL; test = test->prev) { - if (HAS_FLAG_input_which_test) { - if (strcmp(FLAGS_input_which_test, test->test_name) == 0) { - break; - } - } else { - DeepState_LogFormat(DeepState_LogWarning, - "No test specified, defaulting to first test defined (%s)", - test->test_name); - break; - } - } - - if (test == NULL) { - DeepState_LogFormat(DeepState_LogInfo, - "Could not find matching test for %s", - FLAGS_input_which_test); - return 0; - } - - enum DeepState_TestRunResult result = - DeepState_RunSavedTestCase(test, "", FLAGS_input_test_file); - - if ((result == DeepState_TestRunFail) || (result == DeepState_TestRunCrash)) { - if (FLAGS_abort_on_fail) { - DeepState_HardCrash(); - } - if (FLAGS_exit_on_fail) { - exit(255); // Terminate the testing - } - num_failed_tests++; - } - - DeepState_Teardown(); - - return num_failed_tests; -} - -/* Run test from stdin, under `FLAGS_input_which_test` - * or first test, if not defined. */ -static int DeepState_RunTestFromStdin(void) { - int num_failed_tests = 0; - struct DeepState_TestInfo *test = NULL; - - for (test = DeepState_FirstTest(); test != NULL; test = test->prev) { - if (HAS_FLAG_input_which_test) { - if (strcmp(FLAGS_input_which_test, test->test_name) == 0) { - break; - } - } else { - DeepState_LogFormat(DeepState_LogWarning, - "No test specified, defaulting to first test defined (%s)", - test->test_name); - break; - } - } - - if (test == NULL) { - DeepState_LogFormat(DeepState_LogInfo, - "Could not find matching test for %s", - FLAGS_input_which_test); - return 0; - } - - enum DeepState_TestRunResult result = - DeepState_RunSavedTestCase(test, "", "** STDIN **"); - - if ((result == DeepState_TestRunFail) || (result == DeepState_TestRunCrash)) { - if (FLAGS_abort_on_fail) { - DeepState_HardCrash(); - } - if (FLAGS_exit_on_fail) { - exit(255); // Terminate the testing - } - num_failed_tests++; - } - - DeepState_Teardown(); - - return num_failed_tests; -} - -extern int DeepState_Fuzz(void); - -/* Run tests from `FLAGS_input_test_files_dir`, under `FLAGS_input_which_test` - * or first test, if not defined. */ -static int DeepState_RunSingleSavedTestDir(void) { - int num_failed_tests = 0; - struct DeepState_TestInfo *test = NULL; - - if (!HAS_FLAG_min_log_level) { - FLAGS_min_log_level = 2; - } - - for (test = DeepState_FirstTest(); test != NULL; test = test->prev) { - if (HAS_FLAG_input_which_test) { - if (strcmp(FLAGS_input_which_test, test->test_name) == 0) { - break; - } - } else { - DeepState_LogFormat(DeepState_LogWarning, - "No test specified, defaulting to first test defined (%s)", - test->test_name); - break; - } - } - - if (test == NULL) { - DeepState_LogFormat(DeepState_LogInfo, - "Could not find matching test for %s", - FLAGS_input_which_test); - return 0; - } - - struct dirent *dp; - DIR *dir_fd; - - #if defined(__unix) - struct stat path_stat; - #endif - - dir_fd = opendir(FLAGS_input_test_files_dir); - if (dir_fd == NULL) { - DeepState_LogFormat(DeepState_LogInfo, - "No tests to run"); - return 0; - } - - unsigned int i = 0; - - /* Read generated test cases and run a test for each file found. */ - while ((dp = readdir(dir_fd)) != NULL) { - size_t path_len = 2 + sizeof(char) * (strlen(FLAGS_input_test_files_dir) + strlen(dp->d_name)); - char *path = (char *) malloc(path_len); - snprintf(path, path_len, "%s/%s", FLAGS_input_test_files_dir, dp->d_name); - - if (!DeepState_IsRegularFile(path)){ - continue; - } - - i++; - enum DeepState_TestRunResult result = - DeepState_RunSavedTestCase(test, FLAGS_input_test_files_dir, dp->d_name); - - if ((result == DeepState_TestRunFail) || (result == DeepState_TestRunCrash)) { - if (FLAGS_abort_on_fail) { - DeepState_HardCrash(); - } - if (FLAGS_exit_on_fail) { - exit(255); // Terminate the testing - } - num_failed_tests++; - } - } - closedir(dir_fd); - - DeepState_LogFormat(DeepState_LogInfo, "Ran %u tests; %d tests failed", - i, num_failed_tests); - - return num_failed_tests; -} - -/* Run test `FLAGS_input_which_test` with saved input from `FLAGS_input_test_file`. - * - * For each test unit and case, see if there are input files in the - * expected directories. If so, use them to initialize - * `DeepState_Input`, then run the test. If not, skip the test. */ -static int DeepState_RunSavedTestCases(void) { - int num_failed_tests = 0; - struct DeepState_TestInfo *test = NULL; - - if (!HAS_FLAG_min_log_level) { - FLAGS_min_log_level = 2; - } - - for (test = DeepState_FirstTest(); test != NULL; test = test->prev) { - num_failed_tests += DeepState_RunSavedCasesForTest(test); - } - - DeepState_Teardown(); - - return num_failed_tests; -} - -/* Start DeepState and run the tests. Returns the number of failed tests. */ -static int DeepState_Run(void) { - if (!DeepState_OptionsAreInitialized) { - DeepState_Abandon("Please call DeepState_InitOptions(argc, argv) in main"); - } - - if (HAS_FLAG_list_tests) { - return DeepState_RunListTests(); - } - - ENABLE_DIRECT_RUN_FLAG; - - if (HAS_FLAG_input_test_file) { - return DeepState_RunSingleSavedTestCase(); - } - - if (HAS_FLAG_input_stdin) { - return DeepState_RunTestFromStdin(); - } - - if (HAS_FLAG_input_test_dir) { - return DeepState_RunSavedTestCases(); - } - - if (HAS_FLAG_input_test_files_dir) { - return DeepState_RunSingleSavedTestDir(); - } - - if (FLAGS_fuzz || FLAGS_random) { - return DeepState_Fuzz(); - } - - int num_failed_tests = 0; - struct DeepState_TestInfo *test = NULL; - - - for (test = DeepState_FirstTest(); test != NULL; test = test->prev) { - - const char * curr_test = test->test_name; - - /* Run only the Boring* tests */ - if (HAS_FLAG_boring_only) { - if (strstr(curr_test, "Boring") || strstr(curr_test, "BORING")) { - DeepState_Begin(test); - if (DeepState_ForkAndRunTest(test) != 0) { - num_failed_tests++; - } - } else { - continue; - } - } - - /* Check if pattern match exists in test, skip if not */ - if (HAS_FLAG_test_filter) { - if (REG_MATCH(FLAGS_test_filter, curr_test)){ - continue; - } - } - - /* Check if --run_disabled is set, and if not, skip Disabled* tests */ - if (!HAS_FLAG_run_disabled) { - if (strstr(curr_test, "Disabled") || strstr(test->test_name, "DISABLED")) { - continue; - } - } - - DeepState_Begin(test); - if (DeepState_ForkAndRunTest(test) != 0) { - num_failed_tests++; - } - } - - DeepState_Teardown(); - - return num_failed_tests; -} - -DEEPSTATE_END_EXTERN_C - -#endif /* SRC_INCLUDE_DEEPSTATE_DEEPSTATE_H_ */ +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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. + */ + +#ifndef SRC_INCLUDE_DEEPSTATE_DEEPSTATE_H_ +#define SRC_INCLUDE_DEEPSTATE_DEEPSTATE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef assert +# undef assert +#endif + +#define assert DeepState_Assert +#define assume DeepState_Assume +#define check DeepState_Check + +#ifdef DEEPSTATE_TAKEOVER_RAND +#define rand DeepState_RandInt +#define srand DeepState_Warn_srand +#endif + +#ifndef DEEPSTATE_SIZE +#define DEEPSTATE_SIZE 32768 +#endif + +#ifndef DEEPSTATE_MAX_SWARM_CONFIGS +#define DEEPSTATE_MAX_SWARM_CONFIGS 1024 +#endif + +#ifndef DEEPSTATE_SWARM_MAX_PROB_RATIO +#define DEEPSTATE_SWARM_MAX_PROB_RATIO 16 +#endif + +#define MAYBE(...) \ + if (DeepState_Bool()) { \ + __VA_ARGS__ ; \ + } + +DEEPSTATE_BEGIN_EXTERN_C + +DECLARE_string(input_test_dir); +DECLARE_string(input_test_file); +DECLARE_string(input_test_files_dir); +DECLARE_string(input_which_test); +DECLARE_string(output_test_dir); +DECLARE_string(test_filter); + +DECLARE_bool(input_stdin); +DECLARE_bool(take_over); +DECLARE_bool(abort_on_fail); +DECLARE_bool(exit_on_fail); +DECLARE_bool(verbose_reads); +DECLARE_bool(fuzz); +DECLARE_bool(random); +DECLARE_bool(fuzz_save_passing); +DECLARE_bool(fork); +DECLARE_bool(list_tests); +DECLARE_bool(boring_only); +DECLARE_bool(run_disabled); + +DECLARE_int(min_log_level); +DECLARE_int(seed); +DECLARE_int(timeout); + +enum { + DeepState_InputSize = DEEPSTATE_SIZE +}; + + +/* Byte buffer that will contain symbolic data that is used to supply requests + * for symbolic values (e.g. `int`s). */ +extern volatile uint8_t DeepState_Input[DeepState_InputSize]; +extern DeepStateRng DeepState_Rng; + +#define DEEPSTATE_READBYTE \ + ((DeepState_UsingSymExec ? 1 \ + : (DeepState_InputIndex < DeepState_InputInitialized ? 1 \ + : (DeepState_InternalFuzzing \ + ? (DeepState_Input[DeepState_InputIndex] = (uint8_t)deepstate_rng_next(&DeepState_Rng)) \ + : (DeepState_Input[DeepState_InputIndex] = 0)))), \ + DeepState_Input[DeepState_InputIndex++]) + +/* Index into the `DeepState_Input` array that tracks how many input bytes have + * been consumed. */ +extern uint32_t DeepState_InputIndex; +extern uint32_t DeepState_InputInitialized; +extern uint32_t DeepState_InternalFuzzing; + +enum DeepState_SwarmType { + DeepState_SwarmTypePure = 0, + DeepState_SwarmTypeMixed = 1, + DeepState_SwarmTypeProb = 2 +}; + +/* Contains info about a swarm configuration */ +struct DeepState_SwarmConfig { + char* file; + unsigned line; + unsigned orig_fcount; + /* We identify a configuration by these first three elements of the struct */ + + /* These fields allow us to map choices to the restricted configuration */ + unsigned fcount; + unsigned* fmap; +}; + +/* Index into the set of swarm configurations. */ +extern uint32_t DeepState_SwarmConfigsIndex; + +/* Function to return a swarm configuration. */ +extern struct DeepState_SwarmConfig* DeepState_GetSwarmConfig(unsigned fcount, const char* file, unsigned line, enum DeepState_SwarmType stype); + + +#define DEEPSTATE_FOR_EACH_INTEGER(X) \ + X(Size, size_t, size_t) \ + X(Long, long, unsigned long) \ + X(Int64, int64_t, uint64_t) \ + X(UInt64, uint64_t, uint64_t) \ + X(Int, int, unsigned) \ + X(UInt, unsigned, unsigned) \ + X(Short, short, unsigned short) \ + X(UShort, unsigned short, unsigned short) \ + X(Char, char, unsigned char) \ + X(UChar, unsigned char, unsigned char) + +/* Return a symbolic value of a given type. */ +extern int DeepState_Bool(void); + +#define DEEPSTATE_DECLARE(Tname, tname, utname) \ + extern tname DeepState_ ## Tname (void); + +DEEPSTATE_DECLARE(Float, float, void) +DEEPSTATE_DECLARE(Double, double, void) +DEEPSTATE_FOR_EACH_INTEGER(DEEPSTATE_DECLARE) +#undef DEEPSTATE_DECLARE + +/* Returns the minimum satisfiable value for a given symbolic value, given + * the constraints present on that value. */ +extern uint32_t DeepState_MinUInt(uint32_t); +extern int32_t DeepState_MinInt(int32_t); + +extern uint32_t DeepState_MaxUInt(uint32_t); +extern int32_t DeepState_MaxInt(int32_t); + +DEEPSTATE_INLINE static uint16_t DeepState_MinUShort(uint16_t v) { + return DeepState_MinUInt(v); +} + +DEEPSTATE_INLINE static uint8_t DeepState_MinUChar(uint8_t v) { + return (uint8_t) DeepState_MinUInt(v); +} + +DEEPSTATE_INLINE static int16_t DeepState_MinShort(int16_t v) { + return (int16_t) DeepState_MinInt(v); +} + +DEEPSTATE_INLINE static int8_t DeepState_MinChar(int8_t v) { + return (int8_t) DeepState_MinInt(v); +} + +DEEPSTATE_INLINE static uint16_t DeepState_MaxUShort(uint16_t v) { + return (uint16_t) DeepState_MaxUInt(v); +} + +DEEPSTATE_INLINE static uint8_t DeepState_MaxUChar(uint8_t v) { + return (uint8_t) DeepState_MaxUInt(v); +} + +DEEPSTATE_INLINE static int16_t DeepState_MaxShort(int16_t v) { + return (int16_t) DeepState_MaxInt(v); +} + +DEEPSTATE_INLINE static int8_t DeepState_MaxChar(int8_t v) { + return (int8_t) DeepState_MaxInt(v); +} + + +/* Result of a single forked test run. + * Will be passed to the parent process as an exit code. */ +enum DeepState_TestRunResult { + DeepState_TestRunPass = 0, + DeepState_TestRunFail = 1, + DeepState_TestRunCrash = 2, + DeepState_TestRunAbandon = 3, +}; + +/* Contains information about a test case */ +struct DeepState_TestInfo { + struct DeepState_TestInfo *prev; + void (*test_func)(void); + const char *test_name; + const char *file_name; + unsigned line_number; +}; + +struct DeepState_TestRunInfo { + struct DeepState_TestInfo *test; + enum DeepState_TestRunResult result; + const char *reason; +}; + +/* Information about the current test run, if any. */ +extern struct DeepState_TestRunInfo *DeepState_CurrentTestRun; + +/* Function to clean up generated strings, and any other DeepState-managed data. */ +extern void DeepState_CleanUp(); + +/* Returns `1` if `expr` is true, and `0` otherwise. This is kind of an indirect + * way to take a symbolic value, introduce a fork, and on each size, replace its + * value with a concrete value. */ +extern int DeepState_IsTrue(int expr); + +/* Always returns `1`. */ +extern int DeepState_One(void); + +/* Always returns `0`. */ +extern int DeepState_Zero(void); + +/* Always returns `0`. */ +extern int DeepState_ZeroSink(int); + +/* Symbolize the data in the exclusive range `[begin, end)`. */ +extern void DeepState_SymbolizeData(void *begin, void *end); + +/* Symbolize the data in the exclusive range `[begin, end)` with no nulls. */ +extern void DeepState_SymbolizeDataNoNull(void *begin, void *end); + +/* Concretize some data in exclusive the range `[begin, end)`. Returns a + * concrete pointer to the beginning of the concretized data. */ +extern void *DeepState_ConcretizeData(void *begin, void *end); + +/* Assign a symbolic C string of _strlen_ `len` -- with only chars in allowed, + * if `allowed` is non-null; needs space for null + len bytes */ +extern void DeepState_AssignCStr_C(char* str, size_t len, const char* allowed); + +/* Assign a symbolic C string of _strlen_ `len` -- with only chars in allowed, + * if `allowed` is non-null; needs space for null + len bytes */ +extern void DeepState_SwarmAssignCStr_C(const char* file, unsigned line, int mix, + char* str, size_t len, const char* allowed); + +/* Return a symbolic C string of strlen `len`. */ +extern char *DeepState_CStr_C(size_t len, const char* allowed); + +/* Return a symbolic C string of strlen `len`. */ +extern char *DeepState_SwarmCStr_C(const char* file, unsigned line, int mix, + size_t len, const char* allowed); + +/* Symbolize a C string */ +void DeepState_SymbolizeCStr_C(char *begin, const char* allowed); + +/* Symbolize a C string */ +void DeepState_SwarmSymbolizeCStr_C(const char* file, unsigned line, int mix, + char *begin, const char* allowed); + +/* Concretize a C string. Returns a pointer to the beginning of the + * concretized C string. */ +extern const char *DeepState_ConcretizeCStr(const char *begin); + +/* Allocate and return a pointer to `num_bytes` symbolic bytes. */ +extern void *DeepState_Malloc(size_t num_bytes); + +/* Allocate and return a pointer to `num_bytes` symbolic bytes. + Ptr will be freed by DeepState at end of test. */ +extern void *DeepState_GCMalloc(size_t num_bytes); + +/* Initialize the current test run */ +extern void DeepState_InitCurrentTestRun(struct DeepState_TestInfo *test); + +/* Fork and run `test`. Platform specific function. */ +extern enum DeepState_TestRunResult DeepState_ForkAndRunTest(struct DeepState_TestInfo *test); + +/* Portable and architecture-independent memory scrub without dead store elimination. */ +extern void *DeepState_MemScrub(void *pointer, size_t data_size); + +/* Checks if the given path corresponds to a regular file. */ +extern bool DeepState_IsRegularFile(char *path); + +/* Returns the path to a testcase without parsing to any aforementioned types. + * Platform specific function. */ +extern char *DeepState_InputPath(const char* testcase_path); + +#define DEEPSTATE_MAKE_SYMBOLIC_ARRAY(Tname, tname, utname) \ + DEEPSTATE_INLINE static \ + tname *DeepState_Symbolic ## Tname ## Array(size_t num_elms) { \ + tname *arr = (tname *) malloc(sizeof(tname) * num_elms); \ + DeepState_SymbolizeData(arr, &(arr[num_elms])); \ + return arr; \ + } + +DEEPSTATE_FOR_EACH_INTEGER(DEEPSTATE_MAKE_SYMBOLIC_ARRAY) +#undef DEEPSTATE_MAKE_SYMBOLIC_ARRAY + +/* Creates an assumption about a symbolic value. Returns `1` if the assumption + * can hold and was asserted. */ +extern void _DeepState_Assume(int expr, const char *expr_str, const char *file, + unsigned line); + +#define DeepState_Assume(x) _DeepState_Assume(!!(x), #x, __FILE__, __LINE__) + +/* Abandon this test. We've hit some kind of internal problem. */ +DEEPSTATE_NORETURN +extern void DeepState_Abandon(const char *reason); + +/* Mark this test as having crashed. */ +extern void DeepState_Crash(void); + +DEEPSTATE_NORETURN +extern void DeepState_Fail(void); + +/* Mark this test as failing, but don't hard exit. */ +extern void DeepState_SoftFail(void); + +DEEPSTATE_NORETURN +extern void DeepState_Pass(void); + +/* Asserts that `expr` must hold. If it does not, then the test fails and + * immediately stops. */ +DEEPSTATE_INLINE static void DeepState_Assert(int expr) { + if (!expr) { + DeepState_Fail(); + } +} + +/* Used to make DeepState really crash for fuzzers, on any platform. */ +DEEPSTATE_INLINE static void DeepState_HardCrash() { + raise(SIGABRT); +} + +/* Asserts that `expr` must hold. If it does not, then the test fails, but + * nonetheless continues on. */ +DEEPSTATE_INLINE static void DeepState_Check(int expr) { + if (!expr) { + DeepState_SoftFail(); + } +} + +/* Return a symbolic value in a the range `[low_inc, high_inc]`. */ +#ifdef DEEPSTATE_RANGE_BOUNDARY_BIAS + #define DEEPSTATE_MAKE_SYMBOLIC_RANGE(Tname, tname, utname) \ + DEEPSTATE_INLINE static tname DeepState_ ## Tname ## InRange( \ + tname low, tname high) { \ + if (low == high) { \ + return low; \ + } else if (low > high) { \ + const tname copy = high; \ + high = low; \ + low = copy; \ + } \ + tname x = DeepState_ ## Tname(); \ + if (DeepState_UsingSymExec) { \ + (void) DeepState_Assume(low <= x && x <= high); \ + return x; \ + } \ + if (FLAGS_verbose_reads) { \ + printf("Range read low %" PRId64 " high %" PRId64 "\n", \ + (int64_t)low, (int64_t)high); \ + } \ + if (x < low) \ + return low; \ + if (x > high) \ + return high; \ + return x; \ + } +#else + #define DEEPSTATE_MAKE_SYMBOLIC_RANGE(Tname, tname, utname) \ + DEEPSTATE_INLINE static tname DeepState_ ## Tname ## InRange( \ + tname low, tname high) { \ + if (low == high) { \ + return low; \ + } else if (low > high) { \ + const tname copy = high; \ + high = low; \ + low = copy; \ + } \ + tname x = DeepState_ ## Tname(); \ + if (DeepState_UsingSymExec) { \ + (void) DeepState_Assume(low <= x && x <= high); \ + return x; \ + } \ + if (FLAGS_verbose_reads) { \ + printf("Range read low %" PRId64 " high %" PRId64 "\n", \ + (int64_t)low, (int64_t)high); \ + } \ + if ((x < low) || (x > high)) { \ + const utname ux = (utname) x; \ + utname usize; \ + if (__builtin_sub_overflow(high, low, &usize)) { \ + return low; /* Always legal */ \ + } \ + if (__builtin_add_overflow(usize, 1, &usize)) { \ + return high; /* Always legal */ \ + } \ + const utname ux_clamped = ux % usize; \ + const tname x_clamped = (tname) ux_clamped; \ + tname ret; \ + if (__builtin_add_overflow(low, x_clamped, &ret)) { \ + return high; /* Always legal */ \ + } \ + if (FLAGS_verbose_reads) { \ + printf("Converting out-of-range value to %" PRId64 "\n", \ + (int64_t)ret); \ + } \ + return ret; \ + } \ + return x; \ + } +#endif + + +DEEPSTATE_FOR_EACH_INTEGER(DEEPSTATE_MAKE_SYMBOLIC_RANGE) +#undef DEEPSTATE_MAKE_SYMBOLIC_RANGE + +extern float DeepState_FloatInRange(float low, float high); +extern double DeepState_DoubleInRange(double low, double high); + +/* Predicates to check whether or not a particular value is symbolic */ +extern int DeepState_IsSymbolicUInt(uint32_t x); + +/* The following predicates are implemented in terms of `DeepState_IsSymbolicUInt`. + * This simplifies the portability of hooking this predicate interface across + * architectures, because basically all hooking mechanisms know how to get at + * the first integer argument. Passing in floating point values, or 64-bit + * integers on 32-bit architectures, can be more subtle. */ + +DEEPSTATE_INLINE static int DeepState_IsSymbolicInt(int x) { + return DeepState_IsSymbolicUInt((uint32_t) x); +} + +DEEPSTATE_INLINE static int DeepState_IsSymbolicUShort(uint16_t x) { + return DeepState_IsSymbolicUInt((uint32_t) x); +} + +DEEPSTATE_INLINE static int DeepState_IsSymbolicShort(int16_t x) { + return DeepState_IsSymbolicUInt((uint32_t) (uint16_t) x); +} + +DEEPSTATE_INLINE static int DeepState_IsSymbolicUChar(unsigned char x) { + return DeepState_IsSymbolicUInt((uint32_t) x); +} + +DEEPSTATE_INLINE static int DeepState_IsSymbolicChar(char x) { + return DeepState_IsSymbolicUInt((uint32_t) (unsigned char) x); +} + +DEEPSTATE_INLINE static int DeepState_IsSymbolicUInt64(uint64_t x) { + return DeepState_IsSymbolicUInt((uint32_t) x) || + DeepState_IsSymbolicUInt((uint32_t) (x >> 32U)); +} + +DEEPSTATE_INLINE static int DeepState_IsSymbolicInt64(int64_t x) { + return DeepState_IsSymbolicUInt64((uint64_t) x); +} + +DEEPSTATE_INLINE static int DeepState_IsSymbolicBool(int x) { + return DeepState_IsSymbolicInt(x); +} + +DEEPSTATE_INLINE static int DeepState_IsSymbolicFloat(float x) { + return DeepState_IsSymbolicUInt(*((uint32_t *) &x)); +} + +DEEPSTATE_INLINE static int DeepState_IsSymbolicDouble(double x) { + return DeepState_IsSymbolicUInt64(*((uint64_t *) &x)); +} + +/* Basically an ASSUME that also assigns to v; P should be side-effect + free, and type of v should be integral. */ +#ifndef DEEPSTATE_MAX_SEARCH_ITERS +#define DEEPSTATE_MAX_SEARCH_ITERS 4294967296 // 2^32 is enough expense +#endif + +#define ASSIGN_SATISFYING(v, expr, P) \ + do { \ + v = (expr); \ + if (DeepState_UsingSymExec) { \ + (void) DeepState_Assume(P); \ + } else { \ + unsigned long long DeepState_assume_iters = 0; \ + unsigned long long DeepState_safe_incr_v = (unsigned long long) v; \ + unsigned long long DeepState_safe_decr_v = (unsigned long long) v; \ + while(!(P)) { \ + if (DeepState_assume_iters > DEEPSTATE_MAX_SEARCH_ITERS) { \ + (void) DeepState_Assume(0); \ + } \ + DeepState_assume_iters++; \ + DeepState_safe_incr_v++; \ + v = DeepState_safe_incr_v; \ + if (!(P)) { \ + DeepState_safe_decr_v--; \ + v = DeepState_safe_decr_v; \ + } \ + } \ + } \ + } while (0); + +/* Basically an ASSUME that also assigns to v in range low to high; + P should be side-effect free, and type of v should be integral. */ + +#define ASSIGN_SATISFYING_IN_RANGE(v, expr, low, high, P) \ + do { \ + v = (expr); \ + (void) DeepState_Assume(low <= v && v <= high); \ + if (DeepState_UsingSymExec) { \ + (void) DeepState_Assume(P);\ + } else { \ + unsigned long long DeepState_assume_iters = 0; \ + long long DeepState_safe_incr_v = (long long) v; \ + long long DeepState_safe_decr_v = (long long) v; \ + while(!(P)) { \ + if (DeepState_assume_iters > DEEPSTATE_MAX_SEARCH_ITERS) { \ + (void) DeepState_Assume(0); \ + } \ + DeepState_assume_iters++; \ + if (DeepState_safe_incr_v < high) { \ + DeepState_safe_incr_v++; \ + v = DeepState_safe_incr_v; \ + } else if (DeepState_safe_decr_v == low) { \ + (void) DeepState_Assume(0); \ + } \ + if (!(P) && (DeepState_safe_decr_v > low)) { \ + DeepState_safe_decr_v--; \ + v = DeepState_safe_decr_v; \ + } \ + } \ + } \ + } while (0); + +/* Used to define the entrypoint of a test case. */ +#define DeepState_EntryPoint(test_name) \ + _DeepState_EntryPoint(test_name, __FILE__, __LINE__) + + +/* Pointer to the last registered `TestInfo` structure. */ +extern struct DeepState_TestInfo *DeepState_LastTestInfo; + +/* Pointer to first structure of ordered `TestInfo` list (reverse of LastTestInfo). */ +extern struct DeepState_TestInfo *DeepState_FirstTestInfo; + +extern int DeepState_TakeOver(void); + +/* Defines the entrypoint of a test case. This creates a data structure that + * contains the information about the test, and then creates an initializer + * function that runs before `main` that registers the test entrypoint with + * DeepState. */ +#define _DeepState_EntryPoint(test_name, file, line) \ + static void DeepState_Test_ ## test_name (void); \ + static void DeepState_Run_ ## test_name (void) { \ + DeepState_Test_ ## test_name(); \ + DeepState_Pass(); \ + } \ + static struct DeepState_TestInfo DeepState_Info_ ## test_name = { \ + NULL, \ + DeepState_Run_ ## test_name, \ + DEEPSTATE_TO_STR(test_name), \ + file, \ + line, \ + }; \ + DEEPSTATE_INITIALIZER(DeepState_Register_ ## test_name) { \ + DeepState_Info_ ## test_name.prev = DeepState_LastTestInfo; \ + DeepState_LastTestInfo = &(DeepState_Info_ ## test_name); \ + } \ + void DeepState_Test_ ## test_name(void) + +/* Set up DeepState. */ +extern void DeepState_Setup(void); + +/* Tear down DeepState. */ +extern void DeepState_Teardown(void); + +/* Notify that we're about to begin a test. */ +extern void DeepState_Begin(struct DeepState_TestInfo *info); + +/* Return the first test case to run. */ +extern struct DeepState_TestInfo *DeepState_FirstTest(void); + +/* Returns `true` if a failure was caught for the current test case. */ +extern bool DeepState_CatchFail(void); + +/* Returns `true` if the current test case was abandoned. */ +extern bool DeepState_CatchAbandoned(void); + +/* Save a passing test to the output test directory. */ +extern void DeepState_SavePassingTest(void); + +/* Save a failing test to the output test directory. */ +extern void DeepState_SaveFailingTest(void); + +/* Save a crashing test to the output test directory. */ +extern void DeepState_SaveCrashingTest(void); + +/* Jump buffer for returning to `DeepState_Run`. */ +extern jmp_buf DeepState_ReturnToRun; + +/* Checks a filename to see if might be a saved test case. + * + * Valid saved test cases have the suffix `.pass` or `.fail`. */ +static bool DeepState_IsTestCaseFile(const char *name) { + const char *suffix = strchr(name, '.'); + if (suffix == NULL) { + return false; + } + + const char *extensions[] = { + ".pass", + ".fail", + ".crash", + }; + const size_t ext_count = sizeof(extensions) / sizeof(char *); + + for (size_t i = 0; i < ext_count; i++) { + if (!strcmp(suffix, extensions[i])) { + return true; + } + } + + return false; +} + +extern void DeepState_Warn_srand(unsigned int seed); + +/* Resets the global `DeepState_Input` buffer, then fills it with the + * data found in the file `path`. */ +extern void DeepState_InitInputFromFile(const char *path); + +/* Resets the global `DeepState_Input` buffer, then fills it with the + * data found in the file `path`. */ +static void DeepState_InitInputFromStdin() { + + /* Reset the index. */ + DeepState_InputIndex = 0; + DeepState_SwarmConfigsIndex = 0; + + size_t count = read(STDIN_FILENO, (void *) DeepState_Input, DeepState_InputSize); + + DeepState_InputInitialized = count; + + DeepState_LogFormat(DeepState_LogTrace, + "Initialized test input buffer with %zu bytes of data from stdin", + count); +} + +/* Run a test case, assuming we have forked from the test harness to do so. + * + * An exit code of 0 indicates that the test passed. Any other exit + * code, or termination by a signal, indicates a test failure. */ +static void DeepState_RunTest(struct DeepState_TestInfo *test) { + /* Run the test. */ + if (!setjmp(DeepState_ReturnToRun)) { + /* Convert uncaught C++ exceptions into a test failure. */ +#if defined(__cplusplus) && defined(__cpp_exceptions) + try { +#endif /* __cplusplus */ + + test->test_func(); /* Run the test function. */ + exit(DeepState_TestRunPass); + +#if defined(__cplusplus) && defined(__cpp_exceptions) + } catch(...) { + DeepState_Fail(); + } +#endif /* __cplusplus */ + + /* We caught a failure when running the test. */ + } else if (DeepState_CatchFail()) { + DeepState_LogFormat(DeepState_LogError, "Failed: %s", test->test_name); + if (HAS_FLAG_output_test_dir) { + DeepState_SaveFailingTest(); + } + exit(DeepState_TestRunFail); + + /* The test was abandoned. We may have gotten soft failures before + * abandoning, so we prefer to catch those first. */ + } else if (DeepState_CatchAbandoned()) { + DeepState_LogFormat(DeepState_LogTrace, "Abandoned: %s", test->test_name); + exit(DeepState_TestRunAbandon); + + /* The test passed. */ + } else { + DeepState_LogFormat(DeepState_LogTrace, "Passed: %s", test->test_name); + if (HAS_FLAG_output_test_dir) { + if (!FLAGS_fuzz || FLAGS_fuzz_save_passing || FLAGS_random) { + DeepState_SavePassingTest(); + } + } + exit(DeepState_TestRunPass); + } +} + +/* Run a test case, but in libFuzzer, so not inside a fork. */ +static int DeepState_RunTestNoFork(struct DeepState_TestInfo *test) { + /* Run the test. */ + if (!setjmp(DeepState_ReturnToRun)) { + /* Convert uncaught C++ exceptions into a test failure. */ +#if defined(__cplusplus) && defined(__cpp_exceptions) + try { +#endif /* __cplusplus */ + + test->test_func(); /* Run the test function. */ + return(DeepState_TestRunPass); + +#if defined(__cplusplus) && defined(__cpp_exceptions) + } catch(...) { + DeepState_Fail(); + } +#endif /* __cplusplus */ + + /* We caught a failure when running the test. */ + } else if (DeepState_CatchFail()) { + DeepState_LogFormat(DeepState_LogError, "Failed: %s", test->test_name); + if (HAS_FLAG_output_test_dir) { + DeepState_SaveFailingTest(); + } + if (HAS_FLAG_abort_on_fail) { + DeepState_HardCrash(); + } + return(DeepState_TestRunFail); + + /* The test was abandoned. We may have gotten soft failures before + * abandoning, so we prefer to catch those first. */ + } else if (DeepState_CatchAbandoned()) { + DeepState_LogFormat(DeepState_LogTrace, "Abandoned: %s", test->test_name); + return(DeepState_TestRunAbandon); + + /* The test passed. */ + } else { + DeepState_LogFormat(DeepState_LogTrace, "Passed: %s", test->test_name); + if (HAS_FLAG_output_test_dir) { + if (!FLAGS_fuzz || FLAGS_fuzz_save_passing) { + DeepState_SavePassingTest(); + } + } + return(DeepState_TestRunPass); + } +} + +extern enum DeepState_TestRunResult DeepState_FuzzOneTestCase(struct DeepState_TestInfo *test); + +/* Run a single saved test case with input initialized from the file + * `name` in directory `dir`. */ +static enum DeepState_TestRunResult +DeepState_RunSavedTestCase(struct DeepState_TestInfo *test, const char *dir, + const char *name) { + if (!setjmp(DeepState_ReturnToRun)) { + size_t path_len = 2 + sizeof(char) * (strlen(dir) + strlen(name)); + char *path = (char *) malloc(path_len); + if (path == NULL) { + DeepState_Abandon("Error allocating memory"); + } + if (strncmp(dir, "", strlen(dir)) != 0) { + snprintf(path, path_len, "%s/%s", dir, name); + } else { + snprintf(path, path_len, "%s", name); + } + + if (!(strncmp(name, "** STDIN **", strlen(name)) == 0)) { + DeepState_InitInputFromFile(path); + } else { + DeepState_InitInputFromStdin(); + } + + DeepState_Begin(test); + + enum DeepState_TestRunResult result = DeepState_ForkAndRunTest(test); + + if (result == DeepState_TestRunFail) { + DeepState_LogFormat(DeepState_LogError, "Test case %s failed", path); + free(path); + } + else if (result == DeepState_TestRunCrash) { + DeepState_LogFormat(DeepState_LogError, "Crashed: %s", test->test_name); + DeepState_LogFormat(DeepState_LogError, "Test case %s crashed", path); + free(path); + if (HAS_FLAG_output_test_dir) { + DeepState_SaveCrashingTest(); + } + + DeepState_Crash(); + } else { + free(path); + } + + return result; + } else { + DeepState_LogFormat(DeepState_LogError, "Something went wrong running the test case %s", name); + return DeepState_TestRunCrash; + } +} + +/* Run a single test many times, initialized against each saved test case in + * `FLAGS_input_test_dir`. */ +static int DeepState_RunSavedCasesForTest(struct DeepState_TestInfo *test) { + int num_failed_tests = 0; + const char *test_file_name = basename((char *) test->file_name); + + size_t test_case_dir_len = 3 + strlen(FLAGS_input_test_dir) + + strlen(test_file_name) + strlen(test->test_name); + char *test_case_dir = (char *) malloc(test_case_dir_len); + if (test_case_dir == NULL) { + DeepState_Abandon("Error allocating memory"); + } + snprintf(test_case_dir, test_case_dir_len, "%s/%s/%s", + FLAGS_input_test_dir, test_file_name, test->test_name); + + struct dirent *dp; + DIR *dir_fd; + + dir_fd = opendir(test_case_dir); + if (dir_fd == NULL) { + DeepState_LogFormat(DeepState_LogInfo, + "Skipping test `%s`, no saved test cases", + test->test_name); + free(test_case_dir); + return 0; + } + + unsigned int i = 0; + + /* Read generated test cases and run a test for each file found. */ + while ((dp = readdir(dir_fd)) != NULL) { + if (DeepState_IsTestCaseFile(dp->d_name)) { + i++; + enum DeepState_TestRunResult result = + DeepState_RunSavedTestCase(test, test_case_dir, dp->d_name); + + if (result != DeepState_TestRunPass) { + num_failed_tests++; + } + } + } + closedir(dir_fd); + free(test_case_dir); + + DeepState_LogFormat(DeepState_LogInfo, "Ran %u tests for %s; %d tests failed", + i, test->test_name, num_failed_tests); + + return num_failed_tests; +} + +/* Returns a sorted list of all available tests to run, and exits after */ +static int DeepState_RunListTests(void) { + char buff[4096]; + ssize_t write_len = 0; + + int total_test_count = 0; + int boring_count = 0; + int disabled_count = 0; + + struct DeepState_TestInfo *current_test = DeepState_FirstTestInfo; + + sprintf(buff, "Available Tests:\n\n"); + write_len = write(STDERR_FILENO, buff, strlen(buff)); + + /* Print each test and increment counter from linked list */ + for (; current_test != NULL; current_test = current_test->prev) { + + const char * curr_test = current_test->test_name; + + /* Classify tests */ + if (strstr(curr_test, "Boring") || strstr(curr_test, "BORING")) { + boring_count++; + } else if (strstr(curr_test, "Disabled") || strstr(curr_test, "DISABLED")) { + disabled_count++; + } + + /* TODO(alan): also output file name, luckily its sorted :) */ + sprintf(buff, " * %s (line %d)\n", curr_test, current_test->line_number); + write_len = write(STDERR_FILENO, buff, strlen(buff)); + total_test_count++; + } + + sprintf(buff, "\nBoring Tests: %d\nDisabled Tests: %d\n", boring_count, disabled_count); + write_len = write(STDERR_FILENO, buff, strlen(buff)); + + sprintf(buff, "\nTotal Number of Tests: %d\n", total_test_count); + write_len = write(STDERR_FILENO, buff, strlen(buff)); + return 0; +} + +/* Run test from `FLAGS_input_test_file`, under `FLAGS_input_which_test` + * or first test, if not defined. */ +static int DeepState_RunSingleSavedTestCase(void) { + int num_failed_tests = 0; + struct DeepState_TestInfo *test = NULL; + + for (test = DeepState_FirstTest(); test != NULL; test = test->prev) { + if (HAS_FLAG_input_which_test) { + if (strcmp(FLAGS_input_which_test, test->test_name) == 0) { + break; + } + } else { + DeepState_LogFormat(DeepState_LogWarning, + "No test specified, defaulting to first test defined (%s)", + test->test_name); + break; + } + } + + if (test == NULL) { + DeepState_LogFormat(DeepState_LogInfo, + "Could not find matching test for %s", + FLAGS_input_which_test); + return 0; + } + + enum DeepState_TestRunResult result = + DeepState_RunSavedTestCase(test, "", FLAGS_input_test_file); + + if ((result == DeepState_TestRunFail) || (result == DeepState_TestRunCrash)) { + if (FLAGS_abort_on_fail) { + DeepState_HardCrash(); + } + if (FLAGS_exit_on_fail) { + exit(255); // Terminate the testing + } + num_failed_tests++; + } + + DeepState_Teardown(); + + return num_failed_tests; +} + +/* Run test from stdin, under `FLAGS_input_which_test` + * or first test, if not defined. */ +static int DeepState_RunTestFromStdin(void) { + int num_failed_tests = 0; + struct DeepState_TestInfo *test = NULL; + + for (test = DeepState_FirstTest(); test != NULL; test = test->prev) { + if (HAS_FLAG_input_which_test) { + if (strcmp(FLAGS_input_which_test, test->test_name) == 0) { + break; + } + } else { + DeepState_LogFormat(DeepState_LogWarning, + "No test specified, defaulting to first test defined (%s)", + test->test_name); + break; + } + } + + if (test == NULL) { + DeepState_LogFormat(DeepState_LogInfo, + "Could not find matching test for %s", + FLAGS_input_which_test); + return 0; + } + + enum DeepState_TestRunResult result = + DeepState_RunSavedTestCase(test, "", "** STDIN **"); + + if ((result == DeepState_TestRunFail) || (result == DeepState_TestRunCrash)) { + if (FLAGS_abort_on_fail) { + DeepState_HardCrash(); + } + if (FLAGS_exit_on_fail) { + exit(255); // Terminate the testing + } + num_failed_tests++; + } + + DeepState_Teardown(); + + return num_failed_tests; +} + +extern int DeepState_Fuzz(void); + +/* Run tests from `FLAGS_input_test_files_dir`, under `FLAGS_input_which_test` + * or first test, if not defined. */ +static int DeepState_RunSingleSavedTestDir(void) { + int num_failed_tests = 0; + struct DeepState_TestInfo *test = NULL; + + if (!HAS_FLAG_min_log_level) { + FLAGS_min_log_level = 2; + } + + for (test = DeepState_FirstTest(); test != NULL; test = test->prev) { + if (HAS_FLAG_input_which_test) { + if (strcmp(FLAGS_input_which_test, test->test_name) == 0) { + break; + } + } else { + DeepState_LogFormat(DeepState_LogWarning, + "No test specified, defaulting to first test defined (%s)", + test->test_name); + break; + } + } + + if (test == NULL) { + DeepState_LogFormat(DeepState_LogInfo, + "Could not find matching test for %s", + FLAGS_input_which_test); + return 0; + } + + struct dirent *dp; + DIR *dir_fd; + + #if defined(__unix) + struct stat path_stat; + #endif + + dir_fd = opendir(FLAGS_input_test_files_dir); + if (dir_fd == NULL) { + DeepState_LogFormat(DeepState_LogInfo, + "No tests to run"); + return 0; + } + + unsigned int i = 0; + + /* Read generated test cases and run a test for each file found. */ + while ((dp = readdir(dir_fd)) != NULL) { + size_t path_len = 2 + sizeof(char) * (strlen(FLAGS_input_test_files_dir) + strlen(dp->d_name)); + char *path = (char *) malloc(path_len); + snprintf(path, path_len, "%s/%s", FLAGS_input_test_files_dir, dp->d_name); + + if (!DeepState_IsRegularFile(path)){ + continue; + } + + i++; + enum DeepState_TestRunResult result = + DeepState_RunSavedTestCase(test, FLAGS_input_test_files_dir, dp->d_name); + + if ((result == DeepState_TestRunFail) || (result == DeepState_TestRunCrash)) { + if (FLAGS_abort_on_fail) { + DeepState_HardCrash(); + } + if (FLAGS_exit_on_fail) { + exit(255); // Terminate the testing + } + num_failed_tests++; + } + } + closedir(dir_fd); + + DeepState_LogFormat(DeepState_LogInfo, "Ran %u tests; %d tests failed", + i, num_failed_tests); + + return num_failed_tests; +} + +/* Run test `FLAGS_input_which_test` with saved input from `FLAGS_input_test_file`. + * + * For each test unit and case, see if there are input files in the + * expected directories. If so, use them to initialize + * `DeepState_Input`, then run the test. If not, skip the test. */ +static int DeepState_RunSavedTestCases(void) { + int num_failed_tests = 0; + struct DeepState_TestInfo *test = NULL; + + if (!HAS_FLAG_min_log_level) { + FLAGS_min_log_level = 2; + } + + for (test = DeepState_FirstTest(); test != NULL; test = test->prev) { + num_failed_tests += DeepState_RunSavedCasesForTest(test); + } + + DeepState_Teardown(); + + return num_failed_tests; +} + +/* Start DeepState and run the tests. Returns the number of failed tests. */ +static int DeepState_Run(void) { + if (!DeepState_OptionsAreInitialized) { + DeepState_Abandon("Please call DeepState_InitOptions(argc, argv) in main"); + } + + if (HAS_FLAG_list_tests) { + return DeepState_RunListTests(); + } + + ENABLE_DIRECT_RUN_FLAG; + + if (HAS_FLAG_input_test_file) { + return DeepState_RunSingleSavedTestCase(); + } + + if (HAS_FLAG_input_stdin) { + return DeepState_RunTestFromStdin(); + } + + if (HAS_FLAG_input_test_dir) { + return DeepState_RunSavedTestCases(); + } + + if (HAS_FLAG_input_test_files_dir) { + return DeepState_RunSingleSavedTestDir(); + } + + if (FLAGS_fuzz || FLAGS_random) { + return DeepState_Fuzz(); + } + + int num_failed_tests = 0; + struct DeepState_TestInfo *test = NULL; + + + for (test = DeepState_FirstTest(); test != NULL; test = test->prev) { + + const char * curr_test = test->test_name; + + /* Run only the Boring* tests */ + if (HAS_FLAG_boring_only) { + if (strstr(curr_test, "Boring") || strstr(curr_test, "BORING")) { + DeepState_Begin(test); + if (DeepState_ForkAndRunTest(test) != 0) { + num_failed_tests++; + } + } else { + continue; + } + } + + /* Check if pattern match exists in test, skip if not */ + if (HAS_FLAG_test_filter) { + if (REG_MATCH(FLAGS_test_filter, curr_test)){ + continue; + } + } + + /* Check if --run_disabled is set, and if not, skip Disabled* tests */ + if (!HAS_FLAG_run_disabled) { + if (strstr(curr_test, "Disabled") || strstr(test->test_name, "DISABLED")) { + continue; + } + } + + DeepState_Begin(test); + if (DeepState_ForkAndRunTest(test) != 0) { + num_failed_tests++; + } + } + + DeepState_Teardown(); + + return num_failed_tests; +} + +DEEPSTATE_END_EXTERN_C + +#endif /* SRC_INCLUDE_DEEPSTATE_DEEPSTATE_H_ */ diff --git a/src/include/deepstate/DeepState.hpp b/src/include/deepstate/DeepState.hpp index 9a31ddb9..a46589a4 100644 --- a/src/include/deepstate/DeepState.hpp +++ b/src/include/deepstate/DeepState.hpp @@ -1,912 +1,912 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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. - */ - -#ifndef SRC_INCLUDE_DEEPSTATE_DEEPSTATE_HPP_ -#define SRC_INCLUDE_DEEPSTATE_DEEPSTATE_HPP_ - -#include -#include - -#include -#include -#include -#include -#include - -namespace deepstate { - -DEEPSTATE_INLINE static void *Malloc(size_t num_bytes) { - return DeepState_Malloc(num_bytes); -} - -DEEPSTATE_INLINE static void SymbolizeData(void *begin, void *end) { - DeepState_SymbolizeData(begin, end); -} - -DEEPSTATE_INLINE static bool Bool(void) { - return static_cast(DeepState_Bool()); -} - -DEEPSTATE_INLINE static size_t Size(void) { - return DeepState_Size(); -} - -DEEPSTATE_INLINE static uint64_t UInt64(void) { - return DeepState_UInt64(); -} - -DEEPSTATE_INLINE static int64_t Int64(void) { - return DeepState_Int64(); -} - -DEEPSTATE_INLINE static uint32_t UInt(void) { - return DeepState_UInt(); -} - -DEEPSTATE_INLINE static int32_t Int(void) { - return DeepState_Int(); -} - -DEEPSTATE_INLINE static uint16_t UShort(void) { - return DeepState_UShort(); -} - -DEEPSTATE_INLINE static int16_t Short(void) { - return DeepState_Short(); -} - -DEEPSTATE_INLINE static unsigned char UChar(void) { - return DeepState_UChar(); -} - -DEEPSTATE_INLINE static char Char(void) { - return DeepState_Char(); -} - -DEEPSTATE_INLINE static bool IsSymbolic(uint64_t x) { - return DeepState_IsSymbolicUInt64(x); -} - -DEEPSTATE_INLINE static int IsSymbolic(int64_t x) { - return DeepState_IsSymbolicInt64(x); -} - -DEEPSTATE_INLINE static bool IsSymbolic(uint32_t x) { - return DeepState_IsSymbolicUInt(x); -} - -DEEPSTATE_INLINE static bool IsSymbolic(int32_t x) { - return DeepState_IsSymbolicInt(x); -} - -DEEPSTATE_INLINE static int IsSymbolic(uint16_t x) { - return DeepState_IsSymbolicUShort(x); -} - -DEEPSTATE_INLINE static bool IsSymbolic(int16_t x) { - return DeepState_IsSymbolicShort(x); -} - -DEEPSTATE_INLINE static bool IsSymbolic(unsigned char x) { - return DeepState_IsSymbolicUChar(x); -} - -DEEPSTATE_INLINE static bool IsSymbolic(char x) { - return DeepState_IsSymbolicChar(x); -} - -DEEPSTATE_INLINE static bool IsSymbolic(float x) { - return DeepState_IsSymbolicFloat(x); -} - -DEEPSTATE_INLINE static bool IsSymbolic(double x) { - return DeepState_IsSymbolicDouble(x); -} - -// A test fixture. -class Test { - public: - Test(void) = default; - ~Test(void) = default; - inline void SetUp(void) {} - inline void TearDown(void) {} - - private: - Test(const Test &) = delete; - Test(Test &&) = delete; - Test &operator=(const Test &) = delete; - Test &operator=(Test &&) = delete; -}; - -template -class Symbolic { - public: - template - DEEPSTATE_INLINE Symbolic(Args&& ...args) - : value(std::forward(args)...) {} - - DEEPSTATE_INLINE Symbolic(void) { - T *val_ptr = &value; - DeepState_SymbolizeData(val_ptr, &(val_ptr[1])); - } - - DEEPSTATE_INLINE operator T (void) const { - return value; - } - - T value; -}; - -template -class Symbolic {}; - -template -class SymbolicLinearContainer { - public: - DEEPSTATE_INLINE explicit SymbolicLinearContainer(size_t len) - : value(len) { - if (!value.empty()) { - DeepState_SymbolizeData(&(value.front()), &(value.back())); - } - } - - DEEPSTATE_INLINE SymbolicLinearContainer(void) { - value.reserve(32); - value.resize(DeepState_SizeInRange(0, 32)); // Avoids symbolic `malloc`. - } - - DEEPSTATE_INLINE operator T (void) const { - return value; - } - - T value; -}; - -template <> -class Symbolic : public SymbolicLinearContainer { - using SymbolicLinearContainer::SymbolicLinearContainer; -}; - -template <> -class Symbolic : public SymbolicLinearContainer { - using SymbolicLinearContainer::SymbolicLinearContainer; -}; - -template -class Symbolic> : - public SymbolicLinearContainer> {}; - -#define MAKE_SYMBOL_SPECIALIZATION(Tname, tname) \ - template <> \ - class Symbolic { \ - public: \ - using SelfType = Symbolic; \ - \ - DEEPSTATE_INLINE Symbolic(void) \ - : value(DeepState_ ## Tname()) {} \ - \ - DEEPSTATE_INLINE Symbolic(tname that) \ - : value(that) {} \ - \ - DEEPSTATE_INLINE Symbolic(const SelfType &that) \ - : value(that.value) {} \ - \ - DEEPSTATE_INLINE Symbolic(SelfType &&that) \ - : value(std::move(that.value)) {} \ - \ - DEEPSTATE_INLINE operator tname (void) const { \ - return value; \ - } \ - SelfType &operator=(const SelfType &that) = default; \ - SelfType &operator=(SelfType &&that) = default; \ - SelfType &operator=(tname that) { \ - value = that; \ - return *this; \ - } \ - SelfType &operator+=(tname that) { \ - value += that; \ - return *this; \ - } \ - SelfType &operator-=(tname that) { \ - value -= that; \ - return *this; \ - } \ - SelfType &operator*=(tname that) { \ - value *= that; \ - return *this; \ - } \ - SelfType &operator/=(tname that) { \ - value /= that; \ - return *this; \ - } \ - SelfType &operator>>=(tname that) { \ - value >>= that; \ - return *this; \ - } \ - SelfType &operator<<=(tname that) { \ - value <<= that; \ - return *this; \ - } \ - tname &operator++(void) { \ - return ++value; \ - } \ - tname operator++(int) { \ - auto prev_value = value; \ - value++; \ - return prev_value; \ - } \ - tname value; \ - }; - -MAKE_SYMBOL_SPECIALIZATION(UInt64, uint64_t) -MAKE_SYMBOL_SPECIALIZATION(Int64, int64_t) -MAKE_SYMBOL_SPECIALIZATION(UInt, uint32_t) -MAKE_SYMBOL_SPECIALIZATION(Int, int32_t) -MAKE_SYMBOL_SPECIALIZATION(UShort, uint16_t) -MAKE_SYMBOL_SPECIALIZATION(Short, int16_t) -MAKE_SYMBOL_SPECIALIZATION(UChar, uint8_t) -MAKE_SYMBOL_SPECIALIZATION(Char, int8_t) - - -using symbolic_char = Symbolic; -using symbolic_short = Symbolic; -using symbolic_int = Symbolic; -using symbolic_unsigned = Symbolic; -using symbolic_long = Symbolic; - -using symbolic_int8_t = Symbolic; -using symbolic_uint8_t = Symbolic; -using symbolic_int16_t = Symbolic; -using symbolic_uint16_t = Symbolic; -using symbolic_int32_t = Symbolic; -using symbolic_uint32_t = Symbolic; -using symbolic_int64_t = Symbolic; -using symbolic_uint64_t = Symbolic; - -#undef MAKE_SYMBOL_SPECIALIZATION - -#define MAKE_MINIMIZER(Type, type) \ - DEEPSTATE_INLINE static type Minimize(type val) { \ - return DeepState_Min ## Type(val); \ - } \ - DEEPSTATE_INLINE static type Maximize(type val) { \ - return DeepState_Max ## Type(val); \ - } - -MAKE_MINIMIZER(UInt, uint32_t) -MAKE_MINIMIZER(Int, int32_t) -MAKE_MINIMIZER(UShort, uint16_t) -MAKE_MINIMIZER(Short, int16_t) -MAKE_MINIMIZER(UChar, uint8_t) -MAKE_MINIMIZER(Char, int8_t) - -#undef MAKE_MINIMIZER - -template -static T Pump(T val, unsigned max=10) { - if (!IsSymbolic(val)) { - return val; - } - if (!max) { - DeepState_Abandon("Must have a positive maximum number of values to Pump"); - } - for (auto i = 0U; i < max - 1; ++i) { - T min_val = Minimize(val); - if (val == min_val) { - DEEPSTATE_USED(min_val); // Force the concrete `min_val` to be returned, - // as opposed to compiler possibly choosing to - // return `val`. - return min_val; - } - } - return Minimize(val); -} - -template -inline static void ForAll(void (*func)(Args...)) { - func(Symbolic()...); -} - -template -inline static void ForAll(Closure func) { - func(Symbolic()...); -} - -#define PureSwarmOneOf(...) _SwarmOneOf(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) -#define MixedSwarmOneOf(...) _SwarmOneOf(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) -#define ProbSwarmOneOf(...) _SwarmOneOf(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) - -#ifndef DEEPSTATE_PURE_SWARM -#ifndef DEEPSTATE_MIXED_SWARM -#ifndef DEEPSTATE_PROB_SWARM -#define OneOf(...) NoSwarmOneOf(__VA_ARGS__) -#endif -#endif -#endif - -#ifdef DEEPSTATE_PURE_SWARM -#define OneOf(...) _SwarmOneOf(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) -#endif - -#ifdef DEEPSTATE_MIXED_SWARM -#define OneOf(...) _SwarmOneOf(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) -#endif - -#ifdef DEEPSTATE_PROB_SWARM -#define OneOf(...) _SwarmOneOf(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) -#endif - -template -inline static void NoSwarmOneOf(FuncTys&&... funcs) { - if (FLAGS_verbose_reads) { - printf("STARTING OneOf CALL\n"); - } - std::function func_arr[sizeof...(FuncTys)] = {funcs...}; - unsigned index = DeepState_UIntInRange( - 0U, static_cast(sizeof...(funcs))-1); - func_arr[Pump(index, sizeof...(funcs))](); - if (FLAGS_verbose_reads) { - printf("FINISHED OneOf CALL\n"); - } -} - -template -inline static void _SwarmOneOf(const char* file, unsigned line, enum DeepState_SwarmType stype, - FuncTys&&... funcs) { - unsigned fcount = static_cast(sizeof...(funcs)); - std::function func_arr[sizeof...(FuncTys)] = {funcs...}; - struct DeepState_SwarmConfig* sc = DeepState_GetSwarmConfig(fcount, file, line, stype); - if (FLAGS_verbose_reads) { - printf("STARTING OneOf CALL\n"); - } - unsigned index = DeepState_UIntInRange(0U, sc->fcount-1); - func_arr[sc->fmap[Pump(index, sc->fcount)]](); - if (FLAGS_verbose_reads) { - printf("FINISHED OneOf CALL\n"); - } -} - -size_t PickIndex(double *probs, size_t length) { - double total = 0.0; - size_t missing = 0; - for (size_t i = 0; i < length; ++i) { - if (probs[i] >= 0.0) { - total += probs[i]; - } else{ - ++missing; - } - } - if (total > 1.0) { - DeepState_Abandon("Probabilities sum to more than 1.0"); - } - if (missing > 0) { - double remainder = (1.0 - total) / missing; - for (size_t i = 0; i < length; ++i) { - if (probs[i] < 0.0) { - probs[i] = remainder; - } - } - } else if (total < 0.999) { - DeepState_Abandon("Total of probabilities is significantly less than 1.0"); - } - - // We cannot use DeepState_Float/Double here because the distribution is very bad - double P = DeepState_UIntInRange(0, 10000000)/10000000.0; - unsigned index = 0; - double sum = 0.0; - while ((index < length) && (P > (sum + probs[index]))) { - sum += probs[index]; - index++; - } - return index; -} - -typedef std::function func_t; - -void ActuallySelectSomething(double *probs, func_t *funcs, size_t length) { - if (FLAGS_verbose_reads) { - printf("STARTING OneOf CALL\n"); - } - - funcs[PickIndex(probs, length)](); - if (FLAGS_verbose_reads) { - printf("FINISHED OneOf CALL\n"); - } -} - -// These two helper functions participate in the splitting. -// Base case does nothing -void SplitArgs(double* probs, func_t *funcs) { -} - -// recursive case -template -void SplitArgs(double* probs, func_t *funcs, double firstProb, TyFunc &&firstFunc, Rest &&... rest) { - *probs = firstProb; - *funcs = firstFunc; - SplitArgs(probs + 1, funcs + 1, std::forward(rest)...); -} - -// The entry point for OneOfP over lambdas -template -void OneOfP(Args &&... args) { - constexpr auto argsLen = sizeof...(Args); - static_assert((argsLen % 2) == 0, "OneOfP expects probability/lambda pairs"); - constexpr auto length = argsLen / 2; - - double probs[length]; - func_t funcs[length]; - SplitArgs(probs, funcs, std::forward(args)...); - - ActuallySelectSomething(probs, funcs, length); -} - -inline static char NoSwarmOneOf(const char *str) { - if (!str || !str[0]) { - DeepState_Abandon("NULL or empty string passed to OneOf"); - } - return str[DeepState_IntInRange(0, strlen(str) - 1)]; -} - -inline static char _SwarmOneOf(const char* file, unsigned line, enum DeepState_SwarmType stype, const char *str) { - if (!str || !str[0]) { - DeepState_Abandon("NULL or empty string passed to OneOf"); - } - unsigned fcount = strlen(str); - struct DeepState_SwarmConfig* sc = DeepState_GetSwarmConfig(fcount, file, line, stype); - unsigned index = sc->fmap[DeepState_UIntInRange(0U, sc->fcount-1)]; - return str[index]; -} - -template -inline static const T &NoSwarmOneOf(std::vector &arr) { - if (arr.empty()) { - DeepState_Abandon("Empty vector passed to OneOf"); - } - return arr[DeepState_IntInRange(0, arr.size() - 1)]; -} - -size_t PickListIndex(std::initializer_list probs, size_t length) { - // The list is interpreted as follows: a negative probability means "use even distribution" - // over all probabilities not specified", and the same strategy is used to fill out the list - // to match the count of items to be chosen among. - if (probs.size() > length) { - DeepState_Abandon("Probability list size greater than number of choices"); - } - double P[length]; - size_t iP = 0; - for (std::initializer_list::iterator it = probs.begin(); it != probs.end(); ++it) { - P[iP++] = *it; - } - while (iP < length) { - P[iP++] = -1.0; - } - - return PickIndex(P, length); -} - -template -inline static const T &OneOfP(std::initializer_list probs, std::vector &arr) { - if (arr.empty()) { - DeepState_Abandon("Empty vector passed to OneOf"); - } - size_t index = PickListIndex(probs, arr.size()); - return arr[index]; -} - -template -inline static const T &_SwarmOneOf(const char* file, unsigned line, enum DeepState_SwarmType stype, const std::vector &arr) { - if (arr.empty()) { - DeepState_Abandon("Empty vector passed to OneOf"); - } - unsigned fcount = arr.size(); - struct DeepState_SwarmConfig* sc = DeepState_GetSwarmConfig(fcount, file, line, stype); - unsigned index = sc->fmap[DeepState_UIntInRange(0U, sc->fcount-1)]; - return arr[index]; -} - -template -inline static const T &NoSwarmOneOf(T (&arr)[len]) { - if (!len) { - DeepState_Abandon("Empty array passed to OneOf"); - } - return arr[DeepState_IntInRange(0, len - 1)]; -} - -template -inline static const T &OneOfP(std::initializer_list probs, T (&arr)[len]) { - if (!len) { - DeepState_Abandon("Empty array passed to OneOf"); - } - size_t index = PickListIndex(probs, len); - return arr[index]; -} - -template -inline static const T &_SwarmOneOf(const char* file, unsigned line, enum DeepState_SwarmType stype, T (&arr)[len]) { - if (!len) { - DeepState_Abandon("Empty array passed to OneOf"); - } - struct DeepState_SwarmConfig* sc = DeepState_GetSwarmConfig(len, file, line, stype); - unsigned index = sc->fmap[DeepState_UIntInRange(0U, sc->fcount-1)]; - return arr[index]; -} - - -template -struct ExpandedCompareIntegral { - template - static DEEPSTATE_INLINE bool Compare(T a, T b, C cmp) { - if (cmp((a & 0xFF), (b & 0xFF))) { - return ExpandedCompareIntegral::Compare(a >> 8, b >> 8, cmp); - } - return DeepState_ZeroSink(k); // Also false. - } -}; - -template -struct ExpandedCompareIntegral { - template - static DEEPSTATE_INLINE bool Compare(T a, T b, C cmp) { - if (cmp((a & 0xFF), (b & 0xFF))) { - return DeepState_ZeroSink(0); - } else { - return DeepState_ZeroSink(100); - } - } -}; - -template -struct DeclType { - using Type = T; -}; - -template -struct DeclType : public DeclType {}; - -template -struct DeclType> : public DeclType {}; - -template -struct DeclType &> : public DeclType {}; - -template -struct IsIntegral : public std::is_integral {}; - -template -struct IsIntegral : public IsIntegral {}; - -template -struct IsIntegral> : public IsIntegral {}; - -template -struct IsSigned : public std::is_signed {}; - -template -struct IsSigned : public IsSigned {}; - -template -struct IsSigned> : public IsSigned {}; - -template -struct IsUnsigned : public std::is_unsigned {}; - -template -struct IsUnsigned : public IsUnsigned {}; - -template -struct IsUnsigned> : public std::is_unsigned {}; - -template -struct BestType { - - // type alias for bools, since std::make_unsigned returns unexpected behavior - using _A = typename std::conditional::value, unsigned int, A>::type; - using _B = typename std::conditional::value, unsigned int, B>::type; - - using UA = typename std::conditional< - IsUnsigned::value, - typename std::make_unsigned<_A>::type, A>::type; - - using UB = typename std::conditional< - IsUnsigned::value, - typename std::make_unsigned<_B>::type, B>::type; - - using Type = typename std::conditional<(sizeof(UA) > sizeof(UB)), - UA, UB>::type; -}; - -template -struct Comparer { - static constexpr bool kIsIntegral = IsIntegral() && IsIntegral(); - static constexpr bool IsBool = std::is_same::value && std::is_same::value; - - struct tag_int {}; - struct tag_not_int {}; - - using tag = typename std::conditional::type; - - template - static DEEPSTATE_INLINE bool Do(const A &a, const B &b, C cmp, tag_not_int) { - return cmp(a, b); - } - - template - static DEEPSTATE_INLINE bool Do(A a, B b, C cmp, tag_int) { - using T = typename ::deepstate::BestType::Type; - if (cmp(a, b)) { - return true; - } - DEEPSTATE_USED(a); // These make the compiler forget everything it knew - DEEPSTATE_USED(b); // about `a` and `b`. - return ::deepstate::ExpandedCompareIntegral::Compare(a, b, cmp); - } - - template - static DEEPSTATE_INLINE bool Do(const A &a, const B &b, C cmp) { - - // IsIntegral returns true for booleans, so we override to basic overloaded method - // if we have boolean template parameters passed to prevent error in ASSERT_EQ - if (IsBool) { - return Do(a, b, cmp, tag_not_int()); - } - return Do(a, b, cmp, tag()); - } - -}; - -#define DeepState_PureSwarmAssignCStr(...) _DeepState_SwarmAssignCStr(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) -#define DeepState_MixedSwarmAssignCStr(...) _DeepState_SwarmAssignCStr(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) -#define DeepState_ProbSwarmAssignCStr(...) _DeepState_SwarmAssignCStr(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) - -#define DeepState_PureSwarmAssignCStrUpToLen(...) _DeepState_SwarmAssignCStr(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) -#define DeepState_MixedSwarmAssignCStrUpToLen(...) _DeepState_SwarmAssignCStr(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) -#define DeepState_ProbSwarmAssignCStrUpToLen(...) _DeepState_SwarmAssignCStr(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) - -#define DeepState_PureSwarmCStr(...) _DeepState_SwarmCStr(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) -#define DeepState_MixedSwarmCStr(...) _DeepState_SwarmCStr(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) -#define DeepState_ProbSwarmCStr(...) _DeepState_SwarmCStr(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) - -#define DeepState_PureSwarmCStrUpToLen(...) _DeepState_SwarmCStr(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) -#define DeepState_MixedSwarmCStrUpToLen(...) _DeepState_SwarmCStr(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) -#define DeepState_ProbSwarmCStrUpToLen(...) _DeepState_SwarmCStr(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) - -#define DeepState_PureSwarmSymbolizeCStr(...) _DeepState_SwarmSymbolizeCStr(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) -#define DeepState_MixedSwarmSymbolizeCStr(...) _DeepState_SwarmSymbolizeCStr(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) -#define DeepState_ProbSwarmSymbolizeCStr(...) _DeepState_SwarmSymbolizeCStr(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) - -#ifndef DEEPSTATE_PURE_SWARM -#ifndef DEEPSTATE_MIXED_SWARM -#ifndef DEEPSTATE_PROB_SWARM -#define DeepState_AssignCStr(...) DeepState_NoSwarmAssignCStr(__VA_ARGS__) -#define DeepState_AssignCStrUpToLen(...) DeepState_NoSwarmAssignCStrUpToLen(__VA_ARGS__) -#define DeepState_CStr(...) DeepState_NoSwarmCStr(__VA_ARGS__) -#define DeepState_CStrUpToLen(...) DeepState_NoSwarmCStrUpToLen(__VA_ARGS__) -#define DeepState_SymbolizeCStr(...) DeepState_NoSwarmSymbolizeCStr(__VA_ARGS__) -#endif -#endif -#endif - - -#ifdef DEEPSTATE_PURE_SWARM -#define DeepState_AssignCStr(...) _DeepState_SwarmAssignCStr(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) -#define DeepState_AssignCStrUpToLen(...) _DeepState_SwarmAssignCStrUpToLen(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) -#define DeepState_CStr(...) _DeepState_SwarmCStr(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) -#define DeepState_CStrUpToLen(...) _DeepState_SwarmCStrUpToLen(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) -#define DeepState_SymbolizeCStr(...) _DeepState_SwarmSymbolizeCStr(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) -#endif - -#ifdef DEEPSTATE_MIXED_SWARM -#define DeepState_AssignCStr(...) _DeepState_SwarmAssignCStr(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) -#define DeepState_AssignCStrUpToLen(...) _DeepState_SwarmAssignCStrUpToLen(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) -#define DeepState_CStr(...) _DeepState_SwarmCStr(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) -#define DeepState_CStrUpToLen(...) _DeepState_SwarmCStrUpToLen(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) -#define DeepState_SymbolizeCStr(...) _DeepState_SwarmSymbolizeCStr(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) -#endif - -#ifdef DEEPSTATE_PROB_SWARM -#define DeepState_AssignCStr(...) _DeepState_SwarmAssignCStr(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) -#define DeepState_AssignCStrUpToLen(...) _DeepState_SwarmAssignCStrUpToLen(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) -#define DeepState_CStr(...) _DeepState_SwarmCStr(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) -#define DeepState_CStrUpToLen(...) _DeepState_SwarmCStrUpToLen(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) -#define DeepState_SymbolizeCStr(...) _DeepState_SwarmSymbolizeCStr(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) -#endif - -/* Like DeepState_AssignCStr_C, but fills in a null `allowed` value. */ -inline static void DeepState_NoSwarmAssignCStr(char* str, size_t len, - const char* allowed = 0) { - DeepState_AssignCStr_C(str, len, allowed); -} - -inline static void _DeepState_SwarmAssignCStr(const char* file, unsigned line, enum DeepState_SwarmType stype, - char* str, size_t len, - const char* allowed = 0) { - DeepState_SwarmAssignCStr_C(file, line, stype, str, len, allowed); -} - -/* Like DeepState_AssignCStr, but Pumps through possible string sizes. */ -inline static void DeepState_NoSwarmAssignCStrUpToLen(char* str, size_t max_len, - const char* allowed = 0) { - uint32_t len = DeepState_UIntInRange(0, max_len); - DeepState_AssignCStr_C(str, Pump(len, max_len+1), allowed); -} - -inline static void _DeepState_SwarmAssignCStrUpToLen(const char* file, unsigned line, enum DeepState_SwarmType stype, - char* str, size_t max_len, - const char* allowed = 0) { - uint32_t len = DeepState_UIntInRange(0, max_len); - DeepState_SwarmAssignCStr_C(file, line, stype, str, Pump(len, max_len+1), allowed); -} - -/* Like DeepState_CStr_C, but fills in a null `allowed` value. */ -inline static char* DeepState_NoSwarmCStr(size_t len, const char* allowed = 0) { - return DeepState_CStr_C(len, allowed); -} - -inline static char* _DeepState_SwarmCStr(const char* file, unsigned line, enum DeepState_SwarmType stype, - size_t len, const char* allowed = 0) { - return DeepState_SwarmCStr_C(file, line, stype, len, allowed); -} - -/* Like DeepState_CStr, but Pumps through possible string sizes. */ -inline static char* DeepState_NoSwarmCStrUpToLen(size_t max_len, const char* allowed = 0) { - uint32_t len = DeepState_UIntInRange(0, max_len); - return DeepState_CStr_C(Pump(len, max_len+1), allowed); -} - -inline static char* _DeepState_SwarmCStrUpToLen(const char* file, unsigned line, enum DeepState_SwarmType stype, - size_t max_len, const char* allowed = 0) { - uint32_t len = DeepState_UIntInRange(0, max_len); - return DeepState_SwarmCStr_C(file, line, stype, Pump(len, max_len+1), allowed); -} - -/* Like DeepState_Symbolize_CStr, but fills in null `allowed` value. */ -inline static void DeepState_NoSwarmSymbolizeCStr(char *begin, const char* allowed = 0) { - DeepState_SymbolizeCStr_C(begin, allowed); -} - -inline static void _DeepState_SwarmSymbolizeCStr(const char* file, unsigned line, enum DeepState_SwarmType stype, - char *begin, const char* allowed = 0) { - DeepState_SwarmSymbolizeCStr_C(file, line, stype, begin, allowed); -} - -} // namespace deepstate - -#define ONE_OF ::deepstate::OneOf - -#define TEST(category, name) \ - DeepState_EntryPoint(category ## _ ## name) - -#define _TEST_F(fixture_name, test_name, file, line) \ - class fixture_name ## _ ## test_name : public fixture_name { \ - public: \ - void DoRunTest(void); \ - static void RunTest(void) { \ - do { \ - fixture_name ## _ ## test_name self; \ - self.SetUp(); \ - self.DoRunTest(); \ - self.TearDown(); \ - } while (false); \ - DeepState_Pass(); \ - } \ - static struct DeepState_TestInfo kTestInfo; \ - }; \ - struct DeepState_TestInfo fixture_name ## _ ## test_name::kTestInfo = { \ - nullptr, \ - fixture_name ## _ ## test_name::RunTest, \ - DEEPSTATE_TO_STR(fixture_name ## _ ## test_name), \ - file, \ - line, \ - }; \ - DEEPSTATE_INITIALIZER(DeepState_Register_ ## test_name) { \ - fixture_name ## _ ## test_name::kTestInfo.prev = DeepState_LastTestInfo; \ - DeepState_LastTestInfo = &(fixture_name ## _ ## test_name::kTestInfo); \ - } \ - void fixture_name ## _ ## test_name :: DoRunTest(void) - - -#define _EXPAND_COMPARE(a, b, op) \ - ([] (decltype(a) __a0, decltype(b) __b0) -> bool { \ - using __A = typename ::deepstate::DeclType::Type; \ - using __B = typename ::deepstate::DeclType::Type; \ - auto __cmp = [] (__A __a4, __B __b4) { return __a4 op __b4; }; \ - return ::deepstate::Comparer<__A, __B>::Do(__a0, __b0, __cmp); \ - })((a), (b)) - -#define TEST_F(fixture_name, test_name) \ - _TEST_F(fixture_name, test_name, __FILE__, __LINE__) - -#define LOG_DEBUG(cond) \ - ::deepstate::Stream(DeepState_LogDebug, (cond), __FILE__, __LINE__) - -#define LOG_TRACE(cond) \ - ::deepstate::Stream(DeepState_LogTrace, (cond), __FILE__, __LINE__) - -#define LOG_INFO(cond) \ - ::deepstate::Stream(DeepState_LogInfo, (cond), __FILE__, __LINE__) - -#define LOG_WARNING(cond) \ - ::deepstate::Stream(DeepState_LogWarning, (cond), __FILE__, __LINE__) - -#define LOG_WARN(cond) \ - ::deepstate::Stream(DeepState_LogWarning, (cond), __FILE__, __LINE__) - -#define LOG_ERROR(cond) \ - ::deepstate::Stream(DeepState_LogError, (cond), __FILE__, __LINE__) - -#define LOG_FATAL(cond) \ - ::deepstate::Stream(DeepState_LogFatal, (cond), __FILE__, __LINE__) - -#define LOG_CRITICAL(cond) \ - ::deepstate::Stream(DeepState_LogFatal, (cond), __FILE__, __LINE__) - -#define LOG(LEVEL) LOG_ ## LEVEL(true) - -#define LOG_IF(LEVEL, cond) LOG_ ## LEVEL(cond) - -#define DEEPSTATE_LOG_EQNE(a, b, op, level) \ - ::deepstate::Stream( \ - level, !(_EXPAND_COMPARE(a, b, op)), __FILE__, __LINE__) - -#define DEEPSTATE_LOG_BINOP(a, b, op, level) \ - ::deepstate::Stream( \ - level, !(a op b), __FILE__, __LINE__) - -#define ASSERT_EQ(a, b) DEEPSTATE_LOG_EQNE(a, b, ==, DeepState_LogFatal) -#define ASSERT_NE(a, b) DEEPSTATE_LOG_EQNE(a, b, !=, DeepState_LogFatal) -#define ASSERT_LT(a, b) DEEPSTATE_LOG_BINOP(a, b, <, DeepState_LogFatal) -#define ASSERT_LE(a, b) DEEPSTATE_LOG_BINOP(a, b, <=, DeepState_LogFatal) -#define ASSERT_GT(a, b) DEEPSTATE_LOG_BINOP(a, b, >, DeepState_LogFatal) -#define ASSERT_GE(a, b) DEEPSTATE_LOG_BINOP(a, b, >=, DeepState_LogFatal) - -#define CHECK_EQ(a, b) DEEPSTATE_LOG_EQNE(a, b, ==, DeepState_LogError) -#define CHECK_NE(a, b) DEEPSTATE_LOG_EQNE(a, b, !=, DeepState_LogError) -#define CHECK_LT(a, b) DEEPSTATE_LOG_BINOP(a, b, <, DeepState_LogError) -#define CHECK_LE(a, b) DEEPSTATE_LOG_BINOP(a, b, <=, DeepState_LogError) -#define CHECK_GT(a, b) DEEPSTATE_LOG_BINOP(a, b, >, DeepState_LogError) -#define CHECK_GE(a, b) DEEPSTATE_LOG_BINOP(a, b, >=, DeepState_LogError) - -#define ASSERT(expr) \ - ::deepstate::Stream( \ - DeepState_LogFatal, !(expr), __FILE__, __LINE__) - -#define ASSERT_TRUE ASSERT -#define ASSERT_FALSE(expr) ASSERT(!(expr)) - -#define CHECK(expr) \ - ::deepstate::Stream( \ - DeepState_LogError, !(expr), __FILE__, __LINE__) - -#define CHECK_TRUE CHECK -#define CHECK_FALSE(expr) CHECK(!(expr)) - -#define ASSUME(expr) \ - DeepState_Assume(expr), ::deepstate::Stream( \ - DeepState_LogTrace, true, __FILE__, __LINE__) - -#define DEEPSTATE_ASSUME_BINOP(a, b, op) \ - DeepState_Assume((a op b)), ::deepstate::Stream( \ - DeepState_LogTrace, true, __FILE__, __LINE__) - -#define ASSUME_EQ(a, b) DEEPSTATE_ASSUME_BINOP(a, b, ==) -#define ASSUME_NE(a, b) DEEPSTATE_ASSUME_BINOP(a, b, !=) -#define ASSUME_LT(a, b) DEEPSTATE_ASSUME_BINOP(a, b, <) -#define ASSUME_LE(a, b) DEEPSTATE_ASSUME_BINOP(a, b, <=) -#define ASSUME_GT(a, b) DEEPSTATE_ASSUME_BINOP(a, b, >) -#define ASSUME_GE(a, b) DEEPSTATE_ASSUME_BINOP(a, b, >=) - -#endif // SRC_INCLUDE_DEEPSTATE_DEEPSTATE_HPP_ +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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. + */ + +#ifndef SRC_INCLUDE_DEEPSTATE_DEEPSTATE_HPP_ +#define SRC_INCLUDE_DEEPSTATE_DEEPSTATE_HPP_ + +#include +#include + +#include +#include +#include +#include +#include + +namespace deepstate { + +DEEPSTATE_INLINE static void *Malloc(size_t num_bytes) { + return DeepState_Malloc(num_bytes); +} + +DEEPSTATE_INLINE static void SymbolizeData(void *begin, void *end) { + DeepState_SymbolizeData(begin, end); +} + +DEEPSTATE_INLINE static bool Bool(void) { + return static_cast(DeepState_Bool()); +} + +DEEPSTATE_INLINE static size_t Size(void) { + return DeepState_Size(); +} + +DEEPSTATE_INLINE static uint64_t UInt64(void) { + return DeepState_UInt64(); +} + +DEEPSTATE_INLINE static int64_t Int64(void) { + return DeepState_Int64(); +} + +DEEPSTATE_INLINE static uint32_t UInt(void) { + return DeepState_UInt(); +} + +DEEPSTATE_INLINE static int32_t Int(void) { + return DeepState_Int(); +} + +DEEPSTATE_INLINE static uint16_t UShort(void) { + return DeepState_UShort(); +} + +DEEPSTATE_INLINE static int16_t Short(void) { + return DeepState_Short(); +} + +DEEPSTATE_INLINE static unsigned char UChar(void) { + return DeepState_UChar(); +} + +DEEPSTATE_INLINE static char Char(void) { + return DeepState_Char(); +} + +DEEPSTATE_INLINE static bool IsSymbolic(uint64_t x) { + return DeepState_IsSymbolicUInt64(x); +} + +DEEPSTATE_INLINE static int IsSymbolic(int64_t x) { + return DeepState_IsSymbolicInt64(x); +} + +DEEPSTATE_INLINE static bool IsSymbolic(uint32_t x) { + return DeepState_IsSymbolicUInt(x); +} + +DEEPSTATE_INLINE static bool IsSymbolic(int32_t x) { + return DeepState_IsSymbolicInt(x); +} + +DEEPSTATE_INLINE static int IsSymbolic(uint16_t x) { + return DeepState_IsSymbolicUShort(x); +} + +DEEPSTATE_INLINE static bool IsSymbolic(int16_t x) { + return DeepState_IsSymbolicShort(x); +} + +DEEPSTATE_INLINE static bool IsSymbolic(unsigned char x) { + return DeepState_IsSymbolicUChar(x); +} + +DEEPSTATE_INLINE static bool IsSymbolic(char x) { + return DeepState_IsSymbolicChar(x); +} + +DEEPSTATE_INLINE static bool IsSymbolic(float x) { + return DeepState_IsSymbolicFloat(x); +} + +DEEPSTATE_INLINE static bool IsSymbolic(double x) { + return DeepState_IsSymbolicDouble(x); +} + +// A test fixture. +class Test { + public: + Test(void) = default; + ~Test(void) = default; + inline void SetUp(void) {} + inline void TearDown(void) {} + + private: + Test(const Test &) = delete; + Test(Test &&) = delete; + Test &operator=(const Test &) = delete; + Test &operator=(Test &&) = delete; +}; + +template +class Symbolic { + public: + template + DEEPSTATE_INLINE Symbolic(Args&& ...args) + : value(std::forward(args)...) {} + + DEEPSTATE_INLINE Symbolic(void) { + T *val_ptr = &value; + DeepState_SymbolizeData(val_ptr, &(val_ptr[1])); + } + + DEEPSTATE_INLINE operator T (void) const { + return value; + } + + T value; +}; + +template +class Symbolic {}; + +template +class SymbolicLinearContainer { + public: + DEEPSTATE_INLINE explicit SymbolicLinearContainer(size_t len) + : value(len) { + if (!value.empty()) { + DeepState_SymbolizeData(&(value.front()), &(value.back())); + } + } + + DEEPSTATE_INLINE SymbolicLinearContainer(void) { + value.reserve(32); + value.resize(DeepState_SizeInRange(0, 32)); // Avoids symbolic `malloc`. + } + + DEEPSTATE_INLINE operator T (void) const { + return value; + } + + T value; +}; + +template <> +class Symbolic : public SymbolicLinearContainer { + using SymbolicLinearContainer::SymbolicLinearContainer; +}; + +template <> +class Symbolic : public SymbolicLinearContainer { + using SymbolicLinearContainer::SymbolicLinearContainer; +}; + +template +class Symbolic> : + public SymbolicLinearContainer> {}; + +#define MAKE_SYMBOL_SPECIALIZATION(Tname, tname) \ + template <> \ + class Symbolic { \ + public: \ + using SelfType = Symbolic; \ + \ + DEEPSTATE_INLINE Symbolic(void) \ + : value(DeepState_ ## Tname()) {} \ + \ + DEEPSTATE_INLINE Symbolic(tname that) \ + : value(that) {} \ + \ + DEEPSTATE_INLINE Symbolic(const SelfType &that) \ + : value(that.value) {} \ + \ + DEEPSTATE_INLINE Symbolic(SelfType &&that) \ + : value(std::move(that.value)) {} \ + \ + DEEPSTATE_INLINE operator tname (void) const { \ + return value; \ + } \ + SelfType &operator=(const SelfType &that) = default; \ + SelfType &operator=(SelfType &&that) = default; \ + SelfType &operator=(tname that) { \ + value = that; \ + return *this; \ + } \ + SelfType &operator+=(tname that) { \ + value += that; \ + return *this; \ + } \ + SelfType &operator-=(tname that) { \ + value -= that; \ + return *this; \ + } \ + SelfType &operator*=(tname that) { \ + value *= that; \ + return *this; \ + } \ + SelfType &operator/=(tname that) { \ + value /= that; \ + return *this; \ + } \ + SelfType &operator>>=(tname that) { \ + value >>= that; \ + return *this; \ + } \ + SelfType &operator<<=(tname that) { \ + value <<= that; \ + return *this; \ + } \ + tname &operator++(void) { \ + return ++value; \ + } \ + tname operator++(int) { \ + auto prev_value = value; \ + value++; \ + return prev_value; \ + } \ + tname value; \ + }; + +MAKE_SYMBOL_SPECIALIZATION(UInt64, uint64_t) +MAKE_SYMBOL_SPECIALIZATION(Int64, int64_t) +MAKE_SYMBOL_SPECIALIZATION(UInt, uint32_t) +MAKE_SYMBOL_SPECIALIZATION(Int, int32_t) +MAKE_SYMBOL_SPECIALIZATION(UShort, uint16_t) +MAKE_SYMBOL_SPECIALIZATION(Short, int16_t) +MAKE_SYMBOL_SPECIALIZATION(UChar, uint8_t) +MAKE_SYMBOL_SPECIALIZATION(Char, int8_t) + + +using symbolic_char = Symbolic; +using symbolic_short = Symbolic; +using symbolic_int = Symbolic; +using symbolic_unsigned = Symbolic; +using symbolic_long = Symbolic; + +using symbolic_int8_t = Symbolic; +using symbolic_uint8_t = Symbolic; +using symbolic_int16_t = Symbolic; +using symbolic_uint16_t = Symbolic; +using symbolic_int32_t = Symbolic; +using symbolic_uint32_t = Symbolic; +using symbolic_int64_t = Symbolic; +using symbolic_uint64_t = Symbolic; + +#undef MAKE_SYMBOL_SPECIALIZATION + +#define MAKE_MINIMIZER(Type, type) \ + DEEPSTATE_INLINE static type Minimize(type val) { \ + return DeepState_Min ## Type(val); \ + } \ + DEEPSTATE_INLINE static type Maximize(type val) { \ + return DeepState_Max ## Type(val); \ + } + +MAKE_MINIMIZER(UInt, uint32_t) +MAKE_MINIMIZER(Int, int32_t) +MAKE_MINIMIZER(UShort, uint16_t) +MAKE_MINIMIZER(Short, int16_t) +MAKE_MINIMIZER(UChar, uint8_t) +MAKE_MINIMIZER(Char, int8_t) + +#undef MAKE_MINIMIZER + +template +static T Pump(T val, unsigned max=10) { + if (!IsSymbolic(val)) { + return val; + } + if (!max) { + DeepState_Abandon("Must have a positive maximum number of values to Pump"); + } + for (auto i = 0U; i < max - 1; ++i) { + T min_val = Minimize(val); + if (val == min_val) { + DEEPSTATE_USED(min_val); // Force the concrete `min_val` to be returned, + // as opposed to compiler possibly choosing to + // return `val`. + return min_val; + } + } + return Minimize(val); +} + +template +inline static void ForAll(void (*func)(Args...)) { + func(Symbolic()...); +} + +template +inline static void ForAll(Closure func) { + func(Symbolic()...); +} + +#define PureSwarmOneOf(...) _SwarmOneOf(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) +#define MixedSwarmOneOf(...) _SwarmOneOf(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) +#define ProbSwarmOneOf(...) _SwarmOneOf(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) + +#ifndef DEEPSTATE_PURE_SWARM +#ifndef DEEPSTATE_MIXED_SWARM +#ifndef DEEPSTATE_PROB_SWARM +#define OneOf(...) NoSwarmOneOf(__VA_ARGS__) +#endif +#endif +#endif + +#ifdef DEEPSTATE_PURE_SWARM +#define OneOf(...) _SwarmOneOf(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) +#endif + +#ifdef DEEPSTATE_MIXED_SWARM +#define OneOf(...) _SwarmOneOf(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) +#endif + +#ifdef DEEPSTATE_PROB_SWARM +#define OneOf(...) _SwarmOneOf(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) +#endif + +template +inline static void NoSwarmOneOf(FuncTys&&... funcs) { + if (FLAGS_verbose_reads) { + printf("STARTING OneOf CALL\n"); + } + std::function func_arr[sizeof...(FuncTys)] = {funcs...}; + unsigned index = DeepState_UIntInRange( + 0U, static_cast(sizeof...(funcs))-1); + func_arr[Pump(index, sizeof...(funcs))](); + if (FLAGS_verbose_reads) { + printf("FINISHED OneOf CALL\n"); + } +} + +template +inline static void _SwarmOneOf(const char* file, unsigned line, enum DeepState_SwarmType stype, + FuncTys&&... funcs) { + unsigned fcount = static_cast(sizeof...(funcs)); + std::function func_arr[sizeof...(FuncTys)] = {funcs...}; + struct DeepState_SwarmConfig* sc = DeepState_GetSwarmConfig(fcount, file, line, stype); + if (FLAGS_verbose_reads) { + printf("STARTING OneOf CALL\n"); + } + unsigned index = DeepState_UIntInRange(0U, sc->fcount-1); + func_arr[sc->fmap[Pump(index, sc->fcount)]](); + if (FLAGS_verbose_reads) { + printf("FINISHED OneOf CALL\n"); + } +} + +size_t PickIndex(double *probs, size_t length) { + double total = 0.0; + size_t missing = 0; + for (size_t i = 0; i < length; ++i) { + if (probs[i] >= 0.0) { + total += probs[i]; + } else{ + ++missing; + } + } + if (total > 1.0) { + DeepState_Abandon("Probabilities sum to more than 1.0"); + } + if (missing > 0) { + double remainder = (1.0 - total) / missing; + for (size_t i = 0; i < length; ++i) { + if (probs[i] < 0.0) { + probs[i] = remainder; + } + } + } else if (total < 0.999) { + DeepState_Abandon("Total of probabilities is significantly less than 1.0"); + } + + // We cannot use DeepState_Float/Double here because the distribution is very bad + double P = DeepState_UIntInRange(0, 10000000)/10000000.0; + unsigned index = 0; + double sum = 0.0; + while ((index < length) && (P > (sum + probs[index]))) { + sum += probs[index]; + index++; + } + return index; +} + +typedef std::function func_t; + +void ActuallySelectSomething(double *probs, func_t *funcs, size_t length) { + if (FLAGS_verbose_reads) { + printf("STARTING OneOf CALL\n"); + } + + funcs[PickIndex(probs, length)](); + if (FLAGS_verbose_reads) { + printf("FINISHED OneOf CALL\n"); + } +} + +// These two helper functions participate in the splitting. +// Base case does nothing +void SplitArgs(double* probs, func_t *funcs) { +} + +// recursive case +template +void SplitArgs(double* probs, func_t *funcs, double firstProb, TyFunc &&firstFunc, Rest &&... rest) { + *probs = firstProb; + *funcs = firstFunc; + SplitArgs(probs + 1, funcs + 1, std::forward(rest)...); +} + +// The entry point for OneOfP over lambdas +template +void OneOfP(Args &&... args) { + constexpr auto argsLen = sizeof...(Args); + static_assert((argsLen % 2) == 0, "OneOfP expects probability/lambda pairs"); + constexpr auto length = argsLen / 2; + + double probs[length]; + func_t funcs[length]; + SplitArgs(probs, funcs, std::forward(args)...); + + ActuallySelectSomething(probs, funcs, length); +} + +inline static char NoSwarmOneOf(const char *str) { + if (!str || !str[0]) { + DeepState_Abandon("NULL or empty string passed to OneOf"); + } + return str[DeepState_IntInRange(0, strlen(str) - 1)]; +} + +inline static char _SwarmOneOf(const char* file, unsigned line, enum DeepState_SwarmType stype, const char *str) { + if (!str || !str[0]) { + DeepState_Abandon("NULL or empty string passed to OneOf"); + } + unsigned fcount = strlen(str); + struct DeepState_SwarmConfig* sc = DeepState_GetSwarmConfig(fcount, file, line, stype); + unsigned index = sc->fmap[DeepState_UIntInRange(0U, sc->fcount-1)]; + return str[index]; +} + +template +inline static const T &NoSwarmOneOf(std::vector &arr) { + if (arr.empty()) { + DeepState_Abandon("Empty vector passed to OneOf"); + } + return arr[DeepState_IntInRange(0, arr.size() - 1)]; +} + +size_t PickListIndex(std::initializer_list probs, size_t length) { + // The list is interpreted as follows: a negative probability means "use even distribution" + // over all probabilities not specified", and the same strategy is used to fill out the list + // to match the count of items to be chosen among. + if (probs.size() > length) { + DeepState_Abandon("Probability list size greater than number of choices"); + } + double P[length]; + size_t iP = 0; + for (std::initializer_list::iterator it = probs.begin(); it != probs.end(); ++it) { + P[iP++] = *it; + } + while (iP < length) { + P[iP++] = -1.0; + } + + return PickIndex(P, length); +} + +template +inline static const T &OneOfP(std::initializer_list probs, std::vector &arr) { + if (arr.empty()) { + DeepState_Abandon("Empty vector passed to OneOf"); + } + size_t index = PickListIndex(probs, arr.size()); + return arr[index]; +} + +template +inline static const T &_SwarmOneOf(const char* file, unsigned line, enum DeepState_SwarmType stype, const std::vector &arr) { + if (arr.empty()) { + DeepState_Abandon("Empty vector passed to OneOf"); + } + unsigned fcount = arr.size(); + struct DeepState_SwarmConfig* sc = DeepState_GetSwarmConfig(fcount, file, line, stype); + unsigned index = sc->fmap[DeepState_UIntInRange(0U, sc->fcount-1)]; + return arr[index]; +} + +template +inline static const T &NoSwarmOneOf(T (&arr)[len]) { + if (!len) { + DeepState_Abandon("Empty array passed to OneOf"); + } + return arr[DeepState_IntInRange(0, len - 1)]; +} + +template +inline static const T &OneOfP(std::initializer_list probs, T (&arr)[len]) { + if (!len) { + DeepState_Abandon("Empty array passed to OneOf"); + } + size_t index = PickListIndex(probs, len); + return arr[index]; +} + +template +inline static const T &_SwarmOneOf(const char* file, unsigned line, enum DeepState_SwarmType stype, T (&arr)[len]) { + if (!len) { + DeepState_Abandon("Empty array passed to OneOf"); + } + struct DeepState_SwarmConfig* sc = DeepState_GetSwarmConfig(len, file, line, stype); + unsigned index = sc->fmap[DeepState_UIntInRange(0U, sc->fcount-1)]; + return arr[index]; +} + + +template +struct ExpandedCompareIntegral { + template + static DEEPSTATE_INLINE bool Compare(T a, T b, C cmp) { + if (cmp((a & 0xFF), (b & 0xFF))) { + return ExpandedCompareIntegral::Compare(a >> 8, b >> 8, cmp); + } + return DeepState_ZeroSink(k); // Also false. + } +}; + +template +struct ExpandedCompareIntegral { + template + static DEEPSTATE_INLINE bool Compare(T a, T b, C cmp) { + if (cmp((a & 0xFF), (b & 0xFF))) { + return DeepState_ZeroSink(0); + } else { + return DeepState_ZeroSink(100); + } + } +}; + +template +struct DeclType { + using Type = T; +}; + +template +struct DeclType : public DeclType {}; + +template +struct DeclType> : public DeclType {}; + +template +struct DeclType &> : public DeclType {}; + +template +struct IsIntegral : public std::is_integral {}; + +template +struct IsIntegral : public IsIntegral {}; + +template +struct IsIntegral> : public IsIntegral {}; + +template +struct IsSigned : public std::is_signed {}; + +template +struct IsSigned : public IsSigned {}; + +template +struct IsSigned> : public IsSigned {}; + +template +struct IsUnsigned : public std::is_unsigned {}; + +template +struct IsUnsigned : public IsUnsigned {}; + +template +struct IsUnsigned> : public std::is_unsigned {}; + +template +struct BestType { + + // type alias for bools, since std::make_unsigned returns unexpected behavior + using _A = typename std::conditional::value, unsigned int, A>::type; + using _B = typename std::conditional::value, unsigned int, B>::type; + + using UA = typename std::conditional< + IsUnsigned::value, + typename std::make_unsigned<_A>::type, A>::type; + + using UB = typename std::conditional< + IsUnsigned::value, + typename std::make_unsigned<_B>::type, B>::type; + + using Type = typename std::conditional<(sizeof(UA) > sizeof(UB)), + UA, UB>::type; +}; + +template +struct Comparer { + static constexpr bool kIsIntegral = IsIntegral() && IsIntegral(); + static constexpr bool IsBool = std::is_same::value && std::is_same::value; + + struct tag_int {}; + struct tag_not_int {}; + + using tag = typename std::conditional::type; + + template + static DEEPSTATE_INLINE bool Do(const A &a, const B &b, C cmp, tag_not_int) { + return cmp(a, b); + } + + template + static DEEPSTATE_INLINE bool Do(A a, B b, C cmp, tag_int) { + using T = typename ::deepstate::BestType::Type; + if (cmp(a, b)) { + return true; + } + DEEPSTATE_USED(a); // These make the compiler forget everything it knew + DEEPSTATE_USED(b); // about `a` and `b`. + return ::deepstate::ExpandedCompareIntegral::Compare(a, b, cmp); + } + + template + static DEEPSTATE_INLINE bool Do(const A &a, const B &b, C cmp) { + + // IsIntegral returns true for booleans, so we override to basic overloaded method + // if we have boolean template parameters passed to prevent error in ASSERT_EQ + if (IsBool) { + return Do(a, b, cmp, tag_not_int()); + } + return Do(a, b, cmp, tag()); + } + +}; + +#define DeepState_PureSwarmAssignCStr(...) _DeepState_SwarmAssignCStr(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) +#define DeepState_MixedSwarmAssignCStr(...) _DeepState_SwarmAssignCStr(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) +#define DeepState_ProbSwarmAssignCStr(...) _DeepState_SwarmAssignCStr(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) + +#define DeepState_PureSwarmAssignCStrUpToLen(...) _DeepState_SwarmAssignCStr(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) +#define DeepState_MixedSwarmAssignCStrUpToLen(...) _DeepState_SwarmAssignCStr(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) +#define DeepState_ProbSwarmAssignCStrUpToLen(...) _DeepState_SwarmAssignCStr(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) + +#define DeepState_PureSwarmCStr(...) _DeepState_SwarmCStr(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) +#define DeepState_MixedSwarmCStr(...) _DeepState_SwarmCStr(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) +#define DeepState_ProbSwarmCStr(...) _DeepState_SwarmCStr(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) + +#define DeepState_PureSwarmCStrUpToLen(...) _DeepState_SwarmCStr(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) +#define DeepState_MixedSwarmCStrUpToLen(...) _DeepState_SwarmCStr(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) +#define DeepState_ProbSwarmCStrUpToLen(...) _DeepState_SwarmCStr(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) + +#define DeepState_PureSwarmSymbolizeCStr(...) _DeepState_SwarmSymbolizeCStr(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) +#define DeepState_MixedSwarmSymbolizeCStr(...) _DeepState_SwarmSymbolizeCStr(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) +#define DeepState_ProbSwarmSymbolizeCStr(...) _DeepState_SwarmSymbolizeCStr(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) + +#ifndef DEEPSTATE_PURE_SWARM +#ifndef DEEPSTATE_MIXED_SWARM +#ifndef DEEPSTATE_PROB_SWARM +#define DeepState_AssignCStr(...) DeepState_NoSwarmAssignCStr(__VA_ARGS__) +#define DeepState_AssignCStrUpToLen(...) DeepState_NoSwarmAssignCStrUpToLen(__VA_ARGS__) +#define DeepState_CStr(...) DeepState_NoSwarmCStr(__VA_ARGS__) +#define DeepState_CStrUpToLen(...) DeepState_NoSwarmCStrUpToLen(__VA_ARGS__) +#define DeepState_SymbolizeCStr(...) DeepState_NoSwarmSymbolizeCStr(__VA_ARGS__) +#endif +#endif +#endif + + +#ifdef DEEPSTATE_PURE_SWARM +#define DeepState_AssignCStr(...) _DeepState_SwarmAssignCStr(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) +#define DeepState_AssignCStrUpToLen(...) _DeepState_SwarmAssignCStrUpToLen(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) +#define DeepState_CStr(...) _DeepState_SwarmCStr(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) +#define DeepState_CStrUpToLen(...) _DeepState_SwarmCStrUpToLen(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) +#define DeepState_SymbolizeCStr(...) _DeepState_SwarmSymbolizeCStr(__FILE__, __LINE__, DeepState_SwarmTypePure, __VA_ARGS__) +#endif + +#ifdef DEEPSTATE_MIXED_SWARM +#define DeepState_AssignCStr(...) _DeepState_SwarmAssignCStr(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) +#define DeepState_AssignCStrUpToLen(...) _DeepState_SwarmAssignCStrUpToLen(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) +#define DeepState_CStr(...) _DeepState_SwarmCStr(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) +#define DeepState_CStrUpToLen(...) _DeepState_SwarmCStrUpToLen(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) +#define DeepState_SymbolizeCStr(...) _DeepState_SwarmSymbolizeCStr(__FILE__, __LINE__, DeepState_SwarmTypeMixed, __VA_ARGS__) +#endif + +#ifdef DEEPSTATE_PROB_SWARM +#define DeepState_AssignCStr(...) _DeepState_SwarmAssignCStr(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) +#define DeepState_AssignCStrUpToLen(...) _DeepState_SwarmAssignCStrUpToLen(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) +#define DeepState_CStr(...) _DeepState_SwarmCStr(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) +#define DeepState_CStrUpToLen(...) _DeepState_SwarmCStrUpToLen(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) +#define DeepState_SymbolizeCStr(...) _DeepState_SwarmSymbolizeCStr(__FILE__, __LINE__, DeepState_SwarmTypeProb, __VA_ARGS__) +#endif + +/* Like DeepState_AssignCStr_C, but fills in a null `allowed` value. */ +inline static void DeepState_NoSwarmAssignCStr(char* str, size_t len, + const char* allowed = 0) { + DeepState_AssignCStr_C(str, len, allowed); +} + +inline static void _DeepState_SwarmAssignCStr(const char* file, unsigned line, enum DeepState_SwarmType stype, + char* str, size_t len, + const char* allowed = 0) { + DeepState_SwarmAssignCStr_C(file, line, stype, str, len, allowed); +} + +/* Like DeepState_AssignCStr, but Pumps through possible string sizes. */ +inline static void DeepState_NoSwarmAssignCStrUpToLen(char* str, size_t max_len, + const char* allowed = 0) { + uint32_t len = DeepState_UIntInRange(0, max_len); + DeepState_AssignCStr_C(str, Pump(len, max_len+1), allowed); +} + +inline static void _DeepState_SwarmAssignCStrUpToLen(const char* file, unsigned line, enum DeepState_SwarmType stype, + char* str, size_t max_len, + const char* allowed = 0) { + uint32_t len = DeepState_UIntInRange(0, max_len); + DeepState_SwarmAssignCStr_C(file, line, stype, str, Pump(len, max_len+1), allowed); +} + +/* Like DeepState_CStr_C, but fills in a null `allowed` value. */ +inline static char* DeepState_NoSwarmCStr(size_t len, const char* allowed = 0) { + return DeepState_CStr_C(len, allowed); +} + +inline static char* _DeepState_SwarmCStr(const char* file, unsigned line, enum DeepState_SwarmType stype, + size_t len, const char* allowed = 0) { + return DeepState_SwarmCStr_C(file, line, stype, len, allowed); +} + +/* Like DeepState_CStr, but Pumps through possible string sizes. */ +inline static char* DeepState_NoSwarmCStrUpToLen(size_t max_len, const char* allowed = 0) { + uint32_t len = DeepState_UIntInRange(0, max_len); + return DeepState_CStr_C(Pump(len, max_len+1), allowed); +} + +inline static char* _DeepState_SwarmCStrUpToLen(const char* file, unsigned line, enum DeepState_SwarmType stype, + size_t max_len, const char* allowed = 0) { + uint32_t len = DeepState_UIntInRange(0, max_len); + return DeepState_SwarmCStr_C(file, line, stype, Pump(len, max_len+1), allowed); +} + +/* Like DeepState_Symbolize_CStr, but fills in null `allowed` value. */ +inline static void DeepState_NoSwarmSymbolizeCStr(char *begin, const char* allowed = 0) { + DeepState_SymbolizeCStr_C(begin, allowed); +} + +inline static void _DeepState_SwarmSymbolizeCStr(const char* file, unsigned line, enum DeepState_SwarmType stype, + char *begin, const char* allowed = 0) { + DeepState_SwarmSymbolizeCStr_C(file, line, stype, begin, allowed); +} + +} // namespace deepstate + +#define ONE_OF ::deepstate::OneOf + +#define TEST(category, name) \ + DeepState_EntryPoint(category ## _ ## name) + +#define _TEST_F(fixture_name, test_name, file, line) \ + class fixture_name ## _ ## test_name : public fixture_name { \ + public: \ + void DoRunTest(void); \ + static void RunTest(void) { \ + do { \ + fixture_name ## _ ## test_name self; \ + self.SetUp(); \ + self.DoRunTest(); \ + self.TearDown(); \ + } while (false); \ + DeepState_Pass(); \ + } \ + static struct DeepState_TestInfo kTestInfo; \ + }; \ + struct DeepState_TestInfo fixture_name ## _ ## test_name::kTestInfo = { \ + nullptr, \ + fixture_name ## _ ## test_name::RunTest, \ + DEEPSTATE_TO_STR(fixture_name ## _ ## test_name), \ + file, \ + line, \ + }; \ + DEEPSTATE_INITIALIZER(DeepState_Register_ ## test_name) { \ + fixture_name ## _ ## test_name::kTestInfo.prev = DeepState_LastTestInfo; \ + DeepState_LastTestInfo = &(fixture_name ## _ ## test_name::kTestInfo); \ + } \ + void fixture_name ## _ ## test_name :: DoRunTest(void) + + +#define _EXPAND_COMPARE(a, b, op) \ + ([] (decltype(a) __a0, decltype(b) __b0) -> bool { \ + using __A = typename ::deepstate::DeclType::Type; \ + using __B = typename ::deepstate::DeclType::Type; \ + auto __cmp = [] (__A __a4, __B __b4) { return __a4 op __b4; }; \ + return ::deepstate::Comparer<__A, __B>::Do(__a0, __b0, __cmp); \ + })((a), (b)) + +#define TEST_F(fixture_name, test_name) \ + _TEST_F(fixture_name, test_name, __FILE__, __LINE__) + +#define LOG_DEBUG(cond) \ + ::deepstate::Stream(DeepState_LogDebug, (cond), __FILE__, __LINE__) + +#define LOG_TRACE(cond) \ + ::deepstate::Stream(DeepState_LogTrace, (cond), __FILE__, __LINE__) + +#define LOG_INFO(cond) \ + ::deepstate::Stream(DeepState_LogInfo, (cond), __FILE__, __LINE__) + +#define LOG_WARNING(cond) \ + ::deepstate::Stream(DeepState_LogWarning, (cond), __FILE__, __LINE__) + +#define LOG_WARN(cond) \ + ::deepstate::Stream(DeepState_LogWarning, (cond), __FILE__, __LINE__) + +#define LOG_ERROR(cond) \ + ::deepstate::Stream(DeepState_LogError, (cond), __FILE__, __LINE__) + +#define LOG_FATAL(cond) \ + ::deepstate::Stream(DeepState_LogFatal, (cond), __FILE__, __LINE__) + +#define LOG_CRITICAL(cond) \ + ::deepstate::Stream(DeepState_LogFatal, (cond), __FILE__, __LINE__) + +#define LOG(LEVEL) LOG_ ## LEVEL(true) + +#define LOG_IF(LEVEL, cond) LOG_ ## LEVEL(cond) + +#define DEEPSTATE_LOG_EQNE(a, b, op, level) \ + ::deepstate::Stream( \ + level, !(_EXPAND_COMPARE(a, b, op)), __FILE__, __LINE__) + +#define DEEPSTATE_LOG_BINOP(a, b, op, level) \ + ::deepstate::Stream( \ + level, !(a op b), __FILE__, __LINE__) + +#define ASSERT_EQ(a, b) DEEPSTATE_LOG_EQNE(a, b, ==, DeepState_LogFatal) +#define ASSERT_NE(a, b) DEEPSTATE_LOG_EQNE(a, b, !=, DeepState_LogFatal) +#define ASSERT_LT(a, b) DEEPSTATE_LOG_BINOP(a, b, <, DeepState_LogFatal) +#define ASSERT_LE(a, b) DEEPSTATE_LOG_BINOP(a, b, <=, DeepState_LogFatal) +#define ASSERT_GT(a, b) DEEPSTATE_LOG_BINOP(a, b, >, DeepState_LogFatal) +#define ASSERT_GE(a, b) DEEPSTATE_LOG_BINOP(a, b, >=, DeepState_LogFatal) + +#define CHECK_EQ(a, b) DEEPSTATE_LOG_EQNE(a, b, ==, DeepState_LogError) +#define CHECK_NE(a, b) DEEPSTATE_LOG_EQNE(a, b, !=, DeepState_LogError) +#define CHECK_LT(a, b) DEEPSTATE_LOG_BINOP(a, b, <, DeepState_LogError) +#define CHECK_LE(a, b) DEEPSTATE_LOG_BINOP(a, b, <=, DeepState_LogError) +#define CHECK_GT(a, b) DEEPSTATE_LOG_BINOP(a, b, >, DeepState_LogError) +#define CHECK_GE(a, b) DEEPSTATE_LOG_BINOP(a, b, >=, DeepState_LogError) + +#define ASSERT(expr) \ + ::deepstate::Stream( \ + DeepState_LogFatal, !(expr), __FILE__, __LINE__) + +#define ASSERT_TRUE ASSERT +#define ASSERT_FALSE(expr) ASSERT(!(expr)) + +#define CHECK(expr) \ + ::deepstate::Stream( \ + DeepState_LogError, !(expr), __FILE__, __LINE__) + +#define CHECK_TRUE CHECK +#define CHECK_FALSE(expr) CHECK(!(expr)) + +#define ASSUME(expr) \ + DeepState_Assume(expr), ::deepstate::Stream( \ + DeepState_LogTrace, true, __FILE__, __LINE__) + +#define DEEPSTATE_ASSUME_BINOP(a, b, op) \ + DeepState_Assume((a op b)), ::deepstate::Stream( \ + DeepState_LogTrace, true, __FILE__, __LINE__) + +#define ASSUME_EQ(a, b) DEEPSTATE_ASSUME_BINOP(a, b, ==) +#define ASSUME_NE(a, b) DEEPSTATE_ASSUME_BINOP(a, b, !=) +#define ASSUME_LT(a, b) DEEPSTATE_ASSUME_BINOP(a, b, <) +#define ASSUME_LE(a, b) DEEPSTATE_ASSUME_BINOP(a, b, <=) +#define ASSUME_GT(a, b) DEEPSTATE_ASSUME_BINOP(a, b, >) +#define ASSUME_GE(a, b) DEEPSTATE_ASSUME_BINOP(a, b, >=) + +#endif // SRC_INCLUDE_DEEPSTATE_DEEPSTATE_HPP_ diff --git a/src/include/deepstate/Klee.h b/src/include/deepstate/Klee.h index 0387648b..ce6f79cc 100644 --- a/src/include/deepstate/Klee.h +++ b/src/include/deepstate/Klee.h @@ -1,151 +1,151 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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. - */ - -#ifndef SRC_INCLUDE_DEEPSTATE_KLEE_H_ -#define SRC_INCLUDE_DEEPSTATE_KLEE_H_ - -#include - -DEEPSTATE_BEGIN_EXTERN_C - -/* Unsupported. */ -/* static void klee_define_fixed_object(void *addr, size_t nbytes); */ - -static void klee_make_symbolic(void *addr, size_t nbytes, const char *name) { - DeepState_SymbolizeData(addr, addr + nbytes); -} - -static int klee_range(int begin, int end, const char *name) { - return DeepState_IntInRange(begin, end); -} - -static int klee_int(const char *name) { - return DeepState_Int(); -} - -DEEPSTATE_NORETURN static void klee_silent_exit(int status) { - exit(status); -} - -DEEPSTATE_NORETURN static void klee_abort(void) { - abort(); -} - -/* Unsupported. */ -/* static size_t klee_get_obj_size(void *ptr); */ - -static void klee_print_expr(const char *msg, ...) { - /* KLEE debugging command, no DeepState equivalent. */ - /* See impl in `runtime/Runtest/intrinsics.c`. */ -} - -static uintptr_t klee_choose(uintptr_t n) { - uintptr_t out; - klee_make_symbolic(&out, sizeof(out), "klee_choose"); - - if (n <= out) { - klee_silent_exit(0); - } - - return out; -} - -/* Unsupported. */ -/* static unsigned klee_is_symbolic(uintptr_t n); */ - -/* Unsupported. */ -/* static void klee_assume(uintptr_t condition); */ - -static void klee_warning(const char *message) { - DeepState_Log(DeepState_LogWarning, message); -} - -static void klee_warning_once(const char *message) { - DeepState_Log(DeepState_LogWarning, message); -} - -static void klee_prefer_cex(void *object, uintptr_t condition) { - /* KLEE engine command, no DeepState equivalent. */ -} - -static void klee_posix_prefer_cex(void *object, uintptr_t condition) { - /* KLEE engine command, no DeepState equivalent. */ -} - -/* Unsupported. */ -/* static void klee_mark_global(void *object); */ - -#define KLEE_GET_VALUE(suffix, type) type klee_get_value ## suffix(type val) - -/* Unsupported. */ -/* static KLEE_GET_VALUE(f, float); */ - -/* Unsupported. */ -/* static KLEE_GET_VALUE(d, double); */ - -static KLEE_GET_VALUE(l, long) { - if (sizeof(long) == sizeof(int)) { - return DeepState_MinInt((int) val); - } else { - // TODO: We need a MinInt64 function. - return DeepState_MinInt(val); - } -} - -/* Unsupported. */ -/* static KLEE_GET_VALUE(ll, long long) */ - -/* TODO(joe): Implement */ -static KLEE_GET_VALUE(_i32, int32_t) { - return DeepState_MinInt(val); -} - -/* TODO(joe): Implement */ -/* Unsupported. */ -/* static KLEE_GET_VALUE(_i64, int64_t); */ - -#undef KLEE_GET_VALUE - -/* Unsupported. */ -/* static void klee_check_memory_access(const void *address, size_t size); */ - -static void klee_set_forking(unsigned enable) { - /* KLEE engine command, no DeepState equivalent. */ -} - -/* Unsupported. */ -/* static void - * klee_alias_function(const char *fn_name, const char *new_fn_name); */ - -static void klee_stack_trace(void) { - /* KLEE debugging command, no DeepState equivalent. */ -} - -static void klee_print_range(const char *name, int arg) { - /* KLEE debugging command, no DeepState equivalent. */ -} - -static void klee_open_merge(void) { - /* KLEE engine command, no DeepState equivalent. */ -} - -static void klee_close_merge(void) { - /* KLEE engine command, no DeepState equivalent. */ -} - -DEEPSTATE_END_EXTERN_C - -#endif /* SRC_INCLUDE_DEEPSTATE_KLEE_H_ */ +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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. + */ + +#ifndef SRC_INCLUDE_DEEPSTATE_KLEE_H_ +#define SRC_INCLUDE_DEEPSTATE_KLEE_H_ + +#include + +DEEPSTATE_BEGIN_EXTERN_C + +/* Unsupported. */ +/* static void klee_define_fixed_object(void *addr, size_t nbytes); */ + +static void klee_make_symbolic(void *addr, size_t nbytes, const char *name) { + DeepState_SymbolizeData(addr, addr + nbytes); +} + +static int klee_range(int begin, int end, const char *name) { + return DeepState_IntInRange(begin, end); +} + +static int klee_int(const char *name) { + return DeepState_Int(); +} + +DEEPSTATE_NORETURN static void klee_silent_exit(int status) { + exit(status); +} + +DEEPSTATE_NORETURN static void klee_abort(void) { + abort(); +} + +/* Unsupported. */ +/* static size_t klee_get_obj_size(void *ptr); */ + +static void klee_print_expr(const char *msg, ...) { + /* KLEE debugging command, no DeepState equivalent. */ + /* See impl in `runtime/Runtest/intrinsics.c`. */ +} + +static uintptr_t klee_choose(uintptr_t n) { + uintptr_t out; + klee_make_symbolic(&out, sizeof(out), "klee_choose"); + + if (n <= out) { + klee_silent_exit(0); + } + + return out; +} + +/* Unsupported. */ +/* static unsigned klee_is_symbolic(uintptr_t n); */ + +/* Unsupported. */ +/* static void klee_assume(uintptr_t condition); */ + +static void klee_warning(const char *message) { + DeepState_Log(DeepState_LogWarning, message); +} + +static void klee_warning_once(const char *message) { + DeepState_Log(DeepState_LogWarning, message); +} + +static void klee_prefer_cex(void *object, uintptr_t condition) { + /* KLEE engine command, no DeepState equivalent. */ +} + +static void klee_posix_prefer_cex(void *object, uintptr_t condition) { + /* KLEE engine command, no DeepState equivalent. */ +} + +/* Unsupported. */ +/* static void klee_mark_global(void *object); */ + +#define KLEE_GET_VALUE(suffix, type) type klee_get_value ## suffix(type val) + +/* Unsupported. */ +/* static KLEE_GET_VALUE(f, float); */ + +/* Unsupported. */ +/* static KLEE_GET_VALUE(d, double); */ + +static KLEE_GET_VALUE(l, long) { + if (sizeof(long) == sizeof(int)) { + return DeepState_MinInt((int) val); + } else { + // TODO: We need a MinInt64 function. + return DeepState_MinInt(val); + } +} + +/* Unsupported. */ +/* static KLEE_GET_VALUE(ll, long long) */ + +/* TODO(joe): Implement */ +static KLEE_GET_VALUE(_i32, int32_t) { + return DeepState_MinInt(val); +} + +/* TODO(joe): Implement */ +/* Unsupported. */ +/* static KLEE_GET_VALUE(_i64, int64_t); */ + +#undef KLEE_GET_VALUE + +/* Unsupported. */ +/* static void klee_check_memory_access(const void *address, size_t size); */ + +static void klee_set_forking(unsigned enable) { + /* KLEE engine command, no DeepState equivalent. */ +} + +/* Unsupported. */ +/* static void + * klee_alias_function(const char *fn_name, const char *new_fn_name); */ + +static void klee_stack_trace(void) { + /* KLEE debugging command, no DeepState equivalent. */ +} + +static void klee_print_range(const char *name, int arg) { + /* KLEE debugging command, no DeepState equivalent. */ +} + +static void klee_open_merge(void) { + /* KLEE engine command, no DeepState equivalent. */ +} + +static void klee_close_merge(void) { + /* KLEE engine command, no DeepState equivalent. */ +} + +DEEPSTATE_END_EXTERN_C + +#endif /* SRC_INCLUDE_DEEPSTATE_KLEE_H_ */ diff --git a/src/include/deepstate/Log.h b/src/include/deepstate/Log.h index 474a7d25..3dea2bcc 100644 --- a/src/include/deepstate/Log.h +++ b/src/include/deepstate/Log.h @@ -1,60 +1,60 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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. - */ - -#ifndef SRC_INCLUDE_DEEPSTATE_LOG_H_ -#define SRC_INCLUDE_DEEPSTATE_LOG_H_ - -#include - -#include - -DEEPSTATE_BEGIN_EXTERN_C - -extern int DeepState_UsingLibFuzzer; -extern int DeepState_UsingSymExec; - -struct DeepState_Stream; - -struct DeepState_VarArgs { - va_list args; -}; - -enum DeepState_LogLevel { - DeepState_LogDebug = 0, - DeepState_LogTrace = 1, - DeepState_LogInfo = 2, - DeepState_LogWarning = 3, - DeepState_LogWarn = DeepState_LogWarning, - DeepState_LogError = 4, - DeepState_LogExternal = 5, - DeepState_LogFatal = 6, - DeepState_LogCritical = DeepState_LogFatal, -}; - -/* Log a C string. */ -extern void DeepState_Log(enum DeepState_LogLevel level, const char *str); - -/* Log some formatted output. */ -extern void DeepState_LogFormat(enum DeepState_LogLevel level, - const char *format, ...); - -/* Log some formatted output. */ -extern void DeepState_LogVFormat(enum DeepState_LogLevel level, - const char *format, va_list args); - -DEEPSTATE_END_EXTERN_C - -#endif /* SRC_INCLUDE_DEEPSTATE_LOG_H_ */ +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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. + */ + +#ifndef SRC_INCLUDE_DEEPSTATE_LOG_H_ +#define SRC_INCLUDE_DEEPSTATE_LOG_H_ + +#include + +#include + +DEEPSTATE_BEGIN_EXTERN_C + +extern int DeepState_UsingLibFuzzer; +extern int DeepState_UsingSymExec; + +struct DeepState_Stream; + +struct DeepState_VarArgs { + va_list args; +}; + +enum DeepState_LogLevel { + DeepState_LogDebug = 0, + DeepState_LogTrace = 1, + DeepState_LogInfo = 2, + DeepState_LogWarning = 3, + DeepState_LogWarn = DeepState_LogWarning, + DeepState_LogError = 4, + DeepState_LogExternal = 5, + DeepState_LogFatal = 6, + DeepState_LogCritical = DeepState_LogFatal, +}; + +/* Log a C string. */ +extern void DeepState_Log(enum DeepState_LogLevel level, const char *str); + +/* Log some formatted output. */ +extern void DeepState_LogFormat(enum DeepState_LogLevel level, + const char *format, ...); + +/* Log some formatted output. */ +extern void DeepState_LogVFormat(enum DeepState_LogLevel level, + const char *format, va_list args); + +DEEPSTATE_END_EXTERN_C + +#endif /* SRC_INCLUDE_DEEPSTATE_LOG_H_ */ diff --git a/src/include/deepstate/Option.h b/src/include/deepstate/Option.h index d4e7e64e..9d1ac52a 100644 --- a/src/include/deepstate/Option.h +++ b/src/include/deepstate/Option.h @@ -1,130 +1,130 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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. - */ - -#ifndef SRC_INCLUDE_DEEPSTATE_OPTION_H_ -#define SRC_INCLUDE_DEEPSTATE_OPTION_H_ - -#include "deepstate/Compiler.h" - -#include - -#define DEEPSTATE_FLAG_NAME(name) FLAGS_ ## name -#define DEEPSTATE_HAS_DEEPSTATE_FLAG_NAME(name) HAS_FLAG_ ## name - -#define DEEPSTATE_REGISTER_OPTION(name, group, parser, docstring) \ - static struct DeepState_Option DeepState_Option_ ## name = { \ - NULL, \ - DEEPSTATE_TO_STR(name), \ - DEEPSTATE_TO_STR(no_ ## name), \ - group, \ - &parser, \ - (void *) &DEEPSTATE_FLAG_NAME(name), \ - &DEEPSTATE_HAS_DEEPSTATE_FLAG_NAME(name), \ - docstring, \ - }; \ - DEEPSTATE_INITIALIZER(DeepState_AddOption_ ## name) { \ - DeepState_AddOption(&(DeepState_Option_ ## name)); \ - } - -#define DEFINE_string(name, group, default_value, docstring) \ - DECLARE_string(name); \ - DEEPSTATE_REGISTER_OPTION(name, group, DeepState_ParseStringOption, docstring) \ - int DEEPSTATE_HAS_DEEPSTATE_FLAG_NAME(name) = 0; \ - const char *DEEPSTATE_FLAG_NAME(name) = default_value - -#define DECLARE_string(name) \ - extern int DEEPSTATE_HAS_DEEPSTATE_FLAG_NAME(name); \ - extern const char *DEEPSTATE_FLAG_NAME(name) - -#define DEFINE_bool(name, group, default_value, docstring) \ - DECLARE_bool(name); \ - DEEPSTATE_REGISTER_OPTION(name, group, DeepState_ParseBoolOption, docstring) \ - int DEEPSTATE_HAS_DEEPSTATE_FLAG_NAME(name) = 0; \ - int DEEPSTATE_FLAG_NAME(name) = default_value - -#define DECLARE_bool(name) \ - extern int DEEPSTATE_HAS_DEEPSTATE_FLAG_NAME(name); \ - extern int DEEPSTATE_FLAG_NAME(name) - -#define DEFINE_int(name, group, default_value, docstring) \ - DECLARE_int(name); \ - DEEPSTATE_REGISTER_OPTION(name, group, DeepState_ParseIntOption, docstring) \ - int DEEPSTATE_HAS_DEEPSTATE_FLAG_NAME(name) = 0; \ - int DEEPSTATE_FLAG_NAME(name) = default_value - -#define DECLARE_int(name) \ - extern int DEEPSTATE_HAS_DEEPSTATE_FLAG_NAME(name); \ - extern int DEEPSTATE_FLAG_NAME(name) - -#define DEFINE_uint(name, group, default_value, docstring) \ - DECLARE_uint(name); \ - DEEPSTATE_REGISTER_OPTION(name, group, DeepState_ParseUIntOption, docstring) \ - int DEEPSTATE_HAS_DEEPSTATE_FLAG_NAME(name) = 0; \ - unsigned DEEPSTATE_FLAG_NAME(name) = default_value - -#define DECLARE_uint(name) \ - extern int DEEPSTATE_HAS_DEEPSTATE_FLAG_NAME(name); \ - extern unsigned DEEPSTATE_FLAG_NAME(name) -DEEPSTATE_BEGIN_EXTERN_C - -/* Enum for defining command-line groups that options can reside under */ -typedef enum { - InputOutputGroup, - AnalysisGroup, - ExecutionGroup, - TestSelectionGroup, - MiscGroup, -} DeepState_OptGroup; - -/* Backing structure for describing command-line options to DeepState. */ -struct DeepState_Option { - struct DeepState_Option *next; - const char * const name; - const char * const alt_name; /* Only used for booleans. */ - DeepState_OptGroup group; - void (* const parse)(struct DeepState_Option *); - void * const value; - int * const has_value; - const char * const docstring; -}; - -extern int DeepState_OptionsAreInitialized; - -/* Initialize the options from the command-line arguments. */ -void DeepState_InitOptions(int argc, ... /* const char **argv */); - -/* Works for `--help` option: print out each options along with - * their documentation. */ -void DeepState_PrintAllOptions(const char *prog_name); - -/* Initialize an option. */ -void DeepState_AddOption(struct DeepState_Option *option); - -/* Parse an option that is a string. */ -void DeepState_ParseStringOption(struct DeepState_Option *option); - -/* Parse an option that will be interpreted as a boolean value. */ -void DeepState_ParseBoolOption(struct DeepState_Option *option); - -/* Parse an option that will be interpreted as an integer. */ -void DeepState_ParseIntOption(struct DeepState_Option *option); - -/* Parse an option that will be interpreted as an unsigned integer. */ -void DeepState_ParseUIntOption(struct DeepState_Option *option); - -DEEPSTATE_END_EXTERN_C - -#endif /* SRC_INCLUDE_DEEPSTATE_OPTION_H_ */ +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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. + */ + +#ifndef SRC_INCLUDE_DEEPSTATE_OPTION_H_ +#define SRC_INCLUDE_DEEPSTATE_OPTION_H_ + +#include "deepstate/Compiler.h" + +#include + +#define DEEPSTATE_FLAG_NAME(name) FLAGS_ ## name +#define DEEPSTATE_HAS_DEEPSTATE_FLAG_NAME(name) HAS_FLAG_ ## name + +#define DEEPSTATE_REGISTER_OPTION(name, group, parser, docstring) \ + static struct DeepState_Option DeepState_Option_ ## name = { \ + NULL, \ + DEEPSTATE_TO_STR(name), \ + DEEPSTATE_TO_STR(no_ ## name), \ + group, \ + &parser, \ + (void *) &DEEPSTATE_FLAG_NAME(name), \ + &DEEPSTATE_HAS_DEEPSTATE_FLAG_NAME(name), \ + docstring, \ + }; \ + DEEPSTATE_INITIALIZER(DeepState_AddOption_ ## name) { \ + DeepState_AddOption(&(DeepState_Option_ ## name)); \ + } + +#define DEFINE_string(name, group, default_value, docstring) \ + DECLARE_string(name); \ + DEEPSTATE_REGISTER_OPTION(name, group, DeepState_ParseStringOption, docstring) \ + int DEEPSTATE_HAS_DEEPSTATE_FLAG_NAME(name) = 0; \ + const char *DEEPSTATE_FLAG_NAME(name) = default_value + +#define DECLARE_string(name) \ + extern int DEEPSTATE_HAS_DEEPSTATE_FLAG_NAME(name); \ + extern const char *DEEPSTATE_FLAG_NAME(name) + +#define DEFINE_bool(name, group, default_value, docstring) \ + DECLARE_bool(name); \ + DEEPSTATE_REGISTER_OPTION(name, group, DeepState_ParseBoolOption, docstring) \ + int DEEPSTATE_HAS_DEEPSTATE_FLAG_NAME(name) = 0; \ + int DEEPSTATE_FLAG_NAME(name) = default_value + +#define DECLARE_bool(name) \ + extern int DEEPSTATE_HAS_DEEPSTATE_FLAG_NAME(name); \ + extern int DEEPSTATE_FLAG_NAME(name) + +#define DEFINE_int(name, group, default_value, docstring) \ + DECLARE_int(name); \ + DEEPSTATE_REGISTER_OPTION(name, group, DeepState_ParseIntOption, docstring) \ + int DEEPSTATE_HAS_DEEPSTATE_FLAG_NAME(name) = 0; \ + int DEEPSTATE_FLAG_NAME(name) = default_value + +#define DECLARE_int(name) \ + extern int DEEPSTATE_HAS_DEEPSTATE_FLAG_NAME(name); \ + extern int DEEPSTATE_FLAG_NAME(name) + +#define DEFINE_uint(name, group, default_value, docstring) \ + DECLARE_uint(name); \ + DEEPSTATE_REGISTER_OPTION(name, group, DeepState_ParseUIntOption, docstring) \ + int DEEPSTATE_HAS_DEEPSTATE_FLAG_NAME(name) = 0; \ + unsigned DEEPSTATE_FLAG_NAME(name) = default_value + +#define DECLARE_uint(name) \ + extern int DEEPSTATE_HAS_DEEPSTATE_FLAG_NAME(name); \ + extern unsigned DEEPSTATE_FLAG_NAME(name) +DEEPSTATE_BEGIN_EXTERN_C + +/* Enum for defining command-line groups that options can reside under */ +typedef enum { + InputOutputGroup, + AnalysisGroup, + ExecutionGroup, + TestSelectionGroup, + MiscGroup, +} DeepState_OptGroup; + +/* Backing structure for describing command-line options to DeepState. */ +struct DeepState_Option { + struct DeepState_Option *next; + const char * const name; + const char * const alt_name; /* Only used for booleans. */ + DeepState_OptGroup group; + void (* const parse)(struct DeepState_Option *); + void * const value; + int * const has_value; + const char * const docstring; +}; + +extern int DeepState_OptionsAreInitialized; + +/* Initialize the options from the command-line arguments. */ +void DeepState_InitOptions(int argc, ... /* const char **argv */); + +/* Works for `--help` option: print out each options along with + * their documentation. */ +void DeepState_PrintAllOptions(const char *prog_name); + +/* Initialize an option. */ +void DeepState_AddOption(struct DeepState_Option *option); + +/* Parse an option that is a string. */ +void DeepState_ParseStringOption(struct DeepState_Option *option); + +/* Parse an option that will be interpreted as a boolean value. */ +void DeepState_ParseBoolOption(struct DeepState_Option *option); + +/* Parse an option that will be interpreted as an integer. */ +void DeepState_ParseIntOption(struct DeepState_Option *option); + +/* Parse an option that will be interpreted as an unsigned integer. */ +void DeepState_ParseUIntOption(struct DeepState_Option *option); + +DEEPSTATE_END_EXTERN_C + +#endif /* SRC_INCLUDE_DEEPSTATE_OPTION_H_ */ diff --git a/src/include/deepstate/Platform.h b/src/include/deepstate/Platform.h index ee4b5640..ad0f8d03 100644 --- a/src/include/deepstate/Platform.h +++ b/src/include/deepstate/Platform.h @@ -1,74 +1,74 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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. - */ - -#ifndef SRC_INCLUDE_DEEPSTATE_PLATFORM_H_ -#define SRC_INCLUDE_DEEPSTATE_PLATFORM_H_ - -#include - -DEEPSTATE_BEGIN_EXTERN_C - -/* Contains information about a test case */ -struct DeepState_TestInfo; - -#if defined(_WIN32) || defined(_MSC_VER) - -DECLARE_bool(direct_run); - -/* Maximum command length on Windows */ -#define MAX_CMD_LEN 512 - -/* Enables the direct_run flag */ -#define ENABLE_DIRECT_RUN_FLAG if (FLAGS_direct_run) { \ - DeepState_RunSingle(); \ - return 0; \ -} - -/* Match a regular expression pattern inside a given string - * TODO: implementation for Windows - * See issue: https://github.com/trailofbits/deepstate/issues/429 */ -#define REG_MATCH(PATTERN, STRING) false - -/* PRId64 definition */ -#if !defined(PRId64) - #define PRId64 "lld" -#endif - -/* Direct run of a test in a new Windows process. Platform specific function. */ -extern int DeepState_RunTestWin(struct DeepState_TestInfo *test); - -/* Direct run of a single test case. Platform specific function. */ -extern void DeepState_RunSingle(); - - -#elif defined(__unix) - -#include -#include -#include -#include -#include - -#define ENABLE_DIRECT_RUN_FLAG - -/* Match a regular expression pattern inside a given string */ -#define REG_MATCH(PATTERN, STRING) (fnmatch(PATTERN, STRING, FNM_NOESCAPE)) - -#endif - -DEEPSTATE_END_EXTERN_C - +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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. + */ + +#ifndef SRC_INCLUDE_DEEPSTATE_PLATFORM_H_ +#define SRC_INCLUDE_DEEPSTATE_PLATFORM_H_ + +#include + +DEEPSTATE_BEGIN_EXTERN_C + +/* Contains information about a test case */ +struct DeepState_TestInfo; + +#if defined(_WIN32) || defined(_MSC_VER) + +DECLARE_bool(direct_run); + +/* Maximum command length on Windows */ +#define MAX_CMD_LEN 512 + +/* Enables the direct_run flag */ +#define ENABLE_DIRECT_RUN_FLAG if (FLAGS_direct_run) { \ + DeepState_RunSingle(); \ + return 0; \ +} + +/* Match a regular expression pattern inside a given string + * TODO: implementation for Windows + * See issue: https://github.com/trailofbits/deepstate/issues/429 */ +#define REG_MATCH(PATTERN, STRING) false + +/* PRId64 definition */ +#if !defined(PRId64) + #define PRId64 "lld" +#endif + +/* Direct run of a test in a new Windows process. Platform specific function. */ +extern int DeepState_RunTestWin(struct DeepState_TestInfo *test); + +/* Direct run of a single test case. Platform specific function. */ +extern void DeepState_RunSingle(); + + +#elif defined(__unix) + +#include +#include +#include +#include +#include + +#define ENABLE_DIRECT_RUN_FLAG + +/* Match a regular expression pattern inside a given string */ +#define REG_MATCH(PATTERN, STRING) (fnmatch(PATTERN, STRING, FNM_NOESCAPE)) + +#endif + +DEEPSTATE_END_EXTERN_C + #endif /* SRC_INCLUDE_DEEPSTATE_PLATFORM_H_ */ \ No newline at end of file diff --git a/src/include/deepstate/RandGen.h b/src/include/deepstate/RandGen.h new file mode 100644 index 00000000..8700a9f8 --- /dev/null +++ b/src/include/deepstate/RandGen.h @@ -0,0 +1,31 @@ +#pragma once +#include + +typedef struct { + uint32_t mwc_upper; + uint32_t mwc_lower; + uint32_t cong; + uint32_t shr3; +} DeepStateRng; + +static inline void deepstate_rng_seed(DeepStateRng *rng, uint32_t seed) { + rng->mwc_upper = seed ^ 0x9e3779b9u; + rng->mwc_lower = seed ^ 0x243f6a88u; + rng->cong = seed ^ 0xb7e15163u; + rng->shr3 = seed ^ 0x71374491u; + if (rng->shr3 == 0) rng->shr3 = 0xdeadbeef; +} + +static inline uint32_t deepstate_rng_next(DeepStateRng *rng) { + uint64_t mwc = (uint64_t)698769069u * rng->mwc_lower + rng->mwc_upper; + rng->mwc_upper = (uint32_t)(mwc >> 32); + rng->mwc_lower = (uint32_t)mwc; + + rng->cong = 69069u * rng->cong + 12345u; + + rng->shr3 ^= rng->shr3 << 13; + rng->shr3 ^= rng->shr3 >> 17; + rng->shr3 ^= rng->shr3 << 5; + + return rng->mwc_lower ^ rng->cong ^ rng->shr3; +} \ No newline at end of file diff --git a/src/include/deepstate/Stream.h b/src/include/deepstate/Stream.h index 730f94bc..b8b3c0a2 100644 --- a/src/include/deepstate/Stream.h +++ b/src/include/deepstate/Stream.h @@ -1,84 +1,84 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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. - */ - -#ifndef SRC_INCLUDE_DEEPSTATE_STREAM_H_ -#define SRC_INCLUDE_DEEPSTATE_STREAM_H_ - -#include -#include - -#include -#include - -DEEPSTATE_BEGIN_EXTERN_C - -/* Clear the contents of the stream and don't log it. */ -extern void DeepState_ClearStream(enum DeepState_LogLevel level); - -/* Flush the contents of the stream to a log. */ -extern void DeepState_LogStream(enum DeepState_LogLevel level); - -/* Stream a C string into the stream's message. */ -extern void DeepState_StreamCStr(enum DeepState_LogLevel level, - const char *begin); - -/* TODO(pag): Implement `DeepState_StreamWCStr` with `wchar_t`. */ - -/* Stream a some data in the inclusive range `[begin, end]` into the - * stream's message. */ -/*extern void DeepState_StreamData( - enum DeepState_LogLevel level, const void *begin, const void *end); */ - -/* Stream some formatted input */ -extern void DeepState_StreamFormat( - enum DeepState_LogLevel level, const char *format, ...); - -/* Stream some formatted input */ -extern void DeepState_StreamVFormat( - enum DeepState_LogLevel level, const char *format, va_list args); - -#define DEEPSTATE_DECLARE_STREAMER(Type, type) \ - extern void DeepState_Stream ## Type( \ - enum DeepState_LogLevel level, type val); - -DEEPSTATE_DECLARE_STREAMER(Double, double); -DEEPSTATE_DECLARE_STREAMER(Pointer, const void *); - -DEEPSTATE_DECLARE_STREAMER(UInt64, uint64_t) -DEEPSTATE_DECLARE_STREAMER(Int64, int64_t) - -DEEPSTATE_DECLARE_STREAMER(UInt32, uint32_t) -DEEPSTATE_DECLARE_STREAMER(Int32, int32_t) - -DEEPSTATE_DECLARE_STREAMER(UInt16, uint16_t) -DEEPSTATE_DECLARE_STREAMER(Int16, int16_t) - -DEEPSTATE_DECLARE_STREAMER(UInt8, uint8_t) -DEEPSTATE_DECLARE_STREAMER(Int8, int8_t) - -#undef DEEPSTATE_DECLARE_STREAMER - -DEEPSTATE_INLINE static void DeepState_StreamFloat( - enum DeepState_LogLevel level, float val) { - DeepState_StreamDouble(level, (double) val); -} - -/* Reset the formatting in a stream. */ -extern void DeepState_StreamResetFormatting(enum DeepState_LogLevel level); - -DEEPSTATE_END_EXTERN_C - -#endif /* SRC_INCLUDE_DEEPSTATE_STREAM_H_ */ +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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. + */ + +#ifndef SRC_INCLUDE_DEEPSTATE_STREAM_H_ +#define SRC_INCLUDE_DEEPSTATE_STREAM_H_ + +#include +#include + +#include +#include + +DEEPSTATE_BEGIN_EXTERN_C + +/* Clear the contents of the stream and don't log it. */ +extern void DeepState_ClearStream(enum DeepState_LogLevel level); + +/* Flush the contents of the stream to a log. */ +extern void DeepState_LogStream(enum DeepState_LogLevel level); + +/* Stream a C string into the stream's message. */ +extern void DeepState_StreamCStr(enum DeepState_LogLevel level, + const char *begin); + +/* TODO(pag): Implement `DeepState_StreamWCStr` with `wchar_t`. */ + +/* Stream a some data in the inclusive range `[begin, end]` into the + * stream's message. */ +/*extern void DeepState_StreamData( + enum DeepState_LogLevel level, const void *begin, const void *end); */ + +/* Stream some formatted input */ +extern void DeepState_StreamFormat( + enum DeepState_LogLevel level, const char *format, ...); + +/* Stream some formatted input */ +extern void DeepState_StreamVFormat( + enum DeepState_LogLevel level, const char *format, va_list args); + +#define DEEPSTATE_DECLARE_STREAMER(Type, type) \ + extern void DeepState_Stream ## Type( \ + enum DeepState_LogLevel level, type val); + +DEEPSTATE_DECLARE_STREAMER(Double, double); +DEEPSTATE_DECLARE_STREAMER(Pointer, const void *); + +DEEPSTATE_DECLARE_STREAMER(UInt64, uint64_t) +DEEPSTATE_DECLARE_STREAMER(Int64, int64_t) + +DEEPSTATE_DECLARE_STREAMER(UInt32, uint32_t) +DEEPSTATE_DECLARE_STREAMER(Int32, int32_t) + +DEEPSTATE_DECLARE_STREAMER(UInt16, uint16_t) +DEEPSTATE_DECLARE_STREAMER(Int16, int16_t) + +DEEPSTATE_DECLARE_STREAMER(UInt8, uint8_t) +DEEPSTATE_DECLARE_STREAMER(Int8, int8_t) + +#undef DEEPSTATE_DECLARE_STREAMER + +DEEPSTATE_INLINE static void DeepState_StreamFloat( + enum DeepState_LogLevel level, float val) { + DeepState_StreamDouble(level, (double) val); +} + +/* Reset the formatting in a stream. */ +extern void DeepState_StreamResetFormatting(enum DeepState_LogLevel level); + +DEEPSTATE_END_EXTERN_C + +#endif /* SRC_INCLUDE_DEEPSTATE_STREAM_H_ */ diff --git a/src/include/deepstate/Stream.hpp b/src/include/deepstate/Stream.hpp index 9c665d0a..1930dba4 100644 --- a/src/include/deepstate/Stream.hpp +++ b/src/include/deepstate/Stream.hpp @@ -1,163 +1,163 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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. - */ -#ifndef SRC_INCLUDE_DEEPSTATE_STREAM_HPP_ -#define SRC_INCLUDE_DEEPSTATE_STREAM_HPP_ - -#include -#include - -#include -#include -#include - -namespace deepstate { - -/* Conditionally stream output to a log using the streaming APIs. */ -class Stream { - public: - DEEPSTATE_INLINE Stream(DeepState_LogLevel level_, bool do_log_, - const char *file, unsigned line) - : level(level_), - do_log(!!DeepState_IsTrue(do_log_)), - has_something_to_log(false) { - DeepState_LogStream(level); - if (do_log) { - DeepState_StreamFormat(level, "%s(%u): ", file, line); - } - } - - DEEPSTATE_INLINE ~Stream() { - if (do_log) { - if (!has_something_to_log) { - DeepState_StreamCStr(level, "Checked condition"); - } - DeepState_LogStream(level); - } - } - - // The issue being addressed here is that there is a many-to-one mapping from the C integral types - // (note 1) to the DeepState integral types (note 2). To address this, we define a operator<< for - // each C integral type which invokes a helper method overloaded on size and unsignedness. - // This helper method then invokes the correct DeepState_StreamXXX method. Adding a level of - // indirection allows us to deal with the many-to-one mapping problem without worrying about - // multiply-defining a method or leaving one out. For example on many systems, because long - // and long long have the same size, operator<<(long) and operator<<(long long) will both invoke - // Stream_IntType_Helper(8, false, int64_t). - // - // Example: - // On my system unsigned short has size 2 and is unsigned. So operator<<(unsigned short val) will call - // Stream_IntType_Helper(IC<2>, IB, uint16_t val) - // where IC<2> is shorthand for std::integral_constant (operand has size 2) - // and IB is shorthand for std::integral_constant (operand is unsigned). - // These strange-looking types are used for "dispatching on tag" so the compiler can pick the - // correct overload of Stream_IntType_Helper at compile time. - // Finally Stream_IntType_Helper(IC<2>, IB, uint16_t val) will call - // Deepstate_Stream_Uint16(level, val) - // - // Note 1: char, signed char, unsigned char (yes there are three), short, unsigned short, - // int, unsigned int, long, unsigned long, long long, unsigned long long. - // Note2: int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t. - -#define DEEPSTATE_DEFINE_CTYPE_STREAMER(CType) \ - DEEPSTATE_INLINE const Stream &operator<<(CType val) const { \ - if (do_log) { \ - auto sizeTag = std::integral_constant(); \ - auto unsignedTag = std::is_unsigned(); \ - Stream_IntType_Helper(sizeTag, unsignedTag, val); \ - has_something_to_log = true; \ - } \ - return *this; \ - } \ - static_assert(true, "") /* force caller to supply a final semicolon */ - - DEEPSTATE_DEFINE_CTYPE_STREAMER(char); - DEEPSTATE_DEFINE_CTYPE_STREAMER(signed char); - DEEPSTATE_DEFINE_CTYPE_STREAMER(unsigned char); - DEEPSTATE_DEFINE_CTYPE_STREAMER(short); - DEEPSTATE_DEFINE_CTYPE_STREAMER(unsigned short); - DEEPSTATE_DEFINE_CTYPE_STREAMER(int); - DEEPSTATE_DEFINE_CTYPE_STREAMER(unsigned int); - DEEPSTATE_DEFINE_CTYPE_STREAMER(long); - DEEPSTATE_DEFINE_CTYPE_STREAMER(unsigned long); - DEEPSTATE_DEFINE_CTYPE_STREAMER(long long); - DEEPSTATE_DEFINE_CTYPE_STREAMER(unsigned long long); -#undef DEEPSTATE_DEFINE_CTYPE_STREAMER - -private: -#define DEEPSTATE_DEFINE_INT_STREAMER_HELPER(CType, DSType) \ - void Stream_IntType_Helper(std::integral_constant /*size_tag*/, \ - std::integral_constant::value> /*unsigned_tag*/, \ - CType val) const { \ - DeepState_Stream ## DSType(level, val); \ - } \ - static_assert(true, "") /* force caller to supply a final semicolon */ - DEEPSTATE_DEFINE_INT_STREAMER_HELPER(int8_t, Int8); - DEEPSTATE_DEFINE_INT_STREAMER_HELPER(uint8_t, UInt8); - DEEPSTATE_DEFINE_INT_STREAMER_HELPER(int16_t, Int16); - DEEPSTATE_DEFINE_INT_STREAMER_HELPER(uint16_t, UInt16); - DEEPSTATE_DEFINE_INT_STREAMER_HELPER(int32_t, Int32); - DEEPSTATE_DEFINE_INT_STREAMER_HELPER(uint32_t, UInt32); - DEEPSTATE_DEFINE_INT_STREAMER_HELPER(int64_t, Int64); - DEEPSTATE_DEFINE_INT_STREAMER_HELPER(uint64_t, UInt64); -#undef DEEPSTATE_DEFINE_INT_STREAMER_HELPER -public: - -#define DEEPSTATE_DEFINE_STREAMER(Type, type, expr) \ - DEEPSTATE_INLINE const Stream &operator<<(type val) const { \ - if (do_log) { \ - DeepState_Stream ## Type(level, expr); \ - has_something_to_log = true; \ - } \ - return *this; \ - } \ - static_assert(true, "") /* force our user to supply a final semicolon */ - - DEEPSTATE_DEFINE_STREAMER(Float, float, val); - DEEPSTATE_DEFINE_STREAMER(Double, double, val); - - DEEPSTATE_DEFINE_STREAMER(CStr, const char *, val); - - DEEPSTATE_DEFINE_STREAMER(Pointer, std::nullptr_t, nullptr); - - template - DEEPSTATE_DEFINE_STREAMER(Pointer, const T *, val); - -#undef DEEPSTATE_DEFINE_STREAMER - - DEEPSTATE_INLINE const Stream &operator<<(const std::string &str) const { - if (do_log && !str.empty()) { - DeepState_StreamCStr(level, str.c_str()); - has_something_to_log = true; - } - return *this; - } - - // TODO(pag): Implement a `std::wstring` streamer. - - public: - Stream() = delete; - Stream(const Stream &) = delete; - Stream &operator=(const Stream &) = delete; - -private: - const DeepState_LogLevel level; - const bool do_log; - mutable bool has_something_to_log; -}; - -} // namespace deepstate - -#endif // SRC_INCLUDE_DEEPSTATE_STREAM_HPP_ +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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. + */ +#ifndef SRC_INCLUDE_DEEPSTATE_STREAM_HPP_ +#define SRC_INCLUDE_DEEPSTATE_STREAM_HPP_ + +#include +#include + +#include +#include +#include + +namespace deepstate { + +/* Conditionally stream output to a log using the streaming APIs. */ +class Stream { + public: + DEEPSTATE_INLINE Stream(DeepState_LogLevel level_, bool do_log_, + const char *file, unsigned line) + : level(level_), + do_log(!!DeepState_IsTrue(do_log_)), + has_something_to_log(false) { + DeepState_LogStream(level); + if (do_log) { + DeepState_StreamFormat(level, "%s(%u): ", file, line); + } + } + + DEEPSTATE_INLINE ~Stream() { + if (do_log) { + if (!has_something_to_log) { + DeepState_StreamCStr(level, "Checked condition"); + } + DeepState_LogStream(level); + } + } + + // The issue being addressed here is that there is a many-to-one mapping from the C integral types + // (note 1) to the DeepState integral types (note 2). To address this, we define a operator<< for + // each C integral type which invokes a helper method overloaded on size and unsignedness. + // This helper method then invokes the correct DeepState_StreamXXX method. Adding a level of + // indirection allows us to deal with the many-to-one mapping problem without worrying about + // multiply-defining a method or leaving one out. For example on many systems, because long + // and long long have the same size, operator<<(long) and operator<<(long long) will both invoke + // Stream_IntType_Helper(8, false, int64_t). + // + // Example: + // On my system unsigned short has size 2 and is unsigned. So operator<<(unsigned short val) will call + // Stream_IntType_Helper(IC<2>, IB, uint16_t val) + // where IC<2> is shorthand for std::integral_constant (operand has size 2) + // and IB is shorthand for std::integral_constant (operand is unsigned). + // These strange-looking types are used for "dispatching on tag" so the compiler can pick the + // correct overload of Stream_IntType_Helper at compile time. + // Finally Stream_IntType_Helper(IC<2>, IB, uint16_t val) will call + // Deepstate_Stream_Uint16(level, val) + // + // Note 1: char, signed char, unsigned char (yes there are three), short, unsigned short, + // int, unsigned int, long, unsigned long, long long, unsigned long long. + // Note2: int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t. + +#define DEEPSTATE_DEFINE_CTYPE_STREAMER(CType) \ + DEEPSTATE_INLINE const Stream &operator<<(CType val) const { \ + if (do_log) { \ + auto sizeTag = std::integral_constant(); \ + auto unsignedTag = std::is_unsigned(); \ + Stream_IntType_Helper(sizeTag, unsignedTag, val); \ + has_something_to_log = true; \ + } \ + return *this; \ + } \ + static_assert(true, "") /* force caller to supply a final semicolon */ + + DEEPSTATE_DEFINE_CTYPE_STREAMER(char); + DEEPSTATE_DEFINE_CTYPE_STREAMER(signed char); + DEEPSTATE_DEFINE_CTYPE_STREAMER(unsigned char); + DEEPSTATE_DEFINE_CTYPE_STREAMER(short); + DEEPSTATE_DEFINE_CTYPE_STREAMER(unsigned short); + DEEPSTATE_DEFINE_CTYPE_STREAMER(int); + DEEPSTATE_DEFINE_CTYPE_STREAMER(unsigned int); + DEEPSTATE_DEFINE_CTYPE_STREAMER(long); + DEEPSTATE_DEFINE_CTYPE_STREAMER(unsigned long); + DEEPSTATE_DEFINE_CTYPE_STREAMER(long long); + DEEPSTATE_DEFINE_CTYPE_STREAMER(unsigned long long); +#undef DEEPSTATE_DEFINE_CTYPE_STREAMER + +private: +#define DEEPSTATE_DEFINE_INT_STREAMER_HELPER(CType, DSType) \ + void Stream_IntType_Helper(std::integral_constant /*size_tag*/, \ + std::integral_constant::value> /*unsigned_tag*/, \ + CType val) const { \ + DeepState_Stream ## DSType(level, val); \ + } \ + static_assert(true, "") /* force caller to supply a final semicolon */ + DEEPSTATE_DEFINE_INT_STREAMER_HELPER(int8_t, Int8); + DEEPSTATE_DEFINE_INT_STREAMER_HELPER(uint8_t, UInt8); + DEEPSTATE_DEFINE_INT_STREAMER_HELPER(int16_t, Int16); + DEEPSTATE_DEFINE_INT_STREAMER_HELPER(uint16_t, UInt16); + DEEPSTATE_DEFINE_INT_STREAMER_HELPER(int32_t, Int32); + DEEPSTATE_DEFINE_INT_STREAMER_HELPER(uint32_t, UInt32); + DEEPSTATE_DEFINE_INT_STREAMER_HELPER(int64_t, Int64); + DEEPSTATE_DEFINE_INT_STREAMER_HELPER(uint64_t, UInt64); +#undef DEEPSTATE_DEFINE_INT_STREAMER_HELPER +public: + +#define DEEPSTATE_DEFINE_STREAMER(Type, type, expr) \ + DEEPSTATE_INLINE const Stream &operator<<(type val) const { \ + if (do_log) { \ + DeepState_Stream ## Type(level, expr); \ + has_something_to_log = true; \ + } \ + return *this; \ + } \ + static_assert(true, "") /* force our user to supply a final semicolon */ + + DEEPSTATE_DEFINE_STREAMER(Float, float, val); + DEEPSTATE_DEFINE_STREAMER(Double, double, val); + + DEEPSTATE_DEFINE_STREAMER(CStr, const char *, val); + + DEEPSTATE_DEFINE_STREAMER(Pointer, std::nullptr_t, nullptr); + + template + DEEPSTATE_DEFINE_STREAMER(Pointer, const T *, val); + +#undef DEEPSTATE_DEFINE_STREAMER + + DEEPSTATE_INLINE const Stream &operator<<(const std::string &str) const { + if (do_log && !str.empty()) { + DeepState_StreamCStr(level, str.c_str()); + has_something_to_log = true; + } + return *this; + } + + // TODO(pag): Implement a `std::wstring` streamer. + + public: + Stream() = delete; + Stream(const Stream &) = delete; + Stream &operator=(const Stream &) = delete; + +private: + const DeepState_LogLevel level; + const bool do_log; + mutable bool has_something_to_log; +}; + +} // namespace deepstate + +#endif // SRC_INCLUDE_DEEPSTATE_STREAM_HPP_ diff --git a/src/lib/DeepState.c b/src/lib/DeepState.c index 271674df..bdede7d8 100644 --- a/src/lib/DeepState.c +++ b/src/lib/DeepState.c @@ -1,1102 +1,1105 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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 "deepstate/DeepState.h" -#include "deepstate/Option.h" -#include "deepstate/Log.h" -#include "DeepState.h" - -#include -#include -#include -#include -#include - -#ifdef DEEPSTATE_TAKEOVER_RAND -#undef rand -#undef srand -#endif - -DEEPSTATE_BEGIN_EXTERN_C - -/* Basic input and output options, specifies files for read/write before and after test analysis */ -DEFINE_string(input_test_dir, InputOutputGroup, "", "Directory of saved tests to run."); -DEFINE_string(input_test_file, InputOutputGroup, "", "Saved test to run."); -DEFINE_string(input_test_files_dir, InputOutputGroup, "", "Directory of saved test files to run (flat structure)."); -DEFINE_string(output_test_dir, InputOutputGroup, "", "Directory where tests will be saved."); -DEFINE_bool(input_stdin, InputOutputGroup, false, "Run a test from stdin."); - -/* Test execution-related options, configures how an execution run is carried out */ -DEFINE_bool(take_over, ExecutionGroup, false, "Replay test cases in take-over mode."); -DEFINE_bool(abort_on_fail, ExecutionGroup, false, "Abort on file replay failure (useful in file fuzzing)."); -DEFINE_bool(exit_on_fail, ExecutionGroup, false, "Exit with status 255 on test failure."); -DEFINE_bool(verbose_reads, ExecutionGroup, false, "Report on bytes being read during execution of test."); -DEFINE_int(min_log_level, ExecutionGroup, 0, "Minimum level of logging to output (default 0, 0=debug, 1=trace, 2=info, ...)."); -DEFINE_int(timeout, ExecutionGroup, 3600, "Timeout for brute force fuzzing."); -DEFINE_uint(num_workers, ExecutionGroup, 1, "Number of workers to spawn for testing and test generation."); -#if defined(_WIN32) || defined(_MSC_VER) -DEFINE_bool(direct_run, ExecutionGroup, false, "Run test function directly."); -#endif - -/* Fuzzing and symex related options, baked in to perform analysis-related tasks without auxiliary tools */ -DEFINE_bool(fuzz, AnalysisGroup, false, "Perform brute force unguided fuzzing."); -DEFINE_bool(random, AnalysisGroup, false, "Run one test with random inputs."); -DEFINE_bool(fuzz_save_passing, AnalysisGroup, false, "Save passing tests during fuzzing."); -DEFINE_bool(fork, AnalysisGroup, true, "Fork when running a test."); -DEFINE_int(seed, AnalysisGroup, 0, "Seed for brute force fuzzing (uses time if not set)."); - -/* Test selection options to configure what test or tests should be executed during a run */ -DEFINE_string(input_which_test, TestSelectionGroup, "", "Test to use with --input_test_file or --input_test_files_dir."); -DEFINE_string(test_filter, TestSelectionGroup, "", "Run all tests matched with wildcard pattern."); -DEFINE_bool(list_tests, TestSelectionGroup, false, "List all available tests instead of running tests."); -DEFINE_bool(boring_only, TestSelectionGroup, false, "Run Boring concrete tests only."); -DEFINE_bool(run_disabled, TestSelectionGroup, false, "Run Disabled tests alongside other tests."); - -/* Set to 1 by Manticore/Angr/etc. when we're running symbolically. */ -int DeepState_UsingSymExec = 0; - -/* Set to 1 when we're using libFuzzer. */ -int DeepState_UsingLibFuzzer = 0; - -/* To make libFuzzer louder on mac OS. */ -int DeepState_LibFuzzerLoud = 0; - -/* Array of DeepState generated allocations. Impossible for there to - * be more than there are input bytes. Index stores where we are. */ -char* DeepState_GeneratedAllocs[DeepState_InputSize]; -uint32_t DeepState_GeneratedAllocsIndex = 0; - -/* Pointer to the last registers DeepState_TestInfo data structure */ -struct DeepState_TestInfo *DeepState_LastTestInfo = NULL; - -/* Pointer to structure for ordered DeepState_TestInfo */ -struct DeepState_TestInfo *DeepState_FirstTestInfo = NULL; - -/* Pointer to the test being run in this process by Dr. Fuzz. */ -static struct DeepState_TestInfo *DeepState_DrFuzzTest = NULL; - -/* Initialize global input buffer and index / initialized index. */ -volatile uint8_t DeepState_Input[DeepState_InputSize] = {}; -uint32_t DeepState_InputIndex = 0; -uint32_t DeepState_InputInitialized = 0; - -/* Used if we need to generate on-the-fly data while we fuzz */ -uint32_t DeepState_InternalFuzzing = 0; - -/* Swarm related state. */ -uint32_t DeepState_SwarmConfigsIndex = 0; -struct DeepState_SwarmConfig *DeepState_SwarmConfigs[DEEPSTATE_MAX_SWARM_CONFIGS]; - -/* Jump buffer for returning to `DeepState_Run`. */ -jmp_buf DeepState_ReturnToRun = {}; - -/* Information about the current test run, if any. */ -extern struct DeepState_TestRunInfo *DeepState_CurrentTestRun = NULL; - -static void DeepState_SetTestPassed(void) { - DeepState_CurrentTestRun->result = DeepState_TestRunPass; -} - -static void DeepState_SetTestFailed(void) { - DeepState_CurrentTestRun->result = DeepState_TestRunFail; -} - -static void DeepState_SetTestAbandoned(const char *reason) { - DeepState_CurrentTestRun->result = DeepState_TestRunAbandon; - DeepState_CurrentTestRun->reason = reason; -} - -void DeepState_InitCurrentTestRun(struct DeepState_TestInfo *test) { - DeepState_CurrentTestRun->test = test; - DeepState_CurrentTestRun->result = DeepState_TestRunPass; - DeepState_CurrentTestRun->reason = NULL; -} - -/* Abandon this test. We've hit some kind of internal problem. */ -DEEPSTATE_NORETURN -void DeepState_Abandon(const char *reason) { - DeepState_Log(DeepState_LogError, reason); - - DeepState_CurrentTestRun->result = DeepState_TestRunAbandon; - DeepState_CurrentTestRun->reason = reason; - - longjmp(DeepState_ReturnToRun, 1); -} - -/* Abandon this test due to failed assumption. Less important to log. */ -DEEPSTATE_NORETURN -void DeepState_Abandon_Due_to_Assumption(const char *reason) { - DeepState_CurrentTestRun->result = DeepState_TestRunAbandon; - DeepState_CurrentTestRun->reason = reason; - - longjmp(DeepState_ReturnToRun, 1); -} - -/* Mark this test as having crashed. */ -void DeepState_Crash(void) { - DeepState_SetTestFailed(); -} - -/* Mark this test as failing. */ -DEEPSTATE_NORETURN -void DeepState_Fail(void) { - DeepState_SetTestFailed(); - - if (FLAGS_take_over) { - // We want to communicate the failure to a parent process, so exit. - exit(DeepState_TestRunFail); - } else { - longjmp(DeepState_ReturnToRun, 1); - } -} - -/* Mark this test as passing. */ -DEEPSTATE_NORETURN -void DeepState_Pass(void) { - longjmp(DeepState_ReturnToRun, 0); -} - -void DeepState_SoftFail(void) { - DeepState_SetTestFailed(); -} - -/* Symbolize the data in the exclusive range `[begin, end)`. */ -void DeepState_SymbolizeData(void *begin, void *end) { - uintptr_t begin_addr = (uintptr_t) begin; - uintptr_t end_addr = (uintptr_t) end; - - if (begin_addr > end_addr) { - DeepState_Abandon("Invalid data bounds for DeepState_SymbolizeData"); - } else if (begin_addr == end_addr) { - return; - } else { - uint8_t *bytes = (uint8_t *) begin; - for (uintptr_t i = 0, max_i = (end_addr - begin_addr); i < max_i; ++i) { - if (DeepState_InputIndex >= DeepState_InputSize) { - DeepState_Abandon("Exceeded set input limit. Set or expand DEEPSTATE_SIZE to write more bytes."); - } - if (FLAGS_verbose_reads) { - printf("Reading byte at %u\n", DeepState_InputIndex); - } - bytes[i] = DEEPSTATE_READBYTE; - } - } -} - -/* Symbolize the data in the exclusive range `[begin, end)` without null - * characters included. Primarily useful for C strings. */ -void DeepState_SymbolizeDataNoNull(void *begin, void *end) { - uintptr_t begin_addr = (uintptr_t) begin; - uintptr_t end_addr = (uintptr_t) end; - - if (begin_addr > end_addr) { - DeepState_Abandon("Invalid data bounds for DeepState_SymbolizeData"); - } else if (begin_addr == end_addr) { - return; - } else { - uint8_t *bytes = (uint8_t *) begin; - for (uintptr_t i = 0, max_i = (end_addr - begin_addr); i < max_i; ++i) { - if (DeepState_InputIndex >= DeepState_InputSize) { - DeepState_Abandon("Exceeded set input limit. Set or expand DEEPSTATE_SIZE to write more bytes."); - } - if (FLAGS_verbose_reads) { - printf("Reading byte at %u\n", DeepState_InputIndex); - } - bytes[i] = DEEPSTATE_READBYTE; - if (bytes[i] == 0) { - bytes[i] = 1; - } - } - } -} - -/* Concretize some data in exclusive the range `[begin, end)`. */ -void *DeepState_ConcretizeData(void *begin, void *end) { - return begin; -} - -/* Assign a symbolic C string of strlen length `len`. str should include - * storage for both `len` characters AND the null terminator. Allowed - * is a set of chars that are allowed (ignored if null). */ -void DeepState_AssignCStr_C(char* str, size_t len, const char* allowed) { - if (SIZE_MAX <= len) { - DeepState_Abandon("Can't create a SIZE_MAX-length string."); - } - if (NULL == str) { - DeepState_Abandon("Attempted to populate null pointer."); - } - if (len) { - if (allowed == 0) { - DeepState_SymbolizeDataNoNull(str, &(str[len])); - } else { - uint32_t allowed_size = strlen(allowed); - for (int i = 0; i < len; i++) { - str[i] = allowed[DeepState_UIntInRange(0, allowed_size-1)]; - } - } - } - str[len] = '\0'; -} - -void DeepState_SwarmAssignCStr_C(const char* file, unsigned line, int stype, - char* str, size_t len, const char* allowed) { - if (SIZE_MAX <= len) { - DeepState_Abandon("Can't create a SIZE_MAX-length string."); - } - if (NULL == str) { - DeepState_Abandon("Attempted to populate null pointer."); - } - char swarm_allowed[256]; - if (allowed == 0) { - /* In swarm mode, if there is no allowed string, create one over all chars. */ - for (int i = 0; i < 255; i++) { - swarm_allowed[i] = i+1; - } - swarm_allowed[255] = 0; - allowed = (const char*)&swarm_allowed; - } - if (len) { - uint32_t allowed_size = strlen(allowed); - struct DeepState_SwarmConfig* sc = DeepState_GetSwarmConfig(allowed_size, file, line, stype); - for (int i = 0; i < len; i++) { - str[i] = allowed[sc->fmap[DeepState_UIntInRange(0U, sc->fcount-1)]]; - } - } - str[len] = '\0'; -} - -/* Return a symbolic C string of strlen `len`. */ -char *DeepState_CStr_C(size_t len, const char* allowed) { - if (SIZE_MAX <= len) { - DeepState_Abandon("Can't create a SIZE_MAX-length string"); - } - char *str = (char *) malloc(sizeof(char) * (len + 1)); - if (NULL == str) { - DeepState_Abandon("Can't allocate memory"); - } - DeepState_GeneratedAllocs[DeepState_GeneratedAllocsIndex++] = str; - if (len) { - if (allowed == 0) { - DeepState_SymbolizeDataNoNull(str, &(str[len])); - } else { - uint32_t allowed_size = strlen(allowed); - for (int i = 0; i < len; i++) { - str[i] = allowed[DeepState_UIntInRange(0, allowed_size-1)]; - } - } - } - str[len] = '\0'; - return str; -} - -char *DeepState_SwarmCStr_C(const char* file, unsigned line, int stype, - size_t len, const char* allowed) { - if (SIZE_MAX <= len) { - DeepState_Abandon("Can't create a SIZE_MAX-length string"); - } - char *str = (char *) malloc(sizeof(char) * (len + 1)); - if (NULL == str) { - DeepState_Abandon("Can't allocate memory"); - } - char swarm_allowed[256]; - if (allowed == 0) { - /* In swarm mode, if there is no allowed string, create one over all chars. */ - for (int i = 0; i < 255; i++) { - swarm_allowed[i] = i+1; - } - swarm_allowed[255] = 0; - allowed = (const char*)&swarm_allowed; - } - DeepState_GeneratedAllocs[DeepState_GeneratedAllocsIndex++] = str; - if (len) { - uint32_t allowed_size = strlen(allowed); - struct DeepState_SwarmConfig* sc = DeepState_GetSwarmConfig(allowed_size, file, line, stype); - for (int i = 0; i < len; i++) { - str[i] = allowed[sc->fmap[DeepState_UIntInRange(0U, sc->fcount-1)]]; - } - } - str[len] = '\0'; - return str; -} - -/* Symbolize a C string; keeps the null terminator where it was. */ -void DeepState_SymbolizeCStr_C(char *begin, const char* allowed) { - if (begin && begin[0]) { - if (allowed == 0) { - DeepState_SymbolizeDataNoNull(begin, begin + strlen(begin)); - } else { - uint32_t allowed_size = strlen(allowed); - uint8_t *bytes = (uint8_t *) begin; - uintptr_t begin_addr = (uintptr_t) begin; - uintptr_t end_addr = (uintptr_t) (begin + strlen(begin)); - for (uintptr_t i = 0, max_i = (end_addr - begin_addr); i < max_i; ++i) { - bytes[i] = allowed[DeepState_UIntInRange(0, allowed_size-1)]; - } - } - } -} - -void DeepState_SwarmSymbolizeCStr_C(const char* file, unsigned line, int stype, - char *begin, const char* allowed) { - if (begin && begin[0]) { - char swarm_allowed[256]; - if (allowed == 0) { - /* In swarm mode, if there is no allowed string, create one over all chars. */ - for (int i = 0; i < 255; i++) { - swarm_allowed[i] = i+1; - } - swarm_allowed[255] = 0; - allowed = (const char*)&swarm_allowed; - } - uint32_t allowed_size = strlen(allowed); - struct DeepState_SwarmConfig* sc = DeepState_GetSwarmConfig(allowed_size, file, line, stype); - uint8_t *bytes = (uint8_t *) begin; - uintptr_t begin_addr = (uintptr_t) begin; - uintptr_t end_addr = (uintptr_t) (begin + strlen(begin)); - for (uintptr_t i = 0, max_i = (end_addr - begin_addr); i < max_i; ++i) { - bytes[i] = allowed[sc->fmap[DeepState_UIntInRange(0U, sc->fcount-1)]]; - } - } -} - -/* Concretize a C string */ -const char *DeepState_ConcretizeCStr(const char *begin) { - return begin; -} - -/* Allocate and return a pointer to `num_bytes` symbolic bytes. */ -void *DeepState_Malloc(size_t num_bytes) { - void *data = malloc(num_bytes); - uintptr_t data_end = ((uintptr_t) data) + num_bytes; - DeepState_SymbolizeData(data, (void *) data_end); - return data; -} - -/* Allocate and return a pointer to `num_bytes` symbolic bytes. */ -void *DeepState_GCMalloc(size_t num_bytes) { - void *data = malloc(num_bytes); - uintptr_t data_end = ((uintptr_t) data) + num_bytes; - DeepState_SymbolizeData(data, (void *) data_end); - DeepState_GeneratedAllocs[DeepState_GeneratedAllocsIndex++] = data; - return data; -} - -/* Portable and architecture-independent memory scrub without dead store elimination. */ -void *DeepState_MemScrub(void *pointer, size_t data_size) { - volatile unsigned char *p = pointer; - while (data_size--) { - *p++ = 0; - } - return pointer; -} - -/* Generate a new swarm configuration. */ -struct DeepState_SwarmConfig *DeepState_NewSwarmConfig(unsigned fcount, const char* file, unsigned line, - enum DeepState_SwarmType stype) { - struct DeepState_SwarmConfig *new_config = malloc(sizeof(struct DeepState_SwarmConfig)); - size_t buf_len = strlen(file) + 1; - new_config->file = malloc(buf_len); - strncpy(new_config->file, file, buf_len); - new_config->line = line; - new_config->orig_fcount = fcount; - new_config->fcount = 0; - if (stype == DeepState_SwarmTypeProb) { - new_config->fmap = malloc(sizeof(unsigned) * fcount * DEEPSTATE_SWARM_MAX_PROB_RATIO); - for (int i = 0; i < fcount; i++) { - unsigned int prob = DeepState_UIntInRange(0U, DEEPSTATE_SWARM_MAX_PROB_RATIO); - for (int j = 0; j < prob; j++) { - new_config->fmap[new_config->fcount++] = i; - } - } - if (new_config->fcount == 0) { - new_config->fmap[new_config->fcount++] = DeepState_UIntInRange(0, fcount-1); - } - } else { - new_config->fmap = malloc(sizeof(unsigned) * fcount); - /* In mix mode, "half" the time just use everything */ - int full_config = (stype == DeepState_SwarmTypeMixed) && DeepState_Bool(); - if ((stype == DeepState_SwarmTypeMixed) && DeepState_UsingSymExec) { - /* We don't want to make additional pointless paths to explore for symex */ - (void) DeepState_Assume(full_config); - } - for (int i = 0; i < fcount; i++) { - if (full_config) { - new_config->fmap[new_config->fcount++] = i; - } else { - int in_swarm = DeepState_Bool(); - if (DeepState_UsingSymExec) { - /* If not in mix mode, just allow everything in each configuration for symex */ - (void) DeepState_Assume(in_swarm); - } - if (in_swarm) { - new_config->fmap[new_config->fcount++] = i; - } - } - } - } - /* We always need to allow at least one option! */ - if (new_config->fcount == 0) { - new_config->fmap[new_config->fcount++] = DeepState_UIntInRange(0, fcount-1); - } - return new_config; -} - -/* Either fetch existing configuration, or generate a new one. */ -struct DeepState_SwarmConfig *DeepState_GetSwarmConfig(unsigned fcount, const char* file, unsigned line, - enum DeepState_SwarmType stype) { - /* In general, there should be few enough OneOfs in a harness that linear search is fine. */ - for (int i = 0; i < DeepState_SwarmConfigsIndex; i++) { - struct DeepState_SwarmConfig* sc = DeepState_SwarmConfigs[i]; - if ((sc->line == line) && (sc->orig_fcount == fcount) && (strncmp(sc->file, file, strlen(file)) == 0)) { - return sc; - } - } - if (DeepState_SwarmConfigsIndex == DEEPSTATE_MAX_SWARM_CONFIGS) { - DeepState_Abandon("Exceeded swarm config limit. Set or expand DEEPSTATE_MAX_SWARM_CONFIGS. This is highly unusual."); - } - DeepState_SwarmConfigs[DeepState_SwarmConfigsIndex] = DeepState_NewSwarmConfig(fcount, file, line, stype); - return DeepState_SwarmConfigs[DeepState_SwarmConfigsIndex++]; -} - -DEEPSTATE_NOINLINE int DeepState_One(void) { - return 1; -} - -DEEPSTATE_NOINLINE int DeepState_Zero(void) { - return 0; -} - -/* Always returns `0`. */ -int DeepState_ZeroSink(int sink) { - (void) sink; - return 0; -} - -/* Returns `1` if `expr` is true, and `0` otherwise. This is kind of an indirect - * way to take a symbolic value, introduce a fork, and on each size, replace its -* value with a concrete value. */ -int DeepState_IsTrue(int expr) { - if (expr == DeepState_Zero()) { - return DeepState_Zero(); - } else { - return DeepState_One(); - } -} - -/* Return a symbolic value of a given type. */ -int DeepState_Bool(void) { - if (DeepState_InputIndex >= DeepState_InputSize) { - DeepState_Abandon("Exceeded set input limit. Set or expand DEEPSTATE_SIZE to write more bytes."); - } - if (FLAGS_verbose_reads) { - printf("Reading byte as boolean at %u\n", DeepState_InputIndex); - } - return DEEPSTATE_READBYTE & 1; -} - - - -#define MAKE_SYMBOL_FUNC(Type, type) \ - type DeepState_ ## Type(void) { \ - if ((DeepState_InputIndex + sizeof(type)) > DeepState_InputSize) { \ - DeepState_Abandon("Exceeded set input limit. Set or expand DEEPSTATE_SIZE to write more bytes."); \ - } \ - type val = 0; \ - if (FLAGS_verbose_reads) { \ - printf("STARTING MULTI-BYTE READ\n"); \ - } \ - _Pragma("unroll") \ - for (size_t i = 0; i < sizeof(type); ++i) { \ - if (FLAGS_verbose_reads) { \ - printf("Reading byte at %u\n", DeepState_InputIndex); \ - } \ - val = (val << 8) | ((type) DEEPSTATE_READBYTE); \ - } \ - if (FLAGS_verbose_reads) { \ - printf("FINISHED MULTI-BYTE READ\n"); \ - } \ - return val; \ - } - - -MAKE_SYMBOL_FUNC(Size, size_t) -MAKE_SYMBOL_FUNC(Long, long) - -float DeepState_Float(void) { - float float_v; - DeepState_SymbolizeData(&float_v, &float_v + 1); - return float_v; -} - -double DeepState_Double(void) { - double double_v; - DeepState_SymbolizeData(&double_v, &double_v + 1); - return double_v; -} - -MAKE_SYMBOL_FUNC(UInt64, uint64_t) -int64_t DeepState_Int64(void) { - return (int64_t) DeepState_UInt64(); -} - -MAKE_SYMBOL_FUNC(UInt, unsigned) -int DeepState_Int(void) { - return (int) DeepState_UInt(); -} - -MAKE_SYMBOL_FUNC(UShort, unsigned short) -short DeepState_Short(void) { - return (short) DeepState_UShort(); -} - -MAKE_SYMBOL_FUNC(UChar, unsigned char) -char DeepState_Char(void) { - return (char) DeepState_UChar(); -} - -#undef MAKE_SYMBOL_FUNC - -float DeepState_FloatInRange(float low, float high) { - if (low > high) { - return DeepState_FloatInRange(high, low); - } - if (low < 0.0) { // Handle negatives differently - if (high > 0.0) { - if (DeepState_Bool()) { - return -(DeepState_FloatInRange(0.0, -low)); - } else { - return DeepState_FloatInRange(0.0, high); - } - } else { - return -(DeepState_FloatInRange(-high, -low)); - } - } - int32_t int_v = DeepState_IntInRange(*(int32_t *)&low, *(int32_t *)&high); - float float_v = *(float*)&int_v; - assume (float_v >= low); - assume (float_v <= high); - return float_v; -} - -double DeepState_DoubleInRange(double low, double high) { - if (low > high) { - return DeepState_DoubleInRange(high, low); - } - if (low < 0.0) { // Handle negatives differently - if (high > 0.0) { - if (DeepState_Bool()) { - return -(DeepState_DoubleInRange(0.0, -low)); - } else { - return DeepState_DoubleInRange(0.0, high); - } - } else { - return -(DeepState_DoubleInRange(-high, -low)); - } - } - int64_t int_v = DeepState_Int64InRange(*(int64_t *)&low, *(int64_t *)&high); - double double_v = *(double*)&int_v; - assume (double_v >= low); - assume (double_v <= high); - return double_v; -} - -int32_t DeepState_RandInt() { - return DeepState_IntInRange(0, RAND_MAX); -} - -/* Returns the minimum satisfiable value for a given symbolic value, given - * the constraints present on that value. */ -uint32_t DeepState_MinUInt(uint32_t v) { - return v; -} - -int32_t DeepState_MinInt(int32_t v) { - return (int32_t) (DeepState_MinUInt(((uint32_t) v) + 0x80000000U) - - 0x80000000U); -} - -/* Returns the maximum satisfiable value for a given symbolic value, given - * the constraints present on that value. */ -uint32_t DeepState_MaxUInt(uint32_t v) { - return v; -} - -int32_t DeepState_MaxInt(int32_t v) { - return (int32_t) (DeepState_MaxUInt(((uint32_t) v) + 0x80000000U) - - 0x80000000U); -} - -/* Function to clean up generated strings, and any other DeepState-managed data. */ -extern void DeepState_CleanUp() { - for (int i = 0; i < DeepState_GeneratedAllocsIndex; i++) { - free(DeepState_GeneratedAllocs[i]); - } - DeepState_GeneratedAllocsIndex = 0; - - for (int i = 0; i < DeepState_SwarmConfigsIndex; i++) { - free(DeepState_SwarmConfigs[i]->file); - free(DeepState_SwarmConfigs[i]->fmap); - free(DeepState_SwarmConfigs[i]); - } - DeepState_SwarmConfigsIndex = 0; -} - -void _DeepState_Assume(int expr, const char *expr_str, const char *file, - unsigned line) { - if (!expr) { - DeepState_LogFormat(DeepState_LogTrace, - "%s(%u): Assumption %s failed", - file, line, expr_str); - DeepState_Abandon_Due_to_Assumption("Assumption failed"); - } -} - -int DeepState_IsSymbolicUInt(uint32_t x) { - (void) x; - return 0; -} - -/* Defined in Stream.c */ -extern void _DeepState_StreamInt(enum DeepState_LogLevel level, - const char *format, - const char *unpack, uint64_t *val); - -extern void _DeepState_StreamFloat(enum DeepState_LogLevel level, - const char *format, - const char *unpack, double *val); - -extern void _DeepState_StreamString(enum DeepState_LogLevel level, - const char *format, - const char *str); - -/* A DeepState-specific symbol that is needed for hooking. */ -struct DeepState_IndexEntry { - const char * const name; - void * const address; -}; - -/* An index of symbols that the symbolic executors will hook or - * need access to. */ -const struct DeepState_IndexEntry DeepState_API[] = { - - /* Control-flow during the test. */ - {"Pass", (void *) DeepState_Pass}, - {"Crash", (void *) DeepState_Crash}, - {"Fail", (void *) DeepState_Fail}, - {"SoftFail", (void *) DeepState_SoftFail}, - {"Abandon", (void *) DeepState_Abandon}, - - /* Locating the tests. */ - {"LastTestInfo", (void *) &DeepState_LastTestInfo}, - - /* Source of symbolic bytes. */ - {"InputBegin", (void *) &(DeepState_Input[0])}, - {"InputEnd", (void *) &(DeepState_Input[DeepState_InputSize])}, - {"InputIndex", (void *) &DeepState_InputIndex}, - - /* Solver APIs. */ - {"Assume", (void *) _DeepState_Assume}, - {"IsSymbolicUInt", (void *) DeepState_IsSymbolicUInt}, - {"ConcretizeData", (void *) DeepState_ConcretizeData}, - {"ConcretizeCStr", (void *) DeepState_ConcretizeCStr}, - {"MinUInt", (void *) DeepState_MinUInt}, - {"MaxUInt", (void *) DeepState_MaxUInt}, - - /* Logging API. */ - {"Log", (void *) DeepState_Log}, - - /* Streaming API for deferred logging. */ - {"ClearStream", (void *) DeepState_ClearStream}, - {"LogStream", (void *) DeepState_LogStream}, - {"StreamInt", (void *) _DeepState_StreamInt}, - {"StreamFloat", (void *) _DeepState_StreamFloat}, - {"StreamString", (void *) _DeepState_StreamString}, - - {"UsingLibFuzzer", (void *) &DeepState_UsingLibFuzzer}, - {"UsingSymExec", (void *) &DeepState_UsingSymExec}, - - {NULL, NULL}, -}; - -/* Set up DeepState. */ -DEEPSTATE_NOINLINE -void DeepState_Setup(void) { - static int was_setup = 0; - if (!was_setup) { - DeepState_AllocCurrentTestRun(); - was_setup = 1; - } - - /* Sort the test cases by line number. */ - struct DeepState_TestInfo *current = DeepState_LastTestInfo; - if (current == NULL) { - DeepState_LogFormat(DeepState_LogError, - "No tests to run have been defined! Did you include the harness to compile?"); - exit(1); - } - struct DeepState_TestInfo *min_node = current->prev; - current->prev = NULL; - - while (min_node != NULL) { - struct DeepState_TestInfo *temp = min_node; - - min_node = min_node->prev; - temp->prev = current; - current = temp; - } - DeepState_FirstTestInfo = current; -} - -/* Tear down DeepState. */ -void DeepState_Teardown(void) { - -} - -/* Notify that we're about to begin a test. */ -void DeepState_Begin(struct DeepState_TestInfo *test) { - DeepState_InitCurrentTestRun(test); - DeepState_LogFormat(DeepState_LogTrace, "Running: %s from %s(%u)", - test->test_name, test->file_name, test->line_number); -} - -void DeepState_Warn_srand(unsigned int seed) { - DeepState_LogFormat(DeepState_LogWarning, - "srand under DeepState has no effect: rand is re-defined as DeepState_Int"); -} - -/* Right now "fake" a hexdigest by just using random bytes. Not ideal. */ -void makeFilename(char *name, size_t size) { - const char *entities = "0123456789abcdef"; - for (int i = 0; i < size; i++) { - name[i] = entities[rand()%16]; - } -} - -void writeInputData(char* name, int important) { - size_t path_len = 2 + sizeof(char) * (strlen(FLAGS_output_test_dir) + strlen(name)); - char *path = (char *) malloc(path_len); - snprintf(path, path_len, "%s/%s", FLAGS_output_test_dir, name); - FILE *fp = fopen(path, "wb"); - if (fp == NULL) { - DeepState_LogFormat(DeepState_LogError, "Failed to create file `%s`", path); - free(path); - return; - } - size_t written = fwrite((void *)DeepState_Input, 1, DeepState_InputIndex, fp); - if (written != DeepState_InputIndex) { - DeepState_LogFormat(DeepState_LogError, "Failed to write to file `%s`", path); - } else { - if (important) { - DeepState_LogFormat(DeepState_LogInfo, "Saved test case in file `%s`", path); - } else { - DeepState_LogFormat(DeepState_LogTrace, "Saved test case in file `%s`", path); - } - } - free(path); - fclose(fp); -} - -/* Save a passing test to the output test directory. */ -void DeepState_SavePassingTest(void) { - char name[48]; - makeFilename(name, 40); - name[40] = 0; - strncat(name, ".pass", 48); - writeInputData(name, 0); -} - -/* Save a failing test to the output test directory. */ -void DeepState_SaveFailingTest(void) { - char name[48]; - makeFilename(name, 40); - name[40] = 0; - strncat(name, ".fail", 48); - writeInputData(name, 1); -} - -/* Save a crashing test to the output test directory. */ -void DeepState_SaveCrashingTest(void) { - char name[48]; - makeFilename(name, 40); - name[40] = 0; - strncat(name, ".crash", 48); - writeInputData(name, 1); -} - -/* Return the first test case to run. */ -struct DeepState_TestInfo *DeepState_FirstTest(void) { - return DeepState_FirstTestInfo; -} - -/* Returns `true` if a failure was caught for the current test case. */ -bool DeepState_CatchFail(void) { - return DeepState_CurrentTestRun->result == DeepState_TestRunFail; -} - -/* Returns `true` if the current test case was abandoned. */ -bool DeepState_CatchAbandoned(void) { - return DeepState_CurrentTestRun->result == DeepState_TestRunAbandon; -} - -/* Fuzz test `FLAGS_input_which_test` or first test, if not defined. - Has to be defined here since we redefine rand in the header. */ -int DeepState_Fuzz(void){ - DeepState_LogFormat(DeepState_LogInfo, "Starting fuzzing"); - - if (!HAS_FLAG_min_log_level && !HAS_FLAG_random) { - FLAGS_min_log_level = 2; - } - - if (HAS_FLAG_seed) { - srand(FLAGS_seed); - } else { - unsigned int seed = time(NULL); - DeepState_LogFormat(DeepState_LogWarning, "No seed provided; using %u", seed); - srand(seed); - } - - if (HAS_FLAG_fork) { - if (FLAGS_fork) { - DeepState_LogFormat(DeepState_LogFatal, - "Forking should not be combined with brute force fuzzing."); - } - } else { - FLAGS_fork = 0; - } - - long start = (long)time(NULL); - long current = (long)time(NULL); - unsigned diff = 0; - unsigned int i = 0; - - int num_failed_tests = 0; - int num_passed_tests = 0; - int num_abandoned_tests = 0; - - struct DeepState_TestInfo *test = NULL; - - - for (test = DeepState_FirstTest(); test != NULL; test = test->prev) { - if (HAS_FLAG_input_which_test) { - if (strcmp(FLAGS_input_which_test, test->test_name) == 0) { - break; - } - } else { - DeepState_LogFormat(DeepState_LogWarning, - "No test specified, defaulting to first test defined (%s)", - test->test_name); - break; - } - } - - if (test == NULL) { - DeepState_LogFormat(DeepState_LogInfo, - "Could not find matching test for %s", - FLAGS_input_which_test); - return 0; - } - - unsigned int last_status = 0; - - while (diff < FLAGS_timeout) { - i++; - if ((diff != last_status) && ((diff % 30) == 0) ) { - time_t t = time(NULL); - struct tm tm = *localtime(&t); - DeepState_LogFormat(DeepState_LogInfo, "%d-%02d-%02d %02d:%02d:%02d: %u tests/second: %d failed/%d passed/%d abandoned", - tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, i/diff, - num_failed_tests, num_passed_tests, num_abandoned_tests); - last_status = diff; - } - enum DeepState_TestRunResult result = DeepState_FuzzOneTestCase(test); - if ((result == DeepState_TestRunFail) || (result == DeepState_TestRunCrash)) { - num_failed_tests++; - } else if (result == DeepState_TestRunPass) { - num_passed_tests++; - } else if (result == DeepState_TestRunAbandon) { - num_abandoned_tests++; - } - - current = (long)time(NULL); - diff = current-start; - - if ((FLAGS_random) && (i > 0)) { - break; - } - } - - if (!FLAGS_random) { - DeepState_LogFormat(DeepState_LogInfo, "Done fuzzing! Ran %u tests (%u tests/second) with %d failed/%d passed/%d abandoned tests", - i, i/diff, num_failed_tests, num_passed_tests, num_abandoned_tests); - } - return num_failed_tests; -} - - -/* Run a test case with input initialized by fuzzing. - Has to be defined here since we redefine rand in the header. */ -enum DeepState_TestRunResult DeepState_FuzzOneTestCase(struct DeepState_TestInfo *test) { - DeepState_InputIndex = 0; - DeepState_InputInitialized = 0; - DeepState_SwarmConfigsIndex = 0; - DeepState_InternalFuzzing = 1; - - DeepState_Begin(test); - - enum DeepState_TestRunResult result = DeepState_ForkAndRunTest(test); - - if (result == DeepState_TestRunCrash) { - DeepState_LogFormat(DeepState_LogError, "Crashed: %s", test->test_name); - - if (HAS_FLAG_output_test_dir) { - DeepState_SaveCrashingTest(); - } - - DeepState_Crash(); - } - - if (FLAGS_abort_on_fail && ((result == DeepState_TestRunCrash) || - (result == DeepState_TestRunFail))) { - DeepState_HardCrash(); - } - - if (FLAGS_exit_on_fail && ((result == DeepState_TestRunCrash) || - (result == DeepState_TestRunFail))) { - exit(255); // Terminate the testing - } - - return result; -} - -extern int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { - if (Size > sizeof(DeepState_Input)) { - return 0; // Just ignore any too-big inputs - } - - DeepState_UsingLibFuzzer = 1; - - FLAGS_min_log_level = 3; - - const char* log_control = getenv("DEEPSTATE_LOG"); - if (log_control != NULL) { - FLAGS_min_log_level = atoi(log_control); - } - - const char* loud = getenv("LIBFUZZER_LOUD"); - if (loud != NULL) { - DeepState_LibFuzzerLoud = 1; - } - - struct DeepState_TestInfo *test = NULL; - - DeepState_InitOptions(0, ""); - DeepState_Setup(); - - /* we also want to manually allocate CurrentTestRun */ - void *mem = malloc(sizeof(struct DeepState_TestRunInfo)); - DeepState_CurrentTestRun = (struct DeepState_TestRunInfo *) mem; - - test = DeepState_FirstTest(); - const char* which_test = getenv("LIBFUZZER_WHICH_TEST"); - if (which_test != NULL) { - for (test = DeepState_FirstTest(); test != NULL; test = test->prev) { - if (strncmp(which_test, test->test_name, strnlen(which_test, 1024)) == 0) { - break; - } - } - } - - if (test == NULL) { - DeepState_LogFormat(DeepState_LogFatal, - "Could not find matching test for %s (from LIBFUZZER_WHICH_TEST)", - which_test); - exit(255); - } - - DeepState_InputIndex = 0; - DeepState_SwarmConfigsIndex = 0; - - memcpy((void *) DeepState_Input, (void *) Data, Size); - DeepState_InputInitialized = Size; - - DeepState_Begin(test); - - enum DeepState_TestRunResult result = DeepState_RunTestNoFork(test); - DeepState_CleanUp(); - - const char* abort_check = getenv("LIBFUZZER_ABORT_ON_FAIL"); - if (abort_check != NULL) { - if ((result == DeepState_TestRunFail) || (result == DeepState_TestRunCrash)) { - assert(0); // Terminate the testing more permanently - } - } - - const char* exit_check = getenv("LIBFUZZER_EXIT_ON_FAIL"); - if (exit_check != NULL) { - if ((result == DeepState_TestRunFail) || (result == DeepState_TestRunCrash)) { - exit(255); // Terminate the testing - } - } - - DeepState_Teardown(); - DeepState_CurrentTestRun = NULL; - free(mem); - - return 0; // Non-zero return values are reserved for future use. -} - -extern int FuzzerEntrypoint(const uint8_t *data, size_t size) { - return LLVMFuzzerTestOneInput(data, size); -} - -/* Overwrite libc's abort. */ -void abort(void) { - DeepState_Fail(); -} - -void __assert_fail(const char * assertion, const char * file, - unsigned int line, const char * function) { - DeepState_LogFormat(DeepState_LogFatal, - "%s(%u): Assertion %s failed in function %s", - file, line, assertion, function); - if (FLAGS_abort_on_fail) { - DeepState_HardCrash(); - } - __builtin_unreachable(); -} - -void __stack_chk_fail(void) { - DeepState_Log(DeepState_LogFatal, "Stack smash detected"); - __builtin_unreachable(); -} - -#ifndef LIBFUZZER -#ifndef HEADLESS -#if defined(__unix) -__attribute__((weak)) -#endif -int main(int argc, char *argv[]) { - int ret = 0; - DeepState_Setup(); - DeepState_InitOptions(argc, argv); - ret = DeepState_Run(); - DeepState_Teardown(); - return ret; -} -#endif -#endif - -DEEPSTATE_END_EXTERN_C +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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 "deepstate/RandGen.h" +#include "deepstate/DeepState.h" +#include "deepstate/Option.h" +#include "deepstate/Log.h" +#include "DeepState.h" + +#include +#include +#include +#include +#include + +#ifdef DEEPSTATE_TAKEOVER_RAND +#undef rand +#undef srand +#endif + +DEEPSTATE_BEGIN_EXTERN_C + +/* Basic input and output options, specifies files for read/write before and after test analysis */ +DEFINE_string(input_test_dir, InputOutputGroup, "", "Directory of saved tests to run."); +DEFINE_string(input_test_file, InputOutputGroup, "", "Saved test to run."); +DEFINE_string(input_test_files_dir, InputOutputGroup, "", "Directory of saved test files to run (flat structure)."); +DEFINE_string(output_test_dir, InputOutputGroup, "", "Directory where tests will be saved."); +DEFINE_bool(input_stdin, InputOutputGroup, false, "Run a test from stdin."); + +/* Test execution-related options, configures how an execution run is carried out */ +DEFINE_bool(take_over, ExecutionGroup, false, "Replay test cases in take-over mode."); +DEFINE_bool(abort_on_fail, ExecutionGroup, false, "Abort on file replay failure (useful in file fuzzing)."); +DEFINE_bool(exit_on_fail, ExecutionGroup, false, "Exit with status 255 on test failure."); +DEFINE_bool(verbose_reads, ExecutionGroup, false, "Report on bytes being read during execution of test."); +DEFINE_int(min_log_level, ExecutionGroup, 0, "Minimum level of logging to output (default 0, 0=debug, 1=trace, 2=info, ...)."); +DEFINE_int(timeout, ExecutionGroup, 3600, "Timeout for brute force fuzzing."); +DEFINE_uint(num_workers, ExecutionGroup, 1, "Number of workers to spawn for testing and test generation."); +#if defined(_WIN32) || defined(_MSC_VER) +DEFINE_bool(direct_run, ExecutionGroup, false, "Run test function directly."); +#endif + +/* Fuzzing and symex related options, baked in to perform analysis-related tasks without auxiliary tools */ +DEFINE_bool(fuzz, AnalysisGroup, false, "Perform brute force unguided fuzzing."); +DEFINE_bool(random, AnalysisGroup, false, "Run one test with random inputs."); +DEFINE_bool(fuzz_save_passing, AnalysisGroup, false, "Save passing tests during fuzzing."); +DEFINE_bool(fork, AnalysisGroup, true, "Fork when running a test."); +DEFINE_int(seed, AnalysisGroup, 0, "Seed for brute force fuzzing (uses time if not set)."); + +/* Test selection options to configure what test or tests should be executed during a run */ +DEFINE_string(input_which_test, TestSelectionGroup, "", "Test to use with --input_test_file or --input_test_files_dir."); +DEFINE_string(test_filter, TestSelectionGroup, "", "Run all tests matched with wildcard pattern."); +DEFINE_bool(list_tests, TestSelectionGroup, false, "List all available tests instead of running tests."); +DEFINE_bool(boring_only, TestSelectionGroup, false, "Run Boring concrete tests only."); +DEFINE_bool(run_disabled, TestSelectionGroup, false, "Run Disabled tests alongside other tests."); + +/* Set to 1 by Manticore/Angr/etc. when we're running symbolically. */ +int DeepState_UsingSymExec = 0; + +/* Set to 1 when we're using libFuzzer. */ +int DeepState_UsingLibFuzzer = 0; + +/* To make libFuzzer louder on mac OS. */ +int DeepState_LibFuzzerLoud = 0; + +/* Array of DeepState generated allocations. Impossible for there to + * be more than there are input bytes. Index stores where we are. */ +char* DeepState_GeneratedAllocs[DeepState_InputSize]; +uint32_t DeepState_GeneratedAllocsIndex = 0; + +/* Pointer to the last registers DeepState_TestInfo data structure */ +struct DeepState_TestInfo *DeepState_LastTestInfo = NULL; + +/* Pointer to structure for ordered DeepState_TestInfo */ +struct DeepState_TestInfo *DeepState_FirstTestInfo = NULL; + +/* Pointer to the test being run in this process by Dr. Fuzz. */ +static struct DeepState_TestInfo *DeepState_DrFuzzTest = NULL; + +/* Initialize global input buffer and index / initialized index. */ +volatile uint8_t DeepState_Input[DeepState_InputSize] = {}; +uint32_t DeepState_InputIndex = 0; +uint32_t DeepState_InputInitialized = 0; + +/* Used if we need to generate on-the-fly data while we fuzz */ +uint32_t DeepState_InternalFuzzing = 0; + +/* Swarm related state. */ +uint32_t DeepState_SwarmConfigsIndex = 0; +struct DeepState_SwarmConfig *DeepState_SwarmConfigs[DEEPSTATE_MAX_SWARM_CONFIGS]; + +/* pRNG seed */ +DeepStateRng DeepState_Rng; + +/* Jump buffer for returning to `DeepState_Run`. */ +jmp_buf DeepState_ReturnToRun = {}; + +/* Information about the current test run, if any. */ +extern struct DeepState_TestRunInfo *DeepState_CurrentTestRun = NULL; + +static void DeepState_SetTestPassed(void) { + DeepState_CurrentTestRun->result = DeepState_TestRunPass; +} + +static void DeepState_SetTestFailed(void) { + DeepState_CurrentTestRun->result = DeepState_TestRunFail; +} + +static void DeepState_SetTestAbandoned(const char *reason) { + DeepState_CurrentTestRun->result = DeepState_TestRunAbandon; + DeepState_CurrentTestRun->reason = reason; +} + +void DeepState_InitCurrentTestRun(struct DeepState_TestInfo *test) { + DeepState_CurrentTestRun->test = test; + DeepState_CurrentTestRun->result = DeepState_TestRunPass; + DeepState_CurrentTestRun->reason = NULL; +} + +/* Abandon this test. We've hit some kind of internal problem. */ +DEEPSTATE_NORETURN +void DeepState_Abandon(const char *reason) { + DeepState_Log(DeepState_LogError, reason); + + DeepState_CurrentTestRun->result = DeepState_TestRunAbandon; + DeepState_CurrentTestRun->reason = reason; + + longjmp(DeepState_ReturnToRun, 1); +} + +/* Abandon this test due to failed assumption. Less important to log. */ +DEEPSTATE_NORETURN +void DeepState_Abandon_Due_to_Assumption(const char *reason) { + DeepState_CurrentTestRun->result = DeepState_TestRunAbandon; + DeepState_CurrentTestRun->reason = reason; + + longjmp(DeepState_ReturnToRun, 1); +} + +/* Mark this test as having crashed. */ +void DeepState_Crash(void) { + DeepState_SetTestFailed(); +} + +/* Mark this test as failing. */ +DEEPSTATE_NORETURN +void DeepState_Fail(void) { + DeepState_SetTestFailed(); + + if (FLAGS_take_over) { + // We want to communicate the failure to a parent process, so exit. + exit(DeepState_TestRunFail); + } else { + longjmp(DeepState_ReturnToRun, 1); + } +} + +/* Mark this test as passing. */ +DEEPSTATE_NORETURN +void DeepState_Pass(void) { + longjmp(DeepState_ReturnToRun, 0); +} + +void DeepState_SoftFail(void) { + DeepState_SetTestFailed(); +} + +/* Symbolize the data in the exclusive range `[begin, end)`. */ +void DeepState_SymbolizeData(void *begin, void *end) { + uintptr_t begin_addr = (uintptr_t) begin; + uintptr_t end_addr = (uintptr_t) end; + + if (begin_addr > end_addr) { + DeepState_Abandon("Invalid data bounds for DeepState_SymbolizeData"); + } else if (begin_addr == end_addr) { + return; + } else { + uint8_t *bytes = (uint8_t *) begin; + for (uintptr_t i = 0, max_i = (end_addr - begin_addr); i < max_i; ++i) { + if (DeepState_InputIndex >= DeepState_InputSize) { + DeepState_Abandon("Exceeded set input limit. Set or expand DEEPSTATE_SIZE to write more bytes."); + } + if (FLAGS_verbose_reads) { + printf("Reading byte at %u\n", DeepState_InputIndex); + } + bytes[i] = DEEPSTATE_READBYTE; + } + } +} + +/* Symbolize the data in the exclusive range `[begin, end)` without null + * characters included. Primarily useful for C strings. */ +void DeepState_SymbolizeDataNoNull(void *begin, void *end) { + uintptr_t begin_addr = (uintptr_t) begin; + uintptr_t end_addr = (uintptr_t) end; + + if (begin_addr > end_addr) { + DeepState_Abandon("Invalid data bounds for DeepState_SymbolizeData"); + } else if (begin_addr == end_addr) { + return; + } else { + uint8_t *bytes = (uint8_t *) begin; + for (uintptr_t i = 0, max_i = (end_addr - begin_addr); i < max_i; ++i) { + if (DeepState_InputIndex >= DeepState_InputSize) { + DeepState_Abandon("Exceeded set input limit. Set or expand DEEPSTATE_SIZE to write more bytes."); + } + if (FLAGS_verbose_reads) { + printf("Reading byte at %u\n", DeepState_InputIndex); + } + bytes[i] = DEEPSTATE_READBYTE; + if (bytes[i] == 0) { + bytes[i] = 1; + } + } + } +} + +/* Concretize some data in exclusive the range `[begin, end)`. */ +void *DeepState_ConcretizeData(void *begin, void *end) { + return begin; +} + +/* Assign a symbolic C string of strlen length `len`. str should include + * storage for both `len` characters AND the null terminator. Allowed + * is a set of chars that are allowed (ignored if null). */ +void DeepState_AssignCStr_C(char* str, size_t len, const char* allowed) { + if (SIZE_MAX <= len) { + DeepState_Abandon("Can't create a SIZE_MAX-length string."); + } + if (NULL == str) { + DeepState_Abandon("Attempted to populate null pointer."); + } + if (len) { + if (allowed == 0) { + DeepState_SymbolizeDataNoNull(str, &(str[len])); + } else { + uint32_t allowed_size = strlen(allowed); + for (int i = 0; i < len; i++) { + str[i] = allowed[DeepState_UIntInRange(0, allowed_size-1)]; + } + } + } + str[len] = '\0'; +} + +void DeepState_SwarmAssignCStr_C(const char* file, unsigned line, int stype, + char* str, size_t len, const char* allowed) { + if (SIZE_MAX <= len) { + DeepState_Abandon("Can't create a SIZE_MAX-length string."); + } + if (NULL == str) { + DeepState_Abandon("Attempted to populate null pointer."); + } + char swarm_allowed[256]; + if (allowed == 0) { + /* In swarm mode, if there is no allowed string, create one over all chars. */ + for (int i = 0; i < 255; i++) { + swarm_allowed[i] = i+1; + } + swarm_allowed[255] = 0; + allowed = (const char*)&swarm_allowed; + } + if (len) { + uint32_t allowed_size = strlen(allowed); + struct DeepState_SwarmConfig* sc = DeepState_GetSwarmConfig(allowed_size, file, line, stype); + for (int i = 0; i < len; i++) { + str[i] = allowed[sc->fmap[DeepState_UIntInRange(0U, sc->fcount-1)]]; + } + } + str[len] = '\0'; +} + +/* Return a symbolic C string of strlen `len`. */ +char *DeepState_CStr_C(size_t len, const char* allowed) { + if (SIZE_MAX <= len) { + DeepState_Abandon("Can't create a SIZE_MAX-length string"); + } + char *str = (char *) malloc(sizeof(char) * (len + 1)); + if (NULL == str) { + DeepState_Abandon("Can't allocate memory"); + } + DeepState_GeneratedAllocs[DeepState_GeneratedAllocsIndex++] = str; + if (len) { + if (allowed == 0) { + DeepState_SymbolizeDataNoNull(str, &(str[len])); + } else { + uint32_t allowed_size = strlen(allowed); + for (int i = 0; i < len; i++) { + str[i] = allowed[DeepState_UIntInRange(0, allowed_size-1)]; + } + } + } + str[len] = '\0'; + return str; +} + +char *DeepState_SwarmCStr_C(const char* file, unsigned line, int stype, + size_t len, const char* allowed) { + if (SIZE_MAX <= len) { + DeepState_Abandon("Can't create a SIZE_MAX-length string"); + } + char *str = (char *) malloc(sizeof(char) * (len + 1)); + if (NULL == str) { + DeepState_Abandon("Can't allocate memory"); + } + char swarm_allowed[256]; + if (allowed == 0) { + /* In swarm mode, if there is no allowed string, create one over all chars. */ + for (int i = 0; i < 255; i++) { + swarm_allowed[i] = i+1; + } + swarm_allowed[255] = 0; + allowed = (const char*)&swarm_allowed; + } + DeepState_GeneratedAllocs[DeepState_GeneratedAllocsIndex++] = str; + if (len) { + uint32_t allowed_size = strlen(allowed); + struct DeepState_SwarmConfig* sc = DeepState_GetSwarmConfig(allowed_size, file, line, stype); + for (int i = 0; i < len; i++) { + str[i] = allowed[sc->fmap[DeepState_UIntInRange(0U, sc->fcount-1)]]; + } + } + str[len] = '\0'; + return str; +} + +/* Symbolize a C string; keeps the null terminator where it was. */ +void DeepState_SymbolizeCStr_C(char *begin, const char* allowed) { + if (begin && begin[0]) { + if (allowed == 0) { + DeepState_SymbolizeDataNoNull(begin, begin + strlen(begin)); + } else { + uint32_t allowed_size = strlen(allowed); + uint8_t *bytes = (uint8_t *) begin; + uintptr_t begin_addr = (uintptr_t) begin; + uintptr_t end_addr = (uintptr_t) (begin + strlen(begin)); + for (uintptr_t i = 0, max_i = (end_addr - begin_addr); i < max_i; ++i) { + bytes[i] = allowed[DeepState_UIntInRange(0, allowed_size-1)]; + } + } + } +} + +void DeepState_SwarmSymbolizeCStr_C(const char* file, unsigned line, int stype, + char *begin, const char* allowed) { + if (begin && begin[0]) { + char swarm_allowed[256]; + if (allowed == 0) { + /* In swarm mode, if there is no allowed string, create one over all chars. */ + for (int i = 0; i < 255; i++) { + swarm_allowed[i] = i+1; + } + swarm_allowed[255] = 0; + allowed = (const char*)&swarm_allowed; + } + uint32_t allowed_size = strlen(allowed); + struct DeepState_SwarmConfig* sc = DeepState_GetSwarmConfig(allowed_size, file, line, stype); + uint8_t *bytes = (uint8_t *) begin; + uintptr_t begin_addr = (uintptr_t) begin; + uintptr_t end_addr = (uintptr_t) (begin + strlen(begin)); + for (uintptr_t i = 0, max_i = (end_addr - begin_addr); i < max_i; ++i) { + bytes[i] = allowed[sc->fmap[DeepState_UIntInRange(0U, sc->fcount-1)]]; + } + } +} + +/* Concretize a C string */ +const char *DeepState_ConcretizeCStr(const char *begin) { + return begin; +} + +/* Allocate and return a pointer to `num_bytes` symbolic bytes. */ +void *DeepState_Malloc(size_t num_bytes) { + void *data = malloc(num_bytes); + uintptr_t data_end = ((uintptr_t) data) + num_bytes; + DeepState_SymbolizeData(data, (void *) data_end); + return data; +} + +/* Allocate and return a pointer to `num_bytes` symbolic bytes. */ +void *DeepState_GCMalloc(size_t num_bytes) { + void *data = malloc(num_bytes); + uintptr_t data_end = ((uintptr_t) data) + num_bytes; + DeepState_SymbolizeData(data, (void *) data_end); + DeepState_GeneratedAllocs[DeepState_GeneratedAllocsIndex++] = data; + return data; +} + +/* Portable and architecture-independent memory scrub without dead store elimination. */ +void *DeepState_MemScrub(void *pointer, size_t data_size) { + volatile unsigned char *p = pointer; + while (data_size--) { + *p++ = 0; + } + return pointer; +} + +/* Generate a new swarm configuration. */ +struct DeepState_SwarmConfig *DeepState_NewSwarmConfig(unsigned fcount, const char* file, unsigned line, + enum DeepState_SwarmType stype) { + struct DeepState_SwarmConfig *new_config = malloc(sizeof(struct DeepState_SwarmConfig)); + size_t buf_len = strlen(file) + 1; + new_config->file = malloc(buf_len); + strncpy(new_config->file, file, buf_len); + new_config->line = line; + new_config->orig_fcount = fcount; + new_config->fcount = 0; + if (stype == DeepState_SwarmTypeProb) { + new_config->fmap = malloc(sizeof(unsigned) * fcount * DEEPSTATE_SWARM_MAX_PROB_RATIO); + for (int i = 0; i < fcount; i++) { + unsigned int prob = DeepState_UIntInRange(0U, DEEPSTATE_SWARM_MAX_PROB_RATIO); + for (int j = 0; j < prob; j++) { + new_config->fmap[new_config->fcount++] = i; + } + } + if (new_config->fcount == 0) { + new_config->fmap[new_config->fcount++] = DeepState_UIntInRange(0, fcount-1); + } + } else { + new_config->fmap = malloc(sizeof(unsigned) * fcount); + /* In mix mode, "half" the time just use everything */ + int full_config = (stype == DeepState_SwarmTypeMixed) && DeepState_Bool(); + if ((stype == DeepState_SwarmTypeMixed) && DeepState_UsingSymExec) { + /* We don't want to make additional pointless paths to explore for symex */ + (void) DeepState_Assume(full_config); + } + for (int i = 0; i < fcount; i++) { + if (full_config) { + new_config->fmap[new_config->fcount++] = i; + } else { + int in_swarm = DeepState_Bool(); + if (DeepState_UsingSymExec) { + /* If not in mix mode, just allow everything in each configuration for symex */ + (void) DeepState_Assume(in_swarm); + } + if (in_swarm) { + new_config->fmap[new_config->fcount++] = i; + } + } + } + } + /* We always need to allow at least one option! */ + if (new_config->fcount == 0) { + new_config->fmap[new_config->fcount++] = DeepState_UIntInRange(0, fcount-1); + } + return new_config; +} + +/* Either fetch existing configuration, or generate a new one. */ +struct DeepState_SwarmConfig *DeepState_GetSwarmConfig(unsigned fcount, const char* file, unsigned line, + enum DeepState_SwarmType stype) { + /* In general, there should be few enough OneOfs in a harness that linear search is fine. */ + for (int i = 0; i < DeepState_SwarmConfigsIndex; i++) { + struct DeepState_SwarmConfig* sc = DeepState_SwarmConfigs[i]; + if ((sc->line == line) && (sc->orig_fcount == fcount) && (strncmp(sc->file, file, strlen(file)) == 0)) { + return sc; + } + } + if (DeepState_SwarmConfigsIndex == DEEPSTATE_MAX_SWARM_CONFIGS) { + DeepState_Abandon("Exceeded swarm config limit. Set or expand DEEPSTATE_MAX_SWARM_CONFIGS. This is highly unusual."); + } + DeepState_SwarmConfigs[DeepState_SwarmConfigsIndex] = DeepState_NewSwarmConfig(fcount, file, line, stype); + return DeepState_SwarmConfigs[DeepState_SwarmConfigsIndex++]; +} + +DEEPSTATE_NOINLINE int DeepState_One(void) { + return 1; +} + +DEEPSTATE_NOINLINE int DeepState_Zero(void) { + return 0; +} + +/* Always returns `0`. */ +int DeepState_ZeroSink(int sink) { + (void) sink; + return 0; +} + +/* Returns `1` if `expr` is true, and `0` otherwise. This is kind of an indirect + * way to take a symbolic value, introduce a fork, and on each size, replace its +* value with a concrete value. */ +int DeepState_IsTrue(int expr) { + if (expr == DeepState_Zero()) { + return DeepState_Zero(); + } else { + return DeepState_One(); + } +} + +/* Return a symbolic value of a given type. */ +int DeepState_Bool(void) { + if (DeepState_InputIndex >= DeepState_InputSize) { + DeepState_Abandon("Exceeded set input limit. Set or expand DEEPSTATE_SIZE to write more bytes."); + } + if (FLAGS_verbose_reads) { + printf("Reading byte as boolean at %u\n", DeepState_InputIndex); + } + return DEEPSTATE_READBYTE & 1; +} + + + +#define MAKE_SYMBOL_FUNC(Type, type) \ + type DeepState_ ## Type(void) { \ + if ((DeepState_InputIndex + sizeof(type)) > DeepState_InputSize) { \ + DeepState_Abandon("Exceeded set input limit. Set or expand DEEPSTATE_SIZE to write more bytes."); \ + } \ + type val = 0; \ + if (FLAGS_verbose_reads) { \ + printf("STARTING MULTI-BYTE READ\n"); \ + } \ + _Pragma("unroll") \ + for (size_t i = 0; i < sizeof(type); ++i) { \ + if (FLAGS_verbose_reads) { \ + printf("Reading byte at %u\n", DeepState_InputIndex); \ + } \ + val = (val << 8) | ((type) DEEPSTATE_READBYTE); \ + } \ + if (FLAGS_verbose_reads) { \ + printf("FINISHED MULTI-BYTE READ\n"); \ + } \ + return val; \ + } + + +MAKE_SYMBOL_FUNC(Size, size_t) +MAKE_SYMBOL_FUNC(Long, long) + +float DeepState_Float(void) { + float float_v; + DeepState_SymbolizeData(&float_v, &float_v + 1); + return float_v; +} + +double DeepState_Double(void) { + double double_v; + DeepState_SymbolizeData(&double_v, &double_v + 1); + return double_v; +} + +MAKE_SYMBOL_FUNC(UInt64, uint64_t) +int64_t DeepState_Int64(void) { + return (int64_t) DeepState_UInt64(); +} + +MAKE_SYMBOL_FUNC(UInt, unsigned) +int DeepState_Int(void) { + return (int) DeepState_UInt(); +} + +MAKE_SYMBOL_FUNC(UShort, unsigned short) +short DeepState_Short(void) { + return (short) DeepState_UShort(); +} + +MAKE_SYMBOL_FUNC(UChar, unsigned char) +char DeepState_Char(void) { + return (char) DeepState_UChar(); +} + +#undef MAKE_SYMBOL_FUNC + +float DeepState_FloatInRange(float low, float high) { + if (low > high) { + return DeepState_FloatInRange(high, low); + } + if (low < 0.0) { // Handle negatives differently + if (high > 0.0) { + if (DeepState_Bool()) { + return -(DeepState_FloatInRange(0.0, -low)); + } else { + return DeepState_FloatInRange(0.0, high); + } + } else { + return -(DeepState_FloatInRange(-high, -low)); + } + } + int32_t int_v = DeepState_IntInRange(*(int32_t *)&low, *(int32_t *)&high); + float float_v = *(float*)&int_v; + assume (float_v >= low); + assume (float_v <= high); + return float_v; +} + +double DeepState_DoubleInRange(double low, double high) { + if (low > high) { + return DeepState_DoubleInRange(high, low); + } + if (low < 0.0) { // Handle negatives differently + if (high > 0.0) { + if (DeepState_Bool()) { + return -(DeepState_DoubleInRange(0.0, -low)); + } else { + return DeepState_DoubleInRange(0.0, high); + } + } else { + return -(DeepState_DoubleInRange(-high, -low)); + } + } + int64_t int_v = DeepState_Int64InRange(*(int64_t *)&low, *(int64_t *)&high); + double double_v = *(double*)&int_v; + assume (double_v >= low); + assume (double_v <= high); + return double_v; +} + +int32_t DeepState_RandInt() { + return DeepState_IntInRange(0, RAND_MAX); +} + +/* Returns the minimum satisfiable value for a given symbolic value, given + * the constraints present on that value. */ +uint32_t DeepState_MinUInt(uint32_t v) { + return v; +} + +int32_t DeepState_MinInt(int32_t v) { + return (int32_t) (DeepState_MinUInt(((uint32_t) v) + 0x80000000U) - + 0x80000000U); +} + +/* Returns the maximum satisfiable value for a given symbolic value, given + * the constraints present on that value. */ +uint32_t DeepState_MaxUInt(uint32_t v) { + return v; +} + +int32_t DeepState_MaxInt(int32_t v) { + return (int32_t) (DeepState_MaxUInt(((uint32_t) v) + 0x80000000U) - + 0x80000000U); +} + +/* Function to clean up generated strings, and any other DeepState-managed data. */ +extern void DeepState_CleanUp() { + for (int i = 0; i < DeepState_GeneratedAllocsIndex; i++) { + free(DeepState_GeneratedAllocs[i]); + } + DeepState_GeneratedAllocsIndex = 0; + + for (int i = 0; i < DeepState_SwarmConfigsIndex; i++) { + free(DeepState_SwarmConfigs[i]->file); + free(DeepState_SwarmConfigs[i]->fmap); + free(DeepState_SwarmConfigs[i]); + } + DeepState_SwarmConfigsIndex = 0; +} + +void _DeepState_Assume(int expr, const char *expr_str, const char *file, + unsigned line) { + if (!expr) { + DeepState_LogFormat(DeepState_LogTrace, + "%s(%u): Assumption %s failed", + file, line, expr_str); + DeepState_Abandon_Due_to_Assumption("Assumption failed"); + } +} + +int DeepState_IsSymbolicUInt(uint32_t x) { + (void) x; + return 0; +} + +/* Defined in Stream.c */ +extern void _DeepState_StreamInt(enum DeepState_LogLevel level, + const char *format, + const char *unpack, uint64_t *val); + +extern void _DeepState_StreamFloat(enum DeepState_LogLevel level, + const char *format, + const char *unpack, double *val); + +extern void _DeepState_StreamString(enum DeepState_LogLevel level, + const char *format, + const char *str); + +/* A DeepState-specific symbol that is needed for hooking. */ +struct DeepState_IndexEntry { + const char * const name; + void * const address; +}; + +/* An index of symbols that the symbolic executors will hook or + * need access to. */ +const struct DeepState_IndexEntry DeepState_API[] = { + + /* Control-flow during the test. */ + {"Pass", (void *) DeepState_Pass}, + {"Crash", (void *) DeepState_Crash}, + {"Fail", (void *) DeepState_Fail}, + {"SoftFail", (void *) DeepState_SoftFail}, + {"Abandon", (void *) DeepState_Abandon}, + + /* Locating the tests. */ + {"LastTestInfo", (void *) &DeepState_LastTestInfo}, + + /* Source of symbolic bytes. */ + {"InputBegin", (void *) &(DeepState_Input[0])}, + {"InputEnd", (void *) &(DeepState_Input[DeepState_InputSize])}, + {"InputIndex", (void *) &DeepState_InputIndex}, + + /* Solver APIs. */ + {"Assume", (void *) _DeepState_Assume}, + {"IsSymbolicUInt", (void *) DeepState_IsSymbolicUInt}, + {"ConcretizeData", (void *) DeepState_ConcretizeData}, + {"ConcretizeCStr", (void *) DeepState_ConcretizeCStr}, + {"MinUInt", (void *) DeepState_MinUInt}, + {"MaxUInt", (void *) DeepState_MaxUInt}, + + /* Logging API. */ + {"Log", (void *) DeepState_Log}, + + /* Streaming API for deferred logging. */ + {"ClearStream", (void *) DeepState_ClearStream}, + {"LogStream", (void *) DeepState_LogStream}, + {"StreamInt", (void *) _DeepState_StreamInt}, + {"StreamFloat", (void *) _DeepState_StreamFloat}, + {"StreamString", (void *) _DeepState_StreamString}, + + {"UsingLibFuzzer", (void *) &DeepState_UsingLibFuzzer}, + {"UsingSymExec", (void *) &DeepState_UsingSymExec}, + + {NULL, NULL}, +}; + +/* Set up DeepState. */ +DEEPSTATE_NOINLINE +void DeepState_Setup(void) { + static int was_setup = 0; + if (!was_setup) { + DeepState_AllocCurrentTestRun(); + was_setup = 1; + } + + /* Sort the test cases by line number. */ + struct DeepState_TestInfo *current = DeepState_LastTestInfo; + if (current == NULL) { + DeepState_LogFormat(DeepState_LogError, + "No tests to run have been defined! Did you include the harness to compile?"); + exit(1); + } + struct DeepState_TestInfo *min_node = current->prev; + current->prev = NULL; + + while (min_node != NULL) { + struct DeepState_TestInfo *temp = min_node; + + min_node = min_node->prev; + temp->prev = current; + current = temp; + } + DeepState_FirstTestInfo = current; +} + +/* Tear down DeepState. */ +void DeepState_Teardown(void) { + +} + +/* Notify that we're about to begin a test. */ +void DeepState_Begin(struct DeepState_TestInfo *test) { + DeepState_InitCurrentTestRun(test); + DeepState_LogFormat(DeepState_LogTrace, "Running: %s from %s(%u)", + test->test_name, test->file_name, test->line_number); +} + +void DeepState_Warn_srand(unsigned int seed) { + DeepState_LogFormat(DeepState_LogWarning, + "srand under DeepState has no effect: rand is re-defined as DeepState_Int"); +} + +/* Right now "fake" a hexdigest by just using random bytes. Not ideal. */ +void makeFilename(char *name, size_t size) { + const char *entities = "0123456789abcdef"; + for (int i = 0; i < size; i++) { + name[i] = entities[deepstate_rng_next(&DeepState_Rng) % 16]; + } +} + +void writeInputData(char* name, int important) { + size_t path_len = 2 + sizeof(char) * (strlen(FLAGS_output_test_dir) + strlen(name)); + char *path = (char *) malloc(path_len); + snprintf(path, path_len, "%s/%s", FLAGS_output_test_dir, name); + FILE *fp = fopen(path, "wb"); + if (fp == NULL) { + DeepState_LogFormat(DeepState_LogError, "Failed to create file `%s`", path); + free(path); + return; + } + size_t written = fwrite((void *)DeepState_Input, 1, DeepState_InputIndex, fp); + if (written != DeepState_InputIndex) { + DeepState_LogFormat(DeepState_LogError, "Failed to write to file `%s`", path); + } else { + if (important) { + DeepState_LogFormat(DeepState_LogInfo, "Saved test case in file `%s`", path); + } else { + DeepState_LogFormat(DeepState_LogTrace, "Saved test case in file `%s`", path); + } + } + free(path); + fclose(fp); +} + +/* Save a passing test to the output test directory. */ +void DeepState_SavePassingTest(void) { + char name[48]; + makeFilename(name, 40); + name[40] = 0; + strncat(name, ".pass", 48); + writeInputData(name, 0); +} + +/* Save a failing test to the output test directory. */ +void DeepState_SaveFailingTest(void) { + char name[48]; + makeFilename(name, 40); + name[40] = 0; + strncat(name, ".fail", 48); + writeInputData(name, 1); +} + +/* Save a crashing test to the output test directory. */ +void DeepState_SaveCrashingTest(void) { + char name[48]; + makeFilename(name, 40); + name[40] = 0; + strncat(name, ".crash", 48); + writeInputData(name, 1); +} + +/* Return the first test case to run. */ +struct DeepState_TestInfo *DeepState_FirstTest(void) { + return DeepState_FirstTestInfo; +} + +/* Returns `true` if a failure was caught for the current test case. */ +bool DeepState_CatchFail(void) { + return DeepState_CurrentTestRun->result == DeepState_TestRunFail; +} + +/* Returns `true` if the current test case was abandoned. */ +bool DeepState_CatchAbandoned(void) { + return DeepState_CurrentTestRun->result == DeepState_TestRunAbandon; +} + +/* Fuzz test `FLAGS_input_which_test` or first test, if not defined. + Has to be defined here since we redefine rand in the header. */ +int DeepState_Fuzz(void){ + DeepState_LogFormat(DeepState_LogInfo, "Starting fuzzing"); + + if (!HAS_FLAG_min_log_level && !HAS_FLAG_random) { + FLAGS_min_log_level = 2; + } + + if (HAS_FLAG_seed) { + deepstate_rng_seed(&DeepState_Rng, (uint32_t)FLAGS_seed); + } else { + uint32_t seed = (uint32_t)time(NULL); + DeepState_LogFormat(DeepState_LogWarning, "No seed provided; using %u", seed); + deepstate_rng_seed(&DeepState_Rng, seed); + } + + if (HAS_FLAG_fork) { + if (FLAGS_fork) { + DeepState_LogFormat(DeepState_LogFatal, + "Forking should not be combined with brute force fuzzing."); + } + } else { + FLAGS_fork = 0; + } + + long start = (long)time(NULL); + long current = (long)time(NULL); + unsigned diff = 0; + unsigned int i = 0; + + int num_failed_tests = 0; + int num_passed_tests = 0; + int num_abandoned_tests = 0; + + struct DeepState_TestInfo *test = NULL; + + + for (test = DeepState_FirstTest(); test != NULL; test = test->prev) { + if (HAS_FLAG_input_which_test) { + if (strcmp(FLAGS_input_which_test, test->test_name) == 0) { + break; + } + } else { + DeepState_LogFormat(DeepState_LogWarning, + "No test specified, defaulting to first test defined (%s)", + test->test_name); + break; + } + } + + if (test == NULL) { + DeepState_LogFormat(DeepState_LogInfo, + "Could not find matching test for %s", + FLAGS_input_which_test); + return 0; + } + + unsigned int last_status = 0; + + while (diff < FLAGS_timeout) { + i++; + if ((diff != last_status) && ((diff % 30) == 0) ) { + time_t t = time(NULL); + struct tm tm = *localtime(&t); + DeepState_LogFormat(DeepState_LogInfo, "%d-%02d-%02d %02d:%02d:%02d: %u tests/second: %d failed/%d passed/%d abandoned", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, i/diff, + num_failed_tests, num_passed_tests, num_abandoned_tests); + last_status = diff; + } + enum DeepState_TestRunResult result = DeepState_FuzzOneTestCase(test); + if ((result == DeepState_TestRunFail) || (result == DeepState_TestRunCrash)) { + num_failed_tests++; + } else if (result == DeepState_TestRunPass) { + num_passed_tests++; + } else if (result == DeepState_TestRunAbandon) { + num_abandoned_tests++; + } + + current = (long)time(NULL); + diff = current-start; + + if ((FLAGS_random) && (i > 0)) { + break; + } + } + + if (!FLAGS_random) { + DeepState_LogFormat(DeepState_LogInfo, "Done fuzzing! Ran %u tests (%u tests/second) with %d failed/%d passed/%d abandoned tests", + i, i/diff, num_failed_tests, num_passed_tests, num_abandoned_tests); + } + return num_failed_tests; +} + + +/* Run a test case with input initialized by fuzzing. + Has to be defined here since we redefine rand in the header. */ +enum DeepState_TestRunResult DeepState_FuzzOneTestCase(struct DeepState_TestInfo *test) { + DeepState_InputIndex = 0; + DeepState_InputInitialized = 0; + DeepState_SwarmConfigsIndex = 0; + DeepState_InternalFuzzing = 1; + + DeepState_Begin(test); + + enum DeepState_TestRunResult result = DeepState_ForkAndRunTest(test); + + if (result == DeepState_TestRunCrash) { + DeepState_LogFormat(DeepState_LogError, "Crashed: %s", test->test_name); + + if (HAS_FLAG_output_test_dir) { + DeepState_SaveCrashingTest(); + } + + DeepState_Crash(); + } + + if (FLAGS_abort_on_fail && ((result == DeepState_TestRunCrash) || + (result == DeepState_TestRunFail))) { + DeepState_HardCrash(); + } + + if (FLAGS_exit_on_fail && ((result == DeepState_TestRunCrash) || + (result == DeepState_TestRunFail))) { + exit(255); // Terminate the testing + } + + return result; +} + +extern int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + if (Size > sizeof(DeepState_Input)) { + return 0; // Just ignore any too-big inputs + } + + DeepState_UsingLibFuzzer = 1; + + FLAGS_min_log_level = 3; + + const char* log_control = getenv("DEEPSTATE_LOG"); + if (log_control != NULL) { + FLAGS_min_log_level = atoi(log_control); + } + + const char* loud = getenv("LIBFUZZER_LOUD"); + if (loud != NULL) { + DeepState_LibFuzzerLoud = 1; + } + + struct DeepState_TestInfo *test = NULL; + + DeepState_InitOptions(0, ""); + DeepState_Setup(); + + /* we also want to manually allocate CurrentTestRun */ + void *mem = malloc(sizeof(struct DeepState_TestRunInfo)); + DeepState_CurrentTestRun = (struct DeepState_TestRunInfo *) mem; + + test = DeepState_FirstTest(); + const char* which_test = getenv("LIBFUZZER_WHICH_TEST"); + if (which_test != NULL) { + for (test = DeepState_FirstTest(); test != NULL; test = test->prev) { + if (strncmp(which_test, test->test_name, strnlen(which_test, 1024)) == 0) { + break; + } + } + } + + if (test == NULL) { + DeepState_LogFormat(DeepState_LogFatal, + "Could not find matching test for %s (from LIBFUZZER_WHICH_TEST)", + which_test); + exit(255); + } + + DeepState_InputIndex = 0; + DeepState_SwarmConfigsIndex = 0; + + memcpy((void *) DeepState_Input, (void *) Data, Size); + DeepState_InputInitialized = Size; + + DeepState_Begin(test); + + enum DeepState_TestRunResult result = DeepState_RunTestNoFork(test); + DeepState_CleanUp(); + + const char* abort_check = getenv("LIBFUZZER_ABORT_ON_FAIL"); + if (abort_check != NULL) { + if ((result == DeepState_TestRunFail) || (result == DeepState_TestRunCrash)) { + assert(0); // Terminate the testing more permanently + } + } + + const char* exit_check = getenv("LIBFUZZER_EXIT_ON_FAIL"); + if (exit_check != NULL) { + if ((result == DeepState_TestRunFail) || (result == DeepState_TestRunCrash)) { + exit(255); // Terminate the testing + } + } + + DeepState_Teardown(); + DeepState_CurrentTestRun = NULL; + free(mem); + + return 0; // Non-zero return values are reserved for future use. +} + +extern int FuzzerEntrypoint(const uint8_t *data, size_t size) { + return LLVMFuzzerTestOneInput(data, size); +} + +/* Overwrite libc's abort. */ +void abort(void) { + DeepState_Fail(); +} + +void __assert_fail(const char * assertion, const char * file, + unsigned int line, const char * function) { + DeepState_LogFormat(DeepState_LogFatal, + "%s(%u): Assertion %s failed in function %s", + file, line, assertion, function); + if (FLAGS_abort_on_fail) { + DeepState_HardCrash(); + } + __builtin_unreachable(); +} + +void __stack_chk_fail(void) { + DeepState_Log(DeepState_LogFatal, "Stack smash detected"); + __builtin_unreachable(); +} + +#ifndef LIBFUZZER +#ifndef HEADLESS +#if defined(__unix) +__attribute__((weak)) +#endif +int main(int argc, char *argv[]) { + int ret = 0; + DeepState_Setup(); + DeepState_InitOptions(argc, argv); + ret = DeepState_Run(); + DeepState_Teardown(); + return ret; +} +#endif +#endif + +DEEPSTATE_END_EXTERN_C diff --git a/src/lib/DeepState.h b/src/lib/DeepState.h index a24f1e15..8ba3122c 100644 --- a/src/lib/DeepState.h +++ b/src/lib/DeepState.h @@ -1,38 +1,38 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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. - */ - -#ifndef SRC_INCLUDE_DEEPSTATE_DEEPSTATE_PRIVATE_H_ -#define SRC_INCLUDE_DEEPSTATE_DEEPSTATE_PRIVATE_H_ - -#include - -DEEPSTATE_BEGIN_EXTERN_C - - -/* Function that allocates the shared memory for the current test run. Platform - * specific function. */ -extern void DeepState_AllocCurrentTestRun(void); - -/* Run saved take over cases. Platform specific function. */ -extern void DeepState_RunSavedTakeOverCases(jmp_buf env, struct DeepState_TestInfo *test); - -/* Run take over. Platform specific function. */ -extern int DeepState_TakeOver(void); - - -DEEPSTATE_END_EXTERN_C - +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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. + */ + +#ifndef SRC_INCLUDE_DEEPSTATE_DEEPSTATE_PRIVATE_H_ +#define SRC_INCLUDE_DEEPSTATE_DEEPSTATE_PRIVATE_H_ + +#include + +DEEPSTATE_BEGIN_EXTERN_C + + +/* Function that allocates the shared memory for the current test run. Platform + * specific function. */ +extern void DeepState_AllocCurrentTestRun(void); + +/* Run saved take over cases. Platform specific function. */ +extern void DeepState_RunSavedTakeOverCases(jmp_buf env, struct DeepState_TestInfo *test); + +/* Run take over. Platform specific function. */ +extern int DeepState_TakeOver(void); + + +DEEPSTATE_END_EXTERN_C + #endif /* SRC_INCLUDE_DEEPSTATE_DEEPSTATE_PRIVATE_H_ */ \ No newline at end of file diff --git a/src/lib/DeepState_UNIX.c b/src/lib/DeepState_UNIX.c index ef412c11..982f1d99 100644 --- a/src/lib/DeepState_UNIX.c +++ b/src/lib/DeepState_UNIX.c @@ -1,267 +1,267 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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 "deepstate/Platform.h" -#include "deepstate/Option.h" -#include "deepstate/Log.h" -#include "DeepState.h" - -DEEPSTATE_BEGIN_EXTERN_C - -void DeepState_AllocCurrentTestRun(void) { - int mem_prot = PROT_READ | PROT_WRITE; - int mem_vis = MAP_ANONYMOUS | MAP_SHARED; - void *shared_mem = mmap(NULL, sizeof(struct DeepState_TestRunInfo), mem_prot, - mem_vis, 0, 0); - - if (shared_mem == MAP_FAILED) { - DeepState_Log(DeepState_LogError, "Unable to map shared memory"); - exit(1); - } - - DeepState_CurrentTestRun = (struct DeepState_TestRunInfo *) shared_mem; -} - -/* Return a string path to an input file or directory without parsing it to a type. This is - * useful method in the case where a tested function only takes a path input in order - * to generate some specialized structured type. Note: the returned path must be - * deallocated at the end by the caller. */ -char* DeepState_InputPath(const char* testcase_path) { - - struct stat statbuf; - char *abspath; - - /* Use specified path if no --input_test* flag specified. Override if --input_* args specified. */ - if (testcase_path) { - if (!HAS_FLAG_input_test_file && !HAS_FLAG_input_test_files_dir) { - abspath = realpath(testcase_path, NULL); - } - } - - /* Prioritize using CLI-specified input paths, for the sake of fuzzing */ - if (HAS_FLAG_input_test_file) { - abspath = realpath(FLAGS_input_test_file, NULL); - } else if (HAS_FLAG_input_test_files_dir) { - abspath = realpath(FLAGS_input_test_files_dir, NULL); - } else { - DeepState_Abandon("No usable path specified for DeepState_InputPath."); - } - - if (stat(abspath, &statbuf) != 0) { - DeepState_Abandon("Specified input path does not exist."); - } - - if (HAS_FLAG_input_test_files_dir) { - if (!S_ISDIR(statbuf.st_mode)) { - DeepState_Abandon("Specified input directory is not a directory."); - } - } - - DeepState_LogFormat(DeepState_LogInfo, "Using `%s` as input path.", abspath); - return abspath; -} - - -void DeepState_RunSavedTakeOverCases(jmp_buf env, - struct DeepState_TestInfo *test) { - int num_failed_tests = 0; - const char *test_case_dir = FLAGS_input_test_dir; - - DIR *dir_fd = opendir(test_case_dir); - if (dir_fd == NULL) { - DeepState_LogFormat(DeepState_LogInfo, - "Skipping test `%s`, no saved test cases", - test->test_name); - return; - } - - struct dirent *dp; - - /* Read generated test cases and run a test for each file found. */ - while ((dp = readdir(dir_fd)) != NULL) { - if (DeepState_IsTestCaseFile(dp->d_name)) { - DeepState_InitCurrentTestRun(test); - - pid_t case_pid = fork(); - if (!case_pid) { - DeepState_Begin(test); - - size_t path_len = 2 + sizeof(char) * (strlen(test_case_dir) + - strlen(dp->d_name)); - char *path = (char *) malloc(path_len); - if (path == NULL) { - DeepState_Abandon("Error allocating memory"); - } - snprintf(path, path_len, "%s/%s", test_case_dir, dp->d_name); - DeepState_InitInputFromFile(path); - free(path); - - longjmp(env, 1); - } - - int wstatus; - waitpid(case_pid, &wstatus, 0); - - /* If we exited normally, the status code tells us if the test passed. */ - if (WIFEXITED(wstatus)) { - switch (DeepState_CurrentTestRun->result) { - case DeepState_TestRunPass: - DeepState_LogFormat(DeepState_LogTrace, - "Passed: TakeOver test with data from `%s`", - dp->d_name); - break; - case DeepState_TestRunFail: - DeepState_LogFormat(DeepState_LogError, - "Failed: TakeOver test with data from `%s`", - dp->d_name); - break; - case DeepState_TestRunCrash: - DeepState_LogFormat(DeepState_LogError, - "Crashed: TakeOver test with data from `%s`", - dp->d_name); - break; - case DeepState_TestRunAbandon: - DeepState_LogFormat(DeepState_LogError, - "Abandoned: TakeOver test with data from `%s`", - dp->d_name); - break; - default: /* Should never happen */ - DeepState_LogFormat(DeepState_LogError, - "Error: Invalid test run result %d from `%s`", - DeepState_CurrentTestRun->result, dp->d_name); - } - } else { - /* If here, we exited abnormally but didn't catch it in the signal - * handler, and thus the test failed due to a crash. */ - DeepState_LogFormat(DeepState_LogError, - "Crashed: TakeOver test with data from `%s`", - dp->d_name); - } - } - } - closedir(dir_fd); -} - -int DeepState_TakeOver(void) { - struct DeepState_TestInfo test = { - .prev = NULL, - .test_func = NULL, - .test_name = "__takeover_test", - .file_name = "__takeover_file", - .line_number = 0, - }; - - DeepState_AllocCurrentTestRun(); - - jmp_buf env; - if (!setjmp(env)) { - DeepState_RunSavedTakeOverCases(env, &test); - exit(0); - } - - return 0; -} - - -/* Fork and run `test`. */ -extern enum DeepState_TestRunResult -DeepState_ForkAndRunTest(struct DeepState_TestInfo *test) { - int wstatus = 0; - pid_t test_pid; - - if (FLAGS_fork) { - test_pid = fork(); - if (!test_pid) { - DeepState_RunTest(test); - /* No need to clean up in a fork; exit() is the ultimate garbage collector */ - } - } - if (FLAGS_fork) { - waitpid(test_pid, &wstatus, 0); - } else { - wstatus = DeepState_RunTestNoFork(test); - DeepState_CleanUp(); - } - - /* If we exited normally, the status code tells us if the test passed. */ - if (!FLAGS_fork) { - return (enum DeepState_TestRunResult) wstatus; - } else if (WIFEXITED(wstatus)) { - uint8_t status = WEXITSTATUS(wstatus); - return (enum DeepState_TestRunResult) status; - } - - /* If here, we exited abnormally but didn't catch it in the signal - * handler, and thus the test failed due to a crash. */ - return DeepState_TestRunCrash; -} - -/* Checks if the given path corresponds to a regular file. */ -bool DeepState_IsRegularFile(char *path){ - struct stat path_stat; - - stat(path, &path_stat); - return S_ISREG(path_stat.st_mode); -} - - -/* Resets the global `DeepState_Input` buffer, then fills it with the - * data found in the file `path`. */ -void DeepState_InitInputFromFile(const char *path) { - struct stat stat_buf; - - FILE *fp = fopen(path, "r"); - if (fp == NULL) { - /* TODO(joe): Add error log with more info. */ - DeepState_Abandon("Unable to open file"); - } - - int fd = fileno(fp); - if (fd < 0) { - DeepState_Abandon("Tried to get file descriptor for invalid stream"); - } - if (fstat(fd, &stat_buf) < 0) { - DeepState_Abandon("Unable to access input file"); - }; - - size_t to_read = stat_buf.st_size; - - if (stat_buf.st_size > sizeof(DeepState_Input)) { - DeepState_LogFormat(DeepState_LogWarning, "File too large, truncating to max input size"); - to_read = DeepState_InputSize; - } - - /* Reset the index. */ - DeepState_InputIndex = 0; - DeepState_SwarmConfigsIndex = 0; - - size_t count = fread((void *) DeepState_Input, 1, to_read, fp); - fclose(fp); - - if (count != to_read) { - /* TODO(joe): Add error log with more info. */ - DeepState_Abandon("Error reading file"); - } - - DeepState_InputInitialized = count; - - DeepState_LogFormat(DeepState_LogTrace, - "Initialized test input buffer with %zu bytes of data from `%s`", - count, path); -} - - +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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 "deepstate/Platform.h" +#include "deepstate/Option.h" +#include "deepstate/Log.h" +#include "DeepState.h" + +DEEPSTATE_BEGIN_EXTERN_C + +void DeepState_AllocCurrentTestRun(void) { + int mem_prot = PROT_READ | PROT_WRITE; + int mem_vis = MAP_ANONYMOUS | MAP_SHARED; + void *shared_mem = mmap(NULL, sizeof(struct DeepState_TestRunInfo), mem_prot, + mem_vis, 0, 0); + + if (shared_mem == MAP_FAILED) { + DeepState_Log(DeepState_LogError, "Unable to map shared memory"); + exit(1); + } + + DeepState_CurrentTestRun = (struct DeepState_TestRunInfo *) shared_mem; +} + +/* Return a string path to an input file or directory without parsing it to a type. This is + * useful method in the case where a tested function only takes a path input in order + * to generate some specialized structured type. Note: the returned path must be + * deallocated at the end by the caller. */ +char* DeepState_InputPath(const char* testcase_path) { + + struct stat statbuf; + char *abspath; + + /* Use specified path if no --input_test* flag specified. Override if --input_* args specified. */ + if (testcase_path) { + if (!HAS_FLAG_input_test_file && !HAS_FLAG_input_test_files_dir) { + abspath = realpath(testcase_path, NULL); + } + } + + /* Prioritize using CLI-specified input paths, for the sake of fuzzing */ + if (HAS_FLAG_input_test_file) { + abspath = realpath(FLAGS_input_test_file, NULL); + } else if (HAS_FLAG_input_test_files_dir) { + abspath = realpath(FLAGS_input_test_files_dir, NULL); + } else { + DeepState_Abandon("No usable path specified for DeepState_InputPath."); + } + + if (stat(abspath, &statbuf) != 0) { + DeepState_Abandon("Specified input path does not exist."); + } + + if (HAS_FLAG_input_test_files_dir) { + if (!S_ISDIR(statbuf.st_mode)) { + DeepState_Abandon("Specified input directory is not a directory."); + } + } + + DeepState_LogFormat(DeepState_LogInfo, "Using `%s` as input path.", abspath); + return abspath; +} + + +void DeepState_RunSavedTakeOverCases(jmp_buf env, + struct DeepState_TestInfo *test) { + int num_failed_tests = 0; + const char *test_case_dir = FLAGS_input_test_dir; + + DIR *dir_fd = opendir(test_case_dir); + if (dir_fd == NULL) { + DeepState_LogFormat(DeepState_LogInfo, + "Skipping test `%s`, no saved test cases", + test->test_name); + return; + } + + struct dirent *dp; + + /* Read generated test cases and run a test for each file found. */ + while ((dp = readdir(dir_fd)) != NULL) { + if (DeepState_IsTestCaseFile(dp->d_name)) { + DeepState_InitCurrentTestRun(test); + + pid_t case_pid = fork(); + if (!case_pid) { + DeepState_Begin(test); + + size_t path_len = 2 + sizeof(char) * (strlen(test_case_dir) + + strlen(dp->d_name)); + char *path = (char *) malloc(path_len); + if (path == NULL) { + DeepState_Abandon("Error allocating memory"); + } + snprintf(path, path_len, "%s/%s", test_case_dir, dp->d_name); + DeepState_InitInputFromFile(path); + free(path); + + longjmp(env, 1); + } + + int wstatus; + waitpid(case_pid, &wstatus, 0); + + /* If we exited normally, the status code tells us if the test passed. */ + if (WIFEXITED(wstatus)) { + switch (DeepState_CurrentTestRun->result) { + case DeepState_TestRunPass: + DeepState_LogFormat(DeepState_LogTrace, + "Passed: TakeOver test with data from `%s`", + dp->d_name); + break; + case DeepState_TestRunFail: + DeepState_LogFormat(DeepState_LogError, + "Failed: TakeOver test with data from `%s`", + dp->d_name); + break; + case DeepState_TestRunCrash: + DeepState_LogFormat(DeepState_LogError, + "Crashed: TakeOver test with data from `%s`", + dp->d_name); + break; + case DeepState_TestRunAbandon: + DeepState_LogFormat(DeepState_LogError, + "Abandoned: TakeOver test with data from `%s`", + dp->d_name); + break; + default: /* Should never happen */ + DeepState_LogFormat(DeepState_LogError, + "Error: Invalid test run result %d from `%s`", + DeepState_CurrentTestRun->result, dp->d_name); + } + } else { + /* If here, we exited abnormally but didn't catch it in the signal + * handler, and thus the test failed due to a crash. */ + DeepState_LogFormat(DeepState_LogError, + "Crashed: TakeOver test with data from `%s`", + dp->d_name); + } + } + } + closedir(dir_fd); +} + +int DeepState_TakeOver(void) { + struct DeepState_TestInfo test = { + .prev = NULL, + .test_func = NULL, + .test_name = "__takeover_test", + .file_name = "__takeover_file", + .line_number = 0, + }; + + DeepState_AllocCurrentTestRun(); + + jmp_buf env; + if (!setjmp(env)) { + DeepState_RunSavedTakeOverCases(env, &test); + exit(0); + } + + return 0; +} + + +/* Fork and run `test`. */ +extern enum DeepState_TestRunResult +DeepState_ForkAndRunTest(struct DeepState_TestInfo *test) { + int wstatus = 0; + pid_t test_pid; + + if (FLAGS_fork) { + test_pid = fork(); + if (!test_pid) { + DeepState_RunTest(test); + /* No need to clean up in a fork; exit() is the ultimate garbage collector */ + } + } + if (FLAGS_fork) { + waitpid(test_pid, &wstatus, 0); + } else { + wstatus = DeepState_RunTestNoFork(test); + DeepState_CleanUp(); + } + + /* If we exited normally, the status code tells us if the test passed. */ + if (!FLAGS_fork) { + return (enum DeepState_TestRunResult) wstatus; + } else if (WIFEXITED(wstatus)) { + uint8_t status = WEXITSTATUS(wstatus); + return (enum DeepState_TestRunResult) status; + } + + /* If here, we exited abnormally but didn't catch it in the signal + * handler, and thus the test failed due to a crash. */ + return DeepState_TestRunCrash; +} + +/* Checks if the given path corresponds to a regular file. */ +bool DeepState_IsRegularFile(char *path){ + struct stat path_stat; + + stat(path, &path_stat); + return S_ISREG(path_stat.st_mode); +} + + +/* Resets the global `DeepState_Input` buffer, then fills it with the + * data found in the file `path`. */ +void DeepState_InitInputFromFile(const char *path) { + struct stat stat_buf; + + FILE *fp = fopen(path, "r"); + if (fp == NULL) { + /* TODO(joe): Add error log with more info. */ + DeepState_Abandon("Unable to open file"); + } + + int fd = fileno(fp); + if (fd < 0) { + DeepState_Abandon("Tried to get file descriptor for invalid stream"); + } + if (fstat(fd, &stat_buf) < 0) { + DeepState_Abandon("Unable to access input file"); + }; + + size_t to_read = stat_buf.st_size; + + if (stat_buf.st_size > sizeof(DeepState_Input)) { + DeepState_LogFormat(DeepState_LogWarning, "File too large, truncating to max input size"); + to_read = DeepState_InputSize; + } + + /* Reset the index. */ + DeepState_InputIndex = 0; + DeepState_SwarmConfigsIndex = 0; + + size_t count = fread((void *) DeepState_Input, 1, to_read, fp); + fclose(fp); + + if (count != to_read) { + /* TODO(joe): Add error log with more info. */ + DeepState_Abandon("Error reading file"); + } + + DeepState_InputInitialized = count; + + DeepState_LogFormat(DeepState_LogTrace, + "Initialized test input buffer with %zu bytes of data from `%s`", + count, path); +} + + DEEPSTATE_END_EXTERN_C \ No newline at end of file diff --git a/src/lib/DeepState_Win32.c b/src/lib/DeepState_Win32.c index 5c6b84f8..e2caced0 100644 --- a/src/lib/DeepState_Win32.c +++ b/src/lib/DeepState_Win32.c @@ -1,255 +1,255 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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 - -#include "deepstate/Platform.h" -#include "deepstate/Option.h" -#include "deepstate/Log.h" -#include "DeepState.h" - -DEEPSTATE_BEGIN_EXTERN_C - -void DeepState_AllocCurrentTestRun(void) { - HANDLE shared_mem_handle = INVALID_HANDLE_VALUE; - - shared_mem_handle = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, - 0, sizeof(struct DeepState_TestRunInfo), "DeepState_CurrentTestRun"); - if (!shared_mem_handle){ - DeepState_LogFormat(DeepState_LogError, "Unable to map shared memory (%d)", GetLastError()); - exit(1); - } - - struct DeepState_TestRunInfo *shared_mem = (struct DeepState_TestRunInfo*) MapViewOfFile(shared_mem_handle, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(struct DeepState_TestRunInfo)); - if (!shared_mem){ - DeepState_LogFormat(DeepState_LogError, "Unable to map shared memory (%d)", GetLastError()); - exit(1); - } - - DeepState_CurrentTestRun = (struct DeepState_TestRunInfo *) shared_mem; -} - - -/* Return a string path to an input file or directory without parsing it to a type. This is - * useful method in the case where a tested function only takes a path input in order - * to generate some specialized structured type. Note: the returned path must be - * deallocated at the end by the caller. */ -char* DeepState_InputPath(const char* testcase_path) { - - char *abspath = (char*) malloc(MAX_CMD_LEN * sizeof(char)); - - /* Use specified path if no --input_test* flag specified. Override if --input_* args specified. */ - if (testcase_path) { - if (!HAS_FLAG_input_test_file && !HAS_FLAG_input_test_files_dir) { - _fullpath(abspath, testcase_path, MAX_CMD_LEN); - } - } - - /* Prioritize using CLI-specified input paths, for the sake of fuzzing */ - if (HAS_FLAG_input_test_file) { - _fullpath(abspath, FLAGS_input_test_file, MAX_CMD_LEN); - } else if (HAS_FLAG_input_test_files_dir) { - _fullpath(abspath, FLAGS_input_test_files_dir, MAX_CMD_LEN); - } else { - DeepState_Abandon("No usable path specified for DeepState_InputPath."); - } - - DWORD file_attributes = GetFileAttributes(abspath); - if (file_attributes == INVALID_FILE_ATTRIBUTES){ - DeepState_Abandon("Specified input path does not exist."); - } - - if (HAS_FLAG_input_test_files_dir) { - if (!(file_attributes & INVALID_FILE_ATTRIBUTES)){ - DeepState_Abandon("Specified input directory is not a directory."); - } - } - - DeepState_LogFormat(DeepState_LogInfo, "Using `%s` as input path.", abspath); - return abspath; -} - -void DeepState_RunSavedTakeOverCases(jmp_buf env, - struct DeepState_TestInfo *test) { - - /* The method is not supported on Windows, and thus exit with an error. */ - DeepState_LogFormat(DeepState_LogError, - "Error: takeover works only on Unix based systems."); -} - -int DeepState_TakeOver(void) { - - /* The method is not supported on Windows, and thus exit with an error. */ - DeepState_LogFormat(DeepState_LogError, - "Error: takeover works only on Unix based systems."); - return -1; - -} - -/* Run a test case inside a new Windows process */ -int DeepState_RunTestWin(struct DeepState_TestInfo *test){ - - PROCESS_INFORMATION pi; - STARTUPINFO si; - DWORD exit_code = DeepState_TestRunPass; - - ZeroMemory( &si, sizeof(si) ); - si.cb = sizeof(si); - ZeroMemory( &pi, sizeof(pi) ); - - /* Get the fully qualified path of the current module */ - char command[MAX_CMD_LEN]; - if (!GetModuleFileName(NULL, command, MAX_CMD_LEN)){ - DeepState_LogFormat(DeepState_LogError, "GetModuleFileName failed (%d)", GetLastError()); - return DeepState_TestRunAbandon; - } - - /* Append the parameters to specify which test to run and to run the test - directly in the main process */ - snprintf(command, MAX_CMD_LEN, "%s --direct_run --input_which_test %s", command, test->test_name); - - if (HAS_FLAG_output_test_dir) { - snprintf(command, MAX_CMD_LEN, "%s --output_test_dir %s", command, FLAGS_output_test_dir); - } - - if (!FLAGS_fuzz || FLAGS_fuzz_save_passing) { - snprintf(command, MAX_CMD_LEN, "%s --fuzz_save_passing", command); - } - - /* Create the process */ - if(!CreateProcess(NULL, command, NULL, NULL, false, 0, NULL, NULL, &si, &pi)){ - DeepState_LogFormat(DeepState_LogError, "CreateProcess failed (%d)", GetLastError()); - return DeepState_TestRunAbandon; - } - - /* Wait for the process to complete and get it's exit code */ - WaitForSingleObject(pi.hProcess, INFINITE); - if (!GetExitCodeProcess(pi.hProcess, &exit_code)){ - DeepState_LogFormat(DeepState_LogError, "GetExitCodeProcess failed (%d)", GetLastError()); - return DeepState_TestRunAbandon; - } - - /* If at this point the exit code is not DeepState_TestRunPass, it means that - * DeepState_RunTest never reached the end of test->test_func(), and thus the - * function exited abnormally: - * test->test_func(); - * exit(DeepState_TestRunPass); */ - if (exit_code != DeepState_TestRunPass){ - exit_code = DeepState_TestRunCrash; - } - - return exit_code; -} - -/* Run a single test. This function is intended to be executed within a new -Windows process. */ -void DeepState_RunSingle(){ - struct DeepState_TestInfo *test = DeepState_FirstTest(); - - /* Seek for the TEST to run */ - for (test = DeepState_FirstTest(); test != NULL; test = test->prev) { - if (HAS_FLAG_input_which_test) { - if (strcmp(FLAGS_input_which_test, test->test_name) == 0) { - break; - } - } else { - DeepState_LogFormat(DeepState_LogWarning, - "No test specified, defaulting to first test defined (%s)", - test->test_name); - break; - } - } - - if (test == NULL) { - DeepState_LogFormat(DeepState_LogInfo, - "Could not find matching test for %s", - FLAGS_input_which_test); - return; - } - - /* Run the test */ - DeepState_RunTest(test); - -} - -/* Fork and run `test`. */ -extern enum DeepState_TestRunResult -DeepState_ForkAndRunTest(struct DeepState_TestInfo *test) { - int wstatus; - - if (FLAGS_fork) { - wstatus = DeepState_RunTestWin(test); - return (enum DeepState_TestRunResult) wstatus; - } - wstatus = DeepState_RunTestNoFork(test); - DeepState_CleanUp(); - return (enum DeepState_TestRunResult) wstatus; -} - -/* Checks if the given path corresponds to a regular file. */ -bool DeepState_IsRegularFile(char *path){ - DWORD file_attributes = GetFileAttributes(path); - return file_attributes != INVALID_FILE_ATTRIBUTES && !(file_attributes & FILE_ATTRIBUTE_DIRECTORY); -} - - -/* Resets the global `DeepState_Input` buffer, then fills it with the - * data found in the file `path`. */ -void DeepState_InitInputFromFile(const char *path) { - - FILE *fp = fopen(path, "r"); - if (fp == NULL) { - /* TODO(joe): Add error log with more info. */ - DeepState_Abandon("Unable to open file"); - } - - int fd = fileno(fp); - if (fd < 0) { - DeepState_Abandon("Tried to get file descriptor for invalid stream"); - } - - if (fseek(fp, 0L, SEEK_END) < 0){ - DeepState_Abandon("Unable to get test input size"); - } - size_t to_read = ftell(fp); - if(to_read < 0 || fseek(fp, 0L, SEEK_SET) < 0){ - DeepState_Abandon("Unable to get test input size"); - } - - if (to_read > sizeof(DeepState_Input)) { - DeepState_LogFormat(DeepState_LogWarning, "File too large, truncating to max input size"); - to_read = DeepState_InputSize; - } - - /* Reset the index. */ - DeepState_InputIndex = 0; - DeepState_SwarmConfigsIndex = 0; - - size_t count = fread((void *) DeepState_Input, 1, to_read, fp); - fclose(fp); - - if (count != to_read) { - /* TODO(joe): Add error log with more info. */ - DeepState_Abandon("Error reading file"); - } - - DeepState_InputInitialized = count; - - DeepState_LogFormat(DeepState_LogTrace, - "Initialized test input buffer with %zu bytes of data from `%s`", - count, path); -} +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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 + +#include "deepstate/Platform.h" +#include "deepstate/Option.h" +#include "deepstate/Log.h" +#include "DeepState.h" + +DEEPSTATE_BEGIN_EXTERN_C + +void DeepState_AllocCurrentTestRun(void) { + HANDLE shared_mem_handle = INVALID_HANDLE_VALUE; + + shared_mem_handle = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, + 0, sizeof(struct DeepState_TestRunInfo), "DeepState_CurrentTestRun"); + if (!shared_mem_handle){ + DeepState_LogFormat(DeepState_LogError, "Unable to map shared memory (%d)", GetLastError()); + exit(1); + } + + struct DeepState_TestRunInfo *shared_mem = (struct DeepState_TestRunInfo*) MapViewOfFile(shared_mem_handle, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(struct DeepState_TestRunInfo)); + if (!shared_mem){ + DeepState_LogFormat(DeepState_LogError, "Unable to map shared memory (%d)", GetLastError()); + exit(1); + } + + DeepState_CurrentTestRun = (struct DeepState_TestRunInfo *) shared_mem; +} + + +/* Return a string path to an input file or directory without parsing it to a type. This is + * useful method in the case where a tested function only takes a path input in order + * to generate some specialized structured type. Note: the returned path must be + * deallocated at the end by the caller. */ +char* DeepState_InputPath(const char* testcase_path) { + + char *abspath = (char*) malloc(MAX_CMD_LEN * sizeof(char)); + + /* Use specified path if no --input_test* flag specified. Override if --input_* args specified. */ + if (testcase_path) { + if (!HAS_FLAG_input_test_file && !HAS_FLAG_input_test_files_dir) { + _fullpath(abspath, testcase_path, MAX_CMD_LEN); + } + } + + /* Prioritize using CLI-specified input paths, for the sake of fuzzing */ + if (HAS_FLAG_input_test_file) { + _fullpath(abspath, FLAGS_input_test_file, MAX_CMD_LEN); + } else if (HAS_FLAG_input_test_files_dir) { + _fullpath(abspath, FLAGS_input_test_files_dir, MAX_CMD_LEN); + } else { + DeepState_Abandon("No usable path specified for DeepState_InputPath."); + } + + DWORD file_attributes = GetFileAttributes(abspath); + if (file_attributes == INVALID_FILE_ATTRIBUTES){ + DeepState_Abandon("Specified input path does not exist."); + } + + if (HAS_FLAG_input_test_files_dir) { + if (!(file_attributes & INVALID_FILE_ATTRIBUTES)){ + DeepState_Abandon("Specified input directory is not a directory."); + } + } + + DeepState_LogFormat(DeepState_LogInfo, "Using `%s` as input path.", abspath); + return abspath; +} + +void DeepState_RunSavedTakeOverCases(jmp_buf env, + struct DeepState_TestInfo *test) { + + /* The method is not supported on Windows, and thus exit with an error. */ + DeepState_LogFormat(DeepState_LogError, + "Error: takeover works only on Unix based systems."); +} + +int DeepState_TakeOver(void) { + + /* The method is not supported on Windows, and thus exit with an error. */ + DeepState_LogFormat(DeepState_LogError, + "Error: takeover works only on Unix based systems."); + return -1; + +} + +/* Run a test case inside a new Windows process */ +int DeepState_RunTestWin(struct DeepState_TestInfo *test){ + + PROCESS_INFORMATION pi; + STARTUPINFO si; + DWORD exit_code = DeepState_TestRunPass; + + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof(si); + ZeroMemory( &pi, sizeof(pi) ); + + /* Get the fully qualified path of the current module */ + char command[MAX_CMD_LEN]; + if (!GetModuleFileName(NULL, command, MAX_CMD_LEN)){ + DeepState_LogFormat(DeepState_LogError, "GetModuleFileName failed (%d)", GetLastError()); + return DeepState_TestRunAbandon; + } + + /* Append the parameters to specify which test to run and to run the test + directly in the main process */ + snprintf(command, MAX_CMD_LEN, "%s --direct_run --input_which_test %s", command, test->test_name); + + if (HAS_FLAG_output_test_dir) { + snprintf(command, MAX_CMD_LEN, "%s --output_test_dir %s", command, FLAGS_output_test_dir); + } + + if (!FLAGS_fuzz || FLAGS_fuzz_save_passing) { + snprintf(command, MAX_CMD_LEN, "%s --fuzz_save_passing", command); + } + + /* Create the process */ + if(!CreateProcess(NULL, command, NULL, NULL, false, 0, NULL, NULL, &si, &pi)){ + DeepState_LogFormat(DeepState_LogError, "CreateProcess failed (%d)", GetLastError()); + return DeepState_TestRunAbandon; + } + + /* Wait for the process to complete and get it's exit code */ + WaitForSingleObject(pi.hProcess, INFINITE); + if (!GetExitCodeProcess(pi.hProcess, &exit_code)){ + DeepState_LogFormat(DeepState_LogError, "GetExitCodeProcess failed (%d)", GetLastError()); + return DeepState_TestRunAbandon; + } + + /* If at this point the exit code is not DeepState_TestRunPass, it means that + * DeepState_RunTest never reached the end of test->test_func(), and thus the + * function exited abnormally: + * test->test_func(); + * exit(DeepState_TestRunPass); */ + if (exit_code != DeepState_TestRunPass){ + exit_code = DeepState_TestRunCrash; + } + + return exit_code; +} + +/* Run a single test. This function is intended to be executed within a new +Windows process. */ +void DeepState_RunSingle(){ + struct DeepState_TestInfo *test = DeepState_FirstTest(); + + /* Seek for the TEST to run */ + for (test = DeepState_FirstTest(); test != NULL; test = test->prev) { + if (HAS_FLAG_input_which_test) { + if (strcmp(FLAGS_input_which_test, test->test_name) == 0) { + break; + } + } else { + DeepState_LogFormat(DeepState_LogWarning, + "No test specified, defaulting to first test defined (%s)", + test->test_name); + break; + } + } + + if (test == NULL) { + DeepState_LogFormat(DeepState_LogInfo, + "Could not find matching test for %s", + FLAGS_input_which_test); + return; + } + + /* Run the test */ + DeepState_RunTest(test); + +} + +/* Fork and run `test`. */ +extern enum DeepState_TestRunResult +DeepState_ForkAndRunTest(struct DeepState_TestInfo *test) { + int wstatus; + + if (FLAGS_fork) { + wstatus = DeepState_RunTestWin(test); + return (enum DeepState_TestRunResult) wstatus; + } + wstatus = DeepState_RunTestNoFork(test); + DeepState_CleanUp(); + return (enum DeepState_TestRunResult) wstatus; +} + +/* Checks if the given path corresponds to a regular file. */ +bool DeepState_IsRegularFile(char *path){ + DWORD file_attributes = GetFileAttributes(path); + return file_attributes != INVALID_FILE_ATTRIBUTES && !(file_attributes & FILE_ATTRIBUTE_DIRECTORY); +} + + +/* Resets the global `DeepState_Input` buffer, then fills it with the + * data found in the file `path`. */ +void DeepState_InitInputFromFile(const char *path) { + + FILE *fp = fopen(path, "r"); + if (fp == NULL) { + /* TODO(joe): Add error log with more info. */ + DeepState_Abandon("Unable to open file"); + } + + int fd = fileno(fp); + if (fd < 0) { + DeepState_Abandon("Tried to get file descriptor for invalid stream"); + } + + if (fseek(fp, 0L, SEEK_END) < 0){ + DeepState_Abandon("Unable to get test input size"); + } + size_t to_read = ftell(fp); + if(to_read < 0 || fseek(fp, 0L, SEEK_SET) < 0){ + DeepState_Abandon("Unable to get test input size"); + } + + if (to_read > sizeof(DeepState_Input)) { + DeepState_LogFormat(DeepState_LogWarning, "File too large, truncating to max input size"); + to_read = DeepState_InputSize; + } + + /* Reset the index. */ + DeepState_InputIndex = 0; + DeepState_SwarmConfigsIndex = 0; + + size_t count = fread((void *) DeepState_Input, 1, to_read, fp); + fclose(fp); + + if (count != to_read) { + /* TODO(joe): Add error log with more info. */ + DeepState_Abandon("Error reading file"); + } + + DeepState_InputInitialized = count; + + DeepState_LogFormat(DeepState_LogTrace, + "Initialized test input buffer with %zu bytes of data from `%s`", + count, path); +} DEEPSTATE_END_EXTERN_C \ No newline at end of file diff --git a/src/lib/Log.c b/src/lib/Log.c index 2c03a8bc..21a15681 100644 --- a/src/lib/Log.c +++ b/src/lib/Log.c @@ -1,235 +1,235 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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. - */ - -/* Helps to avoid conflicting declaration types of `__printf_chk`. */ -#define printf printf_foo -#define vprintf vprintf_foo -#define fprintf fprintf_foo -#define vfprintf vfprintf_foo -#define __printf_chk __printf_chk_foo -#define __vprintf_chk __vprintf_chk_foo -#define __fprintf_chk __fprintf_chk_foo -#define __vfprintf_chk __vfprintf_chk_foo - -#include -#include -#include - -#include "deepstate/DeepState.h" -#include "deepstate/Log.h" - -#undef printf -#undef vprintf -#undef fprintf -#undef vfprintf -#undef __printf_chk -#undef __vprintf_chk -#undef __fprintf_chk -#undef __vfprintf_chk - -DEEPSTATE_BEGIN_EXTERN_C - -/* Returns a printable string version of the log level. */ -static const char *DeepState_LogLevelStr(enum DeepState_LogLevel level) { - switch (level) { - case DeepState_LogDebug: - return "DEBUG"; - case DeepState_LogTrace: - return "TRACE"; - case DeepState_LogInfo: - return "INFO"; - case DeepState_LogWarning: - return "WARNING"; - case DeepState_LogError: - return "ERROR"; - case DeepState_LogExternal: - return "EXTERNAL"; - case DeepState_LogFatal: - return "CRITICAL"; - default: - return "UNKNOWN"; - } -} - -enum { - DeepState_LogBufSize = 4096 -}; - -extern int DeepState_UsingLibFuzzer; -extern int DeepState_LibFuzzerLoud; - -char DeepState_LogBuf[DeepState_LogBufSize + 1] = {}; - -/* Log a C string. */ -DEEPSTATE_NOINLINE -void DeepState_Log(enum DeepState_LogLevel level, const char *str) { - if ((level < FLAGS_min_log_level) && - !(DeepState_UsingLibFuzzer && level == 0)) { - return; - } - //Removed because I don't see why we need to zero this before writing - //to it. - //DeepState_MemScrub(DeepState_LogBuf, DeepState_LogBufSize); - snprintf(DeepState_LogBuf, DeepState_LogBufSize, "%s: %s\n", - DeepState_LogLevelStr(level), str); - fputs(DeepState_LogBuf, stderr); - - if (DeepState_LogError == level) { - DeepState_SoftFail(); - } else if (DeepState_LogFatal == level) { - /* `DeepState_Fail()` calls `longjmp()`, so we need to make sure - * we clean up the log buffer first. */ - DeepState_ClearStream(level); - DeepState_Fail(); - } -} - -/* Log some formatted output. */ -DEEPSTATE_NOINLINE -void DeepState_LogVFormat(enum DeepState_LogLevel level, - const char *format, va_list args) { - struct DeepState_VarArgs va; - va_copy(va.args, args); - if (DeepState_UsingLibFuzzer && !DeepState_LibFuzzerLoud && - (level != DeepState_LogDebug)) { - return; - } - DeepState_LogStream(level); - DeepState_StreamVFormat(level, format, va.args); - DeepState_LogStream(level); -} - -/* Log some formatted output. */ -DEEPSTATE_NOINLINE -void DeepState_LogVFormatLLVM(enum DeepState_LogLevel level, - const char *format, va_list args) { - struct DeepState_VarArgs va; - va_copy(va.args, args); - DeepState_LogStream(level); - DeepState_StreamVFormat(level, format, va.args); - DeepState_LogStream(level); -} - -/* Log some formatted output. */ -DEEPSTATE_NOINLINE -void DeepState_LogFormat(enum DeepState_LogLevel level, - const char *format, ...) { - va_list args; - va_start(args, format); - DeepState_LogVFormat(level, format, args); - va_end(args); -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunknown-warning-option" - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wbuiltin-declaration-mismatch" - -/* Override libc! */ -DEEPSTATE_NOINLINE -int puts(const char *str) { - DeepState_Log(DeepState_LogTrace, str); - return 0; -} - -DEEPSTATE_NOINLINE -int printf(const char *format, ...) { - va_list args; - va_start(args, format); - DeepState_LogVFormat(DeepState_LogTrace, format, args); - va_end(args); - return 0; -} - -DEEPSTATE_NOINLINE -int __printf_chk(int flag, const char *format, ...) { - va_list args; - va_start(args, format); - DeepState_LogVFormat(DeepState_LogTrace, format, args); - va_end(args); - return 0; -} - -DEEPSTATE_NOINLINE -int vprintf(const char *format, va_list args) { - DeepState_LogVFormat(DeepState_LogTrace, format, args); - return 0; -} - -DEEPSTATE_NOINLINE -int __vprintf_chk(int flag, const char *format, va_list args) { - DeepState_LogVFormat(DeepState_LogTrace, format, args); - return 0; -} - -DEEPSTATE_NOINLINE -int vfprintf(FILE *file, const char *format, va_list args) { - if (stderr == file) { - DeepState_LogVFormat(DeepState_LogDebug, format, args); - } else if (stdout == file) { - DeepState_LogVFormat(DeepState_LogTrace, format, args); - } else { - DeepState_LogVFormat(DeepState_LogExternal, format, args); - } - /* - Old code. Now let's just log everything with odd dest as "external." - - if (!DeepState_UsingLibFuzzer) { - if (strstr(format, "INFO:") != NULL) { - // Assume such a string to an nonstd target is libFuzzer - DeepState_LogVFormat(DeepState_LogExternal, format, args); - } else { - DeepState_LogStream(DeepState_LogWarning); - DeepState_Log(DeepState_LogWarning, - "vfprintf with non-stdout/stderr stream follows:"); - DeepState_LogVFormat(DeepState_LogInfo, format, args); - } - } else { - DeepState_LogVFormat(DeepState_LogExternal, format, args); - } - */ - return 0; -} - -DEEPSTATE_NOINLINE -int fprintf(FILE *file, const char *format, ...) { - va_list args; - va_start(args, format); - vfprintf(file, format, args); - va_end(args); - return 0; -} - -DEEPSTATE_NOINLINE -int __fprintf_chk(int flag, FILE *file, const char *format, ...) { - va_list args; - va_start(args, format); - vfprintf(file, format, args); - va_end(args); - return 0; -} - -DEEPSTATE_NOINLINE -int __vfprintf_chk(int flag, FILE *file, const char *format, va_list args) { - vfprintf(file, format, args); - return 0; -} - -#pragma clang diagnostic pop -#pragma GCC diagnostic pop - -DEEPSTATE_END_EXTERN_C +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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. + */ + +/* Helps to avoid conflicting declaration types of `__printf_chk`. */ +#define printf printf_foo +#define vprintf vprintf_foo +#define fprintf fprintf_foo +#define vfprintf vfprintf_foo +#define __printf_chk __printf_chk_foo +#define __vprintf_chk __vprintf_chk_foo +#define __fprintf_chk __fprintf_chk_foo +#define __vfprintf_chk __vfprintf_chk_foo + +#include +#include +#include + +#include "deepstate/DeepState.h" +#include "deepstate/Log.h" + +#undef printf +#undef vprintf +#undef fprintf +#undef vfprintf +#undef __printf_chk +#undef __vprintf_chk +#undef __fprintf_chk +#undef __vfprintf_chk + +DEEPSTATE_BEGIN_EXTERN_C + +/* Returns a printable string version of the log level. */ +static const char *DeepState_LogLevelStr(enum DeepState_LogLevel level) { + switch (level) { + case DeepState_LogDebug: + return "DEBUG"; + case DeepState_LogTrace: + return "TRACE"; + case DeepState_LogInfo: + return "INFO"; + case DeepState_LogWarning: + return "WARNING"; + case DeepState_LogError: + return "ERROR"; + case DeepState_LogExternal: + return "EXTERNAL"; + case DeepState_LogFatal: + return "CRITICAL"; + default: + return "UNKNOWN"; + } +} + +enum { + DeepState_LogBufSize = 4096 +}; + +extern int DeepState_UsingLibFuzzer; +extern int DeepState_LibFuzzerLoud; + +char DeepState_LogBuf[DeepState_LogBufSize + 1] = {}; + +/* Log a C string. */ +DEEPSTATE_NOINLINE +void DeepState_Log(enum DeepState_LogLevel level, const char *str) { + if ((level < FLAGS_min_log_level) && + !(DeepState_UsingLibFuzzer && level == 0)) { + return; + } + //Removed because I don't see why we need to zero this before writing + //to it. + //DeepState_MemScrub(DeepState_LogBuf, DeepState_LogBufSize); + snprintf(DeepState_LogBuf, DeepState_LogBufSize, "%s: %s\n", + DeepState_LogLevelStr(level), str); + fputs(DeepState_LogBuf, stderr); + + if (DeepState_LogError == level) { + DeepState_SoftFail(); + } else if (DeepState_LogFatal == level) { + /* `DeepState_Fail()` calls `longjmp()`, so we need to make sure + * we clean up the log buffer first. */ + DeepState_ClearStream(level); + DeepState_Fail(); + } +} + +/* Log some formatted output. */ +DEEPSTATE_NOINLINE +void DeepState_LogVFormat(enum DeepState_LogLevel level, + const char *format, va_list args) { + struct DeepState_VarArgs va; + va_copy(va.args, args); + if (DeepState_UsingLibFuzzer && !DeepState_LibFuzzerLoud && + (level != DeepState_LogDebug)) { + return; + } + DeepState_LogStream(level); + DeepState_StreamVFormat(level, format, va.args); + DeepState_LogStream(level); +} + +/* Log some formatted output. */ +DEEPSTATE_NOINLINE +void DeepState_LogVFormatLLVM(enum DeepState_LogLevel level, + const char *format, va_list args) { + struct DeepState_VarArgs va; + va_copy(va.args, args); + DeepState_LogStream(level); + DeepState_StreamVFormat(level, format, va.args); + DeepState_LogStream(level); +} + +/* Log some formatted output. */ +DEEPSTATE_NOINLINE +void DeepState_LogFormat(enum DeepState_LogLevel level, + const char *format, ...) { + va_list args; + va_start(args, format); + DeepState_LogVFormat(level, format, args); + va_end(args); +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wbuiltin-declaration-mismatch" + +/* Override libc! */ +DEEPSTATE_NOINLINE +int puts(const char *str) { + DeepState_Log(DeepState_LogTrace, str); + return 0; +} + +DEEPSTATE_NOINLINE +int printf(const char *format, ...) { + va_list args; + va_start(args, format); + DeepState_LogVFormat(DeepState_LogTrace, format, args); + va_end(args); + return 0; +} + +DEEPSTATE_NOINLINE +int __printf_chk(int flag, const char *format, ...) { + va_list args; + va_start(args, format); + DeepState_LogVFormat(DeepState_LogTrace, format, args); + va_end(args); + return 0; +} + +DEEPSTATE_NOINLINE +int vprintf(const char *format, va_list args) { + DeepState_LogVFormat(DeepState_LogTrace, format, args); + return 0; +} + +DEEPSTATE_NOINLINE +int __vprintf_chk(int flag, const char *format, va_list args) { + DeepState_LogVFormat(DeepState_LogTrace, format, args); + return 0; +} + +DEEPSTATE_NOINLINE +int vfprintf(FILE *file, const char *format, va_list args) { + if (stderr == file) { + DeepState_LogVFormat(DeepState_LogDebug, format, args); + } else if (stdout == file) { + DeepState_LogVFormat(DeepState_LogTrace, format, args); + } else { + DeepState_LogVFormat(DeepState_LogExternal, format, args); + } + /* + Old code. Now let's just log everything with odd dest as "external." + + if (!DeepState_UsingLibFuzzer) { + if (strstr(format, "INFO:") != NULL) { + // Assume such a string to an nonstd target is libFuzzer + DeepState_LogVFormat(DeepState_LogExternal, format, args); + } else { + DeepState_LogStream(DeepState_LogWarning); + DeepState_Log(DeepState_LogWarning, + "vfprintf with non-stdout/stderr stream follows:"); + DeepState_LogVFormat(DeepState_LogInfo, format, args); + } + } else { + DeepState_LogVFormat(DeepState_LogExternal, format, args); + } + */ + return 0; +} + +DEEPSTATE_NOINLINE +int fprintf(FILE *file, const char *format, ...) { + va_list args; + va_start(args, format); + vfprintf(file, format, args); + va_end(args); + return 0; +} + +DEEPSTATE_NOINLINE +int __fprintf_chk(int flag, FILE *file, const char *format, ...) { + va_list args; + va_start(args, format); + vfprintf(file, format, args); + va_end(args); + return 0; +} + +DEEPSTATE_NOINLINE +int __vfprintf_chk(int flag, FILE *file, const char *format, va_list args) { + vfprintf(file, format, args); + return 0; +} + +#pragma clang diagnostic pop +#pragma GCC diagnostic pop + +DEEPSTATE_END_EXTERN_C diff --git a/src/lib/Option.c b/src/lib/Option.c index 3cb1be22..8d5a34ed 100644 --- a/src/lib/Option.c +++ b/src/lib/Option.c @@ -1,375 +1,375 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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 "deepstate/DeepState.h" -#include "deepstate/Option.h" - -#include -#include -#include - -DEFINE_bool(help, MiscGroup, false, "Show the usage message."); - -enum { - kMaxNumOptions = 32, - kMaxOptionLength = 1024 - 1 -}; - -/* Linked list of registered options. */ -static struct DeepState_Option *DeepState_Options = NULL; -int DeepState_OptionsAreInitialized = 0; - -static const char DeepState_FakeSpace = ' ' | ((char) 0x80); - -/* Copy of the option string. */ -static int DeepState_OptionStringLength = 0; -static char DeepState_OptionString[kMaxOptionLength + 1] = {'\0'}; -static const char *DeepState_OptionNames[kMaxNumOptions] = {NULL}; -static const char *DeepState_OptionValues[kMaxNumOptions] = {NULL}; - -/* Defines a mapping between group enums and display strings */ -static const char * ProcessGroupString(DeepState_OptGroup group) { - return (const char *[]) { - "Input/Output Options", - "Fuzzing/Symex Analysis Options", - "Test Execution Options", - "Test Selection Options", - "Other Options", - }[group]; -} - -/* Copy a substring into the main options string. */ -static int CopyStringIntoOptions(int offset, const char *string, - int replace_spaces) { - for (; offset < kMaxOptionLength && *string; ++string) { - char ch = *string; - if (' ' == ch && replace_spaces) { - ch = DeepState_FakeSpace; - } - DeepState_OptionString[offset++] = ch; - } - return offset; -} - -/* Finalize the option string. */ -static void TerminateOptionString(int length) { - if (kMaxOptionLength <= length) { - DeepState_Abandon("Option string is too long."); - } - DeepState_OptionString[length] = '\0'; - DeepState_OptionStringLength = length; -} - -/* Check that a character is a valid option character. */ -static int IsValidOptionChar(char ch) { - return ('a' <= ch && ch <= 'z') || - ('A' <= ch && ch <= 'Z') || - ('_' == ch); -} - -/* Check that a character is a valid option character. */ -static int IsValidValueChar(char ch) { - return (' ' < ch && ch <= '~') || ch == DeepState_FakeSpace; -} - -/* Format an option string into a more amenable internal format. This is a sort - * of pre-processing step to distinguish options from values. */ -static void ProcessOptionString(void) { - char *ch = &DeepState_OptionString[0]; - char * const max_ch = &DeepState_OptionString[DeepState_OptionStringLength]; - unsigned num_options = 0; - - enum OptionLexState { - kInOption, - kInValue, - kSeenEqual, - kSeenSpace, - kSeenDash, - kElsewhere - } state = kElsewhere; - - for (; ch < max_ch; ++ch) { - switch (state) { - case kInOption: { - const char ch_val = *ch; - - /* Terminate the option name. */ - if (!IsValidOptionChar(ch_val)) { - *ch = '\0'; - - /* We've seen an equal, which mean's we're moving into the - * beginning of a value. */ - if ('=' == ch_val) { - state = kSeenEqual; - } else if (' ' == ch_val || DeepState_FakeSpace == ch_val) { - state = kSeenSpace; - } else { - state = kElsewhere; - } - } - break; - } - - case kInValue: - if (!IsValidValueChar(*ch)) { - state = kElsewhere; - *ch = '\0'; - } else if (DeepState_FakeSpace == *ch) { - *ch = ' '; /* Convert back to a space. */ - } - break; - - case kSeenSpace: - if (' ' == *ch || DeepState_FakeSpace == *ch) { - *ch = '\0'; - state = kSeenSpace; - } else if ('-' == *ch) { - state = kSeenDash; - } else if (IsValidValueChar(*ch)) { /* E.g. `--tools bbcount`. */ - state = kInValue; - DeepState_OptionValues[num_options - 1] = ch; - - } else { - state = kElsewhere; - } - break; - - case kSeenEqual: - if (IsValidValueChar(*ch)) { /* E.g. `--tools=bbcount`. */ - state = kInValue; - DeepState_OptionValues[num_options - 1] = ch; - } else { /* E.g. `--tools=`. */ - state = kElsewhere; - } - break; - - case kSeenDash: - if ('-' == *ch) { - state = kInOption; /* Default to positional. */ - if (kMaxNumOptions <= num_options) { - DeepState_Abandon("Parsed too many options!"); - } - DeepState_OptionValues[num_options] = ""; - DeepState_OptionNames[num_options++] = ch + 1; - } else { - state = kElsewhere; - } - *ch = '\0'; - break; - - case kElsewhere: - if ('-' == *ch) { - state = kSeenDash; - } - *ch = '\0'; - break; - } - } -} - -/* Returns a pointer to the value for an option name, or a NULL if the option - * name was not found (or if it was specified but had no value). */ -static const char *FindValueForName(const char *name) { - for (int i = 0; i < kMaxNumOptions && DeepState_OptionNames[i]; ++i) { - if (!strcmp(DeepState_OptionNames[i], name)) { - return DeepState_OptionValues[i]; - } - } - return NULL; -} - -/* Process the pending options.. */ -static void ProcessPendingOptions(void) { - struct DeepState_Option *option = DeepState_Options; - struct DeepState_Option *next_option = NULL; - for (; option != NULL; option = next_option) { - next_option = option->next; - option->parse(option); - } -} - -/* Initialize the options from the command-line arguments. */ -void DeepState_InitOptions(int argc, ...) { - va_list args; - va_start(args, argc); - const char **argv = va_arg(args, const char **); - va_end(args); - - int offset = 0; - int arg = 1; - for (const char *sep = ""; arg < argc; ++arg, sep = " ") { - offset = CopyStringIntoOptions(offset, sep, 0); - offset = CopyStringIntoOptions(offset, argv[arg], 1); - } - TerminateOptionString(offset); - ProcessOptionString(); - DeepState_OptionsAreInitialized = 1; - ProcessPendingOptions(); - - if (FLAGS_help) { - DeepState_PrintAllOptions(argv[0]); - exit(0); - } -} - -enum { - kLineLength = 80, - kTabLength = 8, - kBufferMaxLength = kLineLength - kTabLength -}; - -/* Perform line buffering of the document string. */ -static const char *BufferDocString(char *buff, const char *docstring) { - char *last_stop = buff; - const char *docstring_last_stop = docstring; - const char *docstring_stop = docstring + kBufferMaxLength; - for (; docstring < docstring_stop && *docstring; ) { - if (' ' == *docstring) { - last_stop = buff; - docstring_last_stop = docstring + 1; - } else if ('\n' == *docstring) { - last_stop = buff; - docstring_last_stop = docstring + 1; - break; - } - *buff++ = *docstring++; - } - if (docstring < docstring_stop && !*docstring) { - *buff = '\0'; - return docstring; - } else { - *last_stop = '\0'; - return docstring_last_stop; - } -} - -/* Works for --help option: print out each options along with their document. */ -void DeepState_PrintAllOptions(const char *prog_name) { - char buff[4096]; - ssize_t write_len = 0; - char line_buff[kLineLength]; - struct DeepState_Option *option = DeepState_Options; - struct DeepState_Option *next_option = NULL; - - sprintf(buff, "Usage: %s \n\n", prog_name); - write_len = write(STDERR_FILENO, buff, strlen(buff)); - - /* First, print group display string, then each option that corresponds */ - for (int group = 0; group != MiscGroup; group++) { - - sprintf(buff, "\033[4m%s\033[24m\n\n", ProcessGroupString(group)); - write_len = write(STDERR_FILENO, buff, strlen(buff)); - - for (; option != NULL; option = next_option) { - - next_option = option->next; - if (group == option->group) { - - sprintf(buff, "--%s", option->name); - write_len = write(STDERR_FILENO, buff, strlen(buff)); - - const char *docstring = option->docstring; - do { - docstring = BufferDocString(line_buff, docstring); - sprintf(buff, "\n\t%s", line_buff); - write_len = write(STDERR_FILENO, buff, strlen(buff)); - } while (*docstring); - write_len = write(STDERR_FILENO, "\n\n", 2); - } - } - - /* Reiterate over linked list again with each group */ - option = DeepState_Options; - next_option = NULL; - } - (void) write_len; /* Deal with -Wunused-result. */ -} - -/* Initialize an option. */ -void DeepState_AddOption(struct DeepState_Option *option) { - if (DeepState_OptionsAreInitialized) { - option->parse(option); /* Added late? */ - } - if (!option->next) { - option->next = DeepState_Options; - DeepState_Options = option; - } -} - -/* Parse an option that is a string. */ -void DeepState_ParseStringOption(struct DeepState_Option *option) { - const char *value = FindValueForName(option->name); - if (value != NULL) { - *(option->has_value) = 1; - *((const char **) (option->value)) = value; - } -} - -/* Parse an option that will be interpreted as a boolean value. */ -void DeepState_ParseBoolOption(struct DeepState_Option *option) { - const char *value = FindValueForName(option->name); - if (value != NULL) { - switch (*value) { - case '1': case 'y': case 'Y': case 't': case 'T': - case '\0': /* Treat the presence of the option as truth. */ - *(option->has_value) = 1; - *((int *) (option->value)) = 1; - break; - case '0': case 'n': case 'N': case 'f': case 'F': - *(option->has_value) = 1; - *((int *) (option->value)) = 0; - break; - default: - break; - } - - /* Alternative name, e.g. `--foo` vs. `--no_foo`. */ - } else { - const char *alt_value = FindValueForName(option->alt_name); - if (alt_value != NULL) { - if ('\0' != alt_value[0]) { - DeepState_Abandon("Got an option value for a negated boolean option."); - } - *(option->has_value) = 1; - *((int *) (option->value)) = 0; - } - } -} - - -/* Parse an option that will be interpreted as an unsigned integer. */ -void DeepState_ParseIntOption(struct DeepState_Option *option) { - const char *value = FindValueForName(option->name); - if (value != NULL) { - int int_value = 0; - if (sscanf(value, "%d", &int_value)) { - *(option->has_value) = 1; - *((int *) (option->value)) = int_value; - } - } -} - -/* Parse an option that will be interpreted as an unsigned integer. */ -void DeepState_ParseUIntOption(struct DeepState_Option *option) { - const char *value = FindValueForName(option->name); - if (value != NULL) { - unsigned uint_value = 0; - if (sscanf(value, "%u", &uint_value)) { - *(option->has_value) = 1; - *((unsigned *) (option->value)) = uint_value; - } - } -} +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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 "deepstate/DeepState.h" +#include "deepstate/Option.h" + +#include +#include +#include + +DEFINE_bool(help, MiscGroup, false, "Show the usage message."); + +enum { + kMaxNumOptions = 32, + kMaxOptionLength = 1024 - 1 +}; + +/* Linked list of registered options. */ +static struct DeepState_Option *DeepState_Options = NULL; +int DeepState_OptionsAreInitialized = 0; + +static const char DeepState_FakeSpace = ' ' | ((char) 0x80); + +/* Copy of the option string. */ +static int DeepState_OptionStringLength = 0; +static char DeepState_OptionString[kMaxOptionLength + 1] = {'\0'}; +static const char *DeepState_OptionNames[kMaxNumOptions] = {NULL}; +static const char *DeepState_OptionValues[kMaxNumOptions] = {NULL}; + +/* Defines a mapping between group enums and display strings */ +static const char * ProcessGroupString(DeepState_OptGroup group) { + return (const char *[]) { + "Input/Output Options", + "Fuzzing/Symex Analysis Options", + "Test Execution Options", + "Test Selection Options", + "Other Options", + }[group]; +} + +/* Copy a substring into the main options string. */ +static int CopyStringIntoOptions(int offset, const char *string, + int replace_spaces) { + for (; offset < kMaxOptionLength && *string; ++string) { + char ch = *string; + if (' ' == ch && replace_spaces) { + ch = DeepState_FakeSpace; + } + DeepState_OptionString[offset++] = ch; + } + return offset; +} + +/* Finalize the option string. */ +static void TerminateOptionString(int length) { + if (kMaxOptionLength <= length) { + DeepState_Abandon("Option string is too long."); + } + DeepState_OptionString[length] = '\0'; + DeepState_OptionStringLength = length; +} + +/* Check that a character is a valid option character. */ +static int IsValidOptionChar(char ch) { + return ('a' <= ch && ch <= 'z') || + ('A' <= ch && ch <= 'Z') || + ('_' == ch); +} + +/* Check that a character is a valid option character. */ +static int IsValidValueChar(char ch) { + return (' ' < ch && ch <= '~') || ch == DeepState_FakeSpace; +} + +/* Format an option string into a more amenable internal format. This is a sort + * of pre-processing step to distinguish options from values. */ +static void ProcessOptionString(void) { + char *ch = &DeepState_OptionString[0]; + char * const max_ch = &DeepState_OptionString[DeepState_OptionStringLength]; + unsigned num_options = 0; + + enum OptionLexState { + kInOption, + kInValue, + kSeenEqual, + kSeenSpace, + kSeenDash, + kElsewhere + } state = kElsewhere; + + for (; ch < max_ch; ++ch) { + switch (state) { + case kInOption: { + const char ch_val = *ch; + + /* Terminate the option name. */ + if (!IsValidOptionChar(ch_val)) { + *ch = '\0'; + + /* We've seen an equal, which mean's we're moving into the + * beginning of a value. */ + if ('=' == ch_val) { + state = kSeenEqual; + } else if (' ' == ch_val || DeepState_FakeSpace == ch_val) { + state = kSeenSpace; + } else { + state = kElsewhere; + } + } + break; + } + + case kInValue: + if (!IsValidValueChar(*ch)) { + state = kElsewhere; + *ch = '\0'; + } else if (DeepState_FakeSpace == *ch) { + *ch = ' '; /* Convert back to a space. */ + } + break; + + case kSeenSpace: + if (' ' == *ch || DeepState_FakeSpace == *ch) { + *ch = '\0'; + state = kSeenSpace; + } else if ('-' == *ch) { + state = kSeenDash; + } else if (IsValidValueChar(*ch)) { /* E.g. `--tools bbcount`. */ + state = kInValue; + DeepState_OptionValues[num_options - 1] = ch; + + } else { + state = kElsewhere; + } + break; + + case kSeenEqual: + if (IsValidValueChar(*ch)) { /* E.g. `--tools=bbcount`. */ + state = kInValue; + DeepState_OptionValues[num_options - 1] = ch; + } else { /* E.g. `--tools=`. */ + state = kElsewhere; + } + break; + + case kSeenDash: + if ('-' == *ch) { + state = kInOption; /* Default to positional. */ + if (kMaxNumOptions <= num_options) { + DeepState_Abandon("Parsed too many options!"); + } + DeepState_OptionValues[num_options] = ""; + DeepState_OptionNames[num_options++] = ch + 1; + } else { + state = kElsewhere; + } + *ch = '\0'; + break; + + case kElsewhere: + if ('-' == *ch) { + state = kSeenDash; + } + *ch = '\0'; + break; + } + } +} + +/* Returns a pointer to the value for an option name, or a NULL if the option + * name was not found (or if it was specified but had no value). */ +static const char *FindValueForName(const char *name) { + for (int i = 0; i < kMaxNumOptions && DeepState_OptionNames[i]; ++i) { + if (!strcmp(DeepState_OptionNames[i], name)) { + return DeepState_OptionValues[i]; + } + } + return NULL; +} + +/* Process the pending options.. */ +static void ProcessPendingOptions(void) { + struct DeepState_Option *option = DeepState_Options; + struct DeepState_Option *next_option = NULL; + for (; option != NULL; option = next_option) { + next_option = option->next; + option->parse(option); + } +} + +/* Initialize the options from the command-line arguments. */ +void DeepState_InitOptions(int argc, ...) { + va_list args; + va_start(args, argc); + const char **argv = va_arg(args, const char **); + va_end(args); + + int offset = 0; + int arg = 1; + for (const char *sep = ""; arg < argc; ++arg, sep = " ") { + offset = CopyStringIntoOptions(offset, sep, 0); + offset = CopyStringIntoOptions(offset, argv[arg], 1); + } + TerminateOptionString(offset); + ProcessOptionString(); + DeepState_OptionsAreInitialized = 1; + ProcessPendingOptions(); + + if (FLAGS_help) { + DeepState_PrintAllOptions(argv[0]); + exit(0); + } +} + +enum { + kLineLength = 80, + kTabLength = 8, + kBufferMaxLength = kLineLength - kTabLength +}; + +/* Perform line buffering of the document string. */ +static const char *BufferDocString(char *buff, const char *docstring) { + char *last_stop = buff; + const char *docstring_last_stop = docstring; + const char *docstring_stop = docstring + kBufferMaxLength; + for (; docstring < docstring_stop && *docstring; ) { + if (' ' == *docstring) { + last_stop = buff; + docstring_last_stop = docstring + 1; + } else if ('\n' == *docstring) { + last_stop = buff; + docstring_last_stop = docstring + 1; + break; + } + *buff++ = *docstring++; + } + if (docstring < docstring_stop && !*docstring) { + *buff = '\0'; + return docstring; + } else { + *last_stop = '\0'; + return docstring_last_stop; + } +} + +/* Works for --help option: print out each options along with their document. */ +void DeepState_PrintAllOptions(const char *prog_name) { + char buff[4096]; + ssize_t write_len = 0; + char line_buff[kLineLength]; + struct DeepState_Option *option = DeepState_Options; + struct DeepState_Option *next_option = NULL; + + sprintf(buff, "Usage: %s \n\n", prog_name); + write_len = write(STDERR_FILENO, buff, strlen(buff)); + + /* First, print group display string, then each option that corresponds */ + for (int group = 0; group != MiscGroup; group++) { + + sprintf(buff, "\033[4m%s\033[24m\n\n", ProcessGroupString(group)); + write_len = write(STDERR_FILENO, buff, strlen(buff)); + + for (; option != NULL; option = next_option) { + + next_option = option->next; + if (group == option->group) { + + sprintf(buff, "--%s", option->name); + write_len = write(STDERR_FILENO, buff, strlen(buff)); + + const char *docstring = option->docstring; + do { + docstring = BufferDocString(line_buff, docstring); + sprintf(buff, "\n\t%s", line_buff); + write_len = write(STDERR_FILENO, buff, strlen(buff)); + } while (*docstring); + write_len = write(STDERR_FILENO, "\n\n", 2); + } + } + + /* Reiterate over linked list again with each group */ + option = DeepState_Options; + next_option = NULL; + } + (void) write_len; /* Deal with -Wunused-result. */ +} + +/* Initialize an option. */ +void DeepState_AddOption(struct DeepState_Option *option) { + if (DeepState_OptionsAreInitialized) { + option->parse(option); /* Added late? */ + } + if (!option->next) { + option->next = DeepState_Options; + DeepState_Options = option; + } +} + +/* Parse an option that is a string. */ +void DeepState_ParseStringOption(struct DeepState_Option *option) { + const char *value = FindValueForName(option->name); + if (value != NULL) { + *(option->has_value) = 1; + *((const char **) (option->value)) = value; + } +} + +/* Parse an option that will be interpreted as a boolean value. */ +void DeepState_ParseBoolOption(struct DeepState_Option *option) { + const char *value = FindValueForName(option->name); + if (value != NULL) { + switch (*value) { + case '1': case 'y': case 'Y': case 't': case 'T': + case '\0': /* Treat the presence of the option as truth. */ + *(option->has_value) = 1; + *((int *) (option->value)) = 1; + break; + case '0': case 'n': case 'N': case 'f': case 'F': + *(option->has_value) = 1; + *((int *) (option->value)) = 0; + break; + default: + break; + } + + /* Alternative name, e.g. `--foo` vs. `--no_foo`. */ + } else { + const char *alt_value = FindValueForName(option->alt_name); + if (alt_value != NULL) { + if ('\0' != alt_value[0]) { + DeepState_Abandon("Got an option value for a negated boolean option."); + } + *(option->has_value) = 1; + *((int *) (option->value)) = 0; + } + } +} + + +/* Parse an option that will be interpreted as an unsigned integer. */ +void DeepState_ParseIntOption(struct DeepState_Option *option) { + const char *value = FindValueForName(option->name); + if (value != NULL) { + int int_value = 0; + if (sscanf(value, "%d", &int_value)) { + *(option->has_value) = 1; + *((int *) (option->value)) = int_value; + } + } +} + +/* Parse an option that will be interpreted as an unsigned integer. */ +void DeepState_ParseUIntOption(struct DeepState_Option *option) { + const char *value = FindValueForName(option->name); + if (value != NULL) { + unsigned uint_value = 0; + if (sscanf(value, "%u", &uint_value)) { + *(option->has_value) = 1; + *((unsigned *) (option->value)) = uint_value; + } + } +} diff --git a/src/lib/Stream.c b/src/lib/Stream.c index 9c005ab1..ea94d675 100644 --- a/src/lib/Stream.c +++ b/src/lib/Stream.c @@ -1,632 +1,632 @@ -/* - * Copyright (c) 2019 Trail of Bits, Inc. - * - * 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 -#include -#include -#include -#include -#include -#include - -#include "deepstate/DeepState.h" -#include "deepstate/Log.h" - -DEEPSTATE_BEGIN_EXTERN_C - -enum { - DeepState_StreamSize = 1048576 -}; - -/* Formatting options available to the streaming API. */ -struct DeepState_StreamFormatOptions { - /* int radix; */ - int hex; - int oct; - int show_base; - /*int width; */ - int left_justify; - int add_sign; - char fill; -}; - -/* Stream type that accumulates formatted data to be printed. This loosely - * mirrors C++ I/O streams, not because I/O streams are good, but instead - * because the ability to stream in data to things like the C++-backed - * `ASSERT` and `CHECK` macros is really nice. */ -struct DeepState_Stream { - int size; - struct DeepState_StreamFormatOptions options; - char message[DeepState_StreamSize + 2]; - char staging[32]; - char format[32]; - char unpack[32]; - union { - uint64_t as_uint64; - double as_fp64; - } value; -}; - -/* Hard-coded streams for each log level. */ -static struct DeepState_Stream DeepState_Streams[DeepState_LogFatal + 1] = {}; - -/* Endian specifier for Python's `struct.pack` and `struct.unpack`. - * = Native endian - * < Little endian - * > Big endian - */ -static char DeepState_EndianSpecifier = '='; - -/* Figure out what the Python `struct` endianness specifier should be. */ -DEEPSTATE_INITIALIZER(DetectEndianness) { - static const int one = 1; - if ((const char *) &one) { - DeepState_EndianSpecifier = '<'; /* Little endian. */ - } else { - DeepState_EndianSpecifier = '>'; /* Big endian. */ - } -} - -/* Fills the `stream->unpack` character buffer with a Python `struct.unpack`- - * compatible format specifier. */ -static void DeepState_StreamUnpack(struct DeepState_Stream *stream, char type) { - stream->unpack[0] = DeepState_EndianSpecifier; - stream->unpack[1] = type; - stream->unpack[2] = '\0'; -} - -/* Fill in the format for when we want to stream an integer. */ -static void DeepState_StreamIntFormat(struct DeepState_Stream *stream, - size_t val_size, int is_unsigned) { - char *format = stream->format; - int i = 0; - - format[i++] = '%'; - if(stream->options.left_justify) { - format[i++] = '-'; - } - - if(stream->options.add_sign) { - format[i++] = '+'; - } - - if (stream->options.fill) { - format[i++] = stream->options.fill; - } - - if (stream->options.show_base) { - format[i++] = '#'; /* Show the radix. */ - } - - if (8 == val_size) { - format[i++] = 'l'; - format[i++] = 'l'; - - } else if (2 == val_size) { - format[i++] = 'h'; - - } else if (1 == val_size) { - if (is_unsigned) { - format[i++] = 'h'; - format[i++] = 'h'; - } - } - - if (stream->options.hex) { - format[i++] = 'x'; - } else if (stream->options.oct) { - format[i++] = 'o'; - } else if (is_unsigned) { - format[i++] = 'u'; - } else { - if (1 == val_size) { - format[i++] = 'c'; - } else { - format[i++] = 'd'; - } - } - - format[i++] = '\0'; -} - -/* Make sure that we don't exceed our formatting capacity when running. */ -static void CheckCapacity(struct DeepState_Stream *stream, - int num_chars_to_add) { - if (0 > num_chars_to_add) { - DeepState_Abandon("Can't add a negative number of characters to a stream."); - } else if ((stream->size + num_chars_to_add) >= DeepState_StreamSize) { - DeepState_Abandon("Exceeded capacity of stream buffer."); - } -} - -/* Stream an integer into the stream's message. This function is designed to - * be hooked by the symbolic executor, so that it can easily pull out the - * relevant data from `*val`, which may be symbolic, and defer the actual - * formatting until later. */ -DEEPSTATE_NOINLINE -void _DeepState_StreamInt(enum DeepState_LogLevel level, const char *format, - const char *unpack, uint64_t *val) { - struct DeepState_Stream *stream = &(DeepState_Streams[level]); - int size = 0; - int remaining_size = DeepState_StreamSize - stream->size; - if (unpack[1] == 'Q' || unpack[1] == 'q') { - size = snprintf(&(stream->message[stream->size]), - remaining_size, format, *val); - } else { - size = snprintf(&(stream->message[stream->size]), - remaining_size, format, (uint32_t) *val); - } - CheckCapacity(stream, size); - stream->size += size; -} - -/* Format a streamed-in float. This gets hooked. */ -DEEPSTATE_NOINLINE -void _DeepState_StreamFloat(enum DeepState_LogLevel level, const char *format, - const char *unpack, double *val) { - struct DeepState_Stream *stream = &(DeepState_Streams[level]); - int remaining_size = DeepState_StreamSize - stream->size; - int size = snprintf(&(stream->message[stream->size]), - remaining_size, format, *val); - CheckCapacity(stream, size); - stream->size += size; -} - -/* Format a streamed-in NUL-terminated string. This gets hooked. */ -DEEPSTATE_NOINLINE -void _DeepState_StreamString(enum DeepState_LogLevel level, const char *format, - const char *str) { - struct DeepState_Stream *stream = &(DeepState_Streams[level]); - int remaining_size = DeepState_StreamSize - stream->size; - int size = snprintf(&(stream->message[stream->size]), - remaining_size, format, str); - CheckCapacity(stream, size); - stream->size += size; -} - -void DeepState_StreamPointer(enum DeepState_LogLevel level, const void *val) { - struct DeepState_Stream *stream = &(DeepState_Streams[level]); - stream->format[0] = '0'; - stream->format[1] = 'x'; - stream->format[2] = '%'; - stream->format[3] = '0'; - if (sizeof(void *) == 4) { - stream->format[4] = '8'; - stream->format[5] = 'x'; - stream->format[6] = '\0'; - } else { - stream->format[4] = '1'; - stream->format[5] = '6'; - stream->format[6] = 'x'; - stream->format[7] = '\0'; - } - DeepState_StreamUnpack(stream, (sizeof(void *) == 8 ? 'Q' : 'I')); - stream->value.as_uint64 = (uintptr_t) val; - _DeepState_StreamInt(level, stream->format, stream->unpack, - &(stream->value.as_uint64)); -} - -#define MAKE_INT_STREAMER(Type, type, is_unsigned, pack_kind) \ - void DeepState_Stream ## Type(enum DeepState_LogLevel level, type val) { \ - struct DeepState_Stream *stream = &(DeepState_Streams[level]); \ - DeepState_StreamIntFormat(stream, sizeof(val), is_unsigned); \ - DeepState_StreamUnpack(stream, pack_kind); \ - stream->value.as_uint64 = (uint64_t) val; \ - _DeepState_StreamInt(level, stream->format, stream->unpack, \ - &(stream->value.as_uint64)); \ - } - -MAKE_INT_STREAMER(UInt64, uint64_t, 1, 'Q') -MAKE_INT_STREAMER(Int64, int64_t, 0, 'q') - -MAKE_INT_STREAMER(UInt32, uint32_t, 1, 'I') -MAKE_INT_STREAMER(Int32, int32_t, 0, 'i') - -MAKE_INT_STREAMER(UInt16, uint16_t, 1, 'h') -MAKE_INT_STREAMER(Int16, int16_t, 0, 'H') - -MAKE_INT_STREAMER(UInt8, uint8_t, 1, 'B') -MAKE_INT_STREAMER(Int8, int8_t, 0, 'c') - -#undef MAKE_INT_STREAMER - -/* Stream a C string into the stream's message. */ -void DeepState_StreamCStr(enum DeepState_LogLevel level, const char *begin) { - _DeepState_StreamString(level, "%s", begin); -} - -/* Stream a some data in the inclusive range `[begin, end]` into the - * stream's message. */ -/*void DeepState_StreamData(enum DeepState_LogLevel level, const void *begin, - const void *end) { - struct DeepState_Stream *stream = &(DeepState_Streams[level]); - int remaining_size = DeepState_StreamSize - stream->size; - int input_size = (int) ((uintptr_t) end - (uintptr_t) begin) + 1; - CheckCapacity(stream, input_size); - memcpy(&(stream->message[stream->size]), begin, (size_t) input_size); - stream->size += input_size; -}*/ - -/* Stream a `double` into the stream's message. This function is designed to - * be hooked by the symbolic executor, so that it can easily pull out the - * relevant data from `*val`, which may be symbolic, and defer the actual - * formatting until later. */ -void DeepState_StreamDouble(enum DeepState_LogLevel level, double val) { - struct DeepState_Stream *stream = &(DeepState_Streams[level]); - const char *format = "%f"; /* TODO(pag): Support more? */ - stream->value.as_fp64 = val; - DeepState_StreamUnpack(stream, 'd'); - _DeepState_StreamFloat(level, format, stream->unpack, &(stream->value.as_fp64)); -} - -/* Clear the contents of the stream and don't log it. */ -void DeepState_ClearStream(enum DeepState_LogLevel level) { - struct DeepState_Stream *stream = &(DeepState_Streams[level]); - if (stream->size) { - stream->size = 0; - } -} - -/* Flush the contents of the stream to a log. */ -void DeepState_LogStream(enum DeepState_LogLevel level) { - struct DeepState_Stream *stream = &(DeepState_Streams[level]); - if (stream->size) { - stream->message[stream->size] = '\0'; - stream->message[DeepState_StreamSize] = '\0'; - DeepState_Log(level, stream->message); - DeepState_ClearStream(level); - } -} - -/* Reset the formatting in a stream. */ -void DeepState_StreamResetFormatting(enum DeepState_LogLevel level) { - struct DeepState_Stream *stream = &(DeepState_Streams[level]); - DeepState_MemScrub(&(stream->options), sizeof(stream->options)); -} - -static int DeepState_NumLsInt64BitFormat = 2; - -/* `PRId64` will be "ld" or "lld" */ -DEEPSTATE_INITIALIZER(DeepState_NumLsFor64BitFormat) { - DeepState_NumLsInt64BitFormat = (PRId64)[1] == 'd' ? 1 : 2; -} - -/* Approximately do string format parsing and convert it into calls into our - * streaming API. */ -DEEPSTATE_NOINLINE -static int DeepState_StreamFormatValue(enum DeepState_LogLevel level, - const char *format, - struct DeepState_VarArgs *va) { - struct DeepState_Stream *stream = &(DeepState_Streams[level]); - char format_buf[32] = {'\0'}; - int i = 0; - int k = 0; - int length = 4; - char ch = '\0'; - int is_string = 0; - int is_unsigned = 0; - int is_float = 0; - int long_double = 0; - int num_ls = 0; - char extract = '\0'; - -#define READ_FORMAT_CHAR \ - ch = format[i]; \ - format_buf[i - k] = ch; \ - format_buf[i - k + 1] = '\0'; \ - i++ - - READ_FORMAT_CHAR; /* Read the '%' */ - - if ('%' != ch) { - DeepState_Abandon("Invalid format."); - return 0; - } - - /* Flags */ -get_flag_char: - READ_FORMAT_CHAR; - switch (ch) { - case '\0': - DeepState_Abandon("Incomplete format (flags)."); - return 0; - case '-': - case '+': - case ' ': - case '#': - case '0': - goto get_flag_char; - default: - break; - } - - /* Width */ -get_width_char: - switch (ch) { - case '\0': - DeepState_Abandon("Incomplete format (width)."); - return 0; - case '*': - DeepState_Abandon("Variable width printing not supported."); - return 0; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - READ_FORMAT_CHAR; - goto get_width_char; - default: - break; - } - - /* Precision */ - if ('.' == ch) { - get_precision_char: - READ_FORMAT_CHAR; - switch (ch) { - case '\0': - DeepState_Abandon("Incomplete format (precision)."); - return 0; - case '*': - DeepState_Abandon("Variable precision printing not supported."); - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - goto get_precision_char; - default: - break; - } - } - - /* Length */ -get_length_char: - switch (ch) { - case '\0': - DeepState_Abandon("Incomplete format (length)."); - return 0; - case 'L': - long_double = 1; - k += 1; /* Overwrite the `L`. */ - READ_FORMAT_CHAR; - break; - case 'h': - length /= 2; - READ_FORMAT_CHAR; - goto get_length_char; - case 'l': - num_ls += 1; - READ_FORMAT_CHAR; - goto get_length_char; - case 'j': - length = (int) sizeof(intmax_t); - READ_FORMAT_CHAR; - break; - case 'z': - length = (int) sizeof(size_t); - READ_FORMAT_CHAR; - break; - case 't': - length = (int) sizeof(ptrdiff_t); - READ_FORMAT_CHAR; - break; - default: - break; - } - - if (!length) { - length = 1; - } else if (num_ls >= DeepState_NumLsInt64BitFormat) { - length = 8; - } - - format_buf[i] = '\0'; - - /* Specifier */ - switch(ch) { - case '\0': - DeepState_Abandon("Incomplete format (specifier)."); - return 0; - - case 'n': - return i; /* Nothing printed. */ - - /* Print a character. */ - case 'c': - stream->value.as_uint64 = (uint64_t) (char) va_arg(va->args, int); - extract = 'c'; - goto common_stream_int; - - /* Signed integer. */ - case 'd': - case 'i': - if (1 == length) { - stream->value.as_uint64 = (uint64_t) (int8_t) va_arg(va->args, int); - extract = 'b'; - } else if (2 == length) { - stream->value.as_uint64 = (uint64_t) (int16_t) va_arg(va->args, int); - extract = 'h'; - } else if (4 == length) { - stream->value.as_uint64 = (uint64_t) (int32_t) va_arg(va->args, int); - extract = 'i'; - } else if (8 == length) { - stream->value.as_uint64 = (uint64_t) va_arg(va->args, int64_t); - extract = 'q'; - } else { - DeepState_Abandon("Unsupported integer length."); - } - goto common_stream_int; - - /* Pointer. */ - case 'p': - length = (int) sizeof(void *); - format_buf[i - k - 1] = 'x'; - /* Note: Falls through. */ - - /* Unsigned, hex, octal */ - case 'u': - case 'o': - case 'x': - case 'X': - if (1 == length) { - stream->value.as_uint64 = (uint64_t) (uint8_t) va_arg(va->args, int); - extract = 'B'; - } else if (2 == length) { - stream->value.as_uint64 = (uint64_t) (uint16_t) va_arg(va->args, int); - extract = 'H'; - } else if (4 == length) { - stream->value.as_uint64 = (uint64_t) (uint32_t) va_arg(va->args, int); - extract = 'I'; - } else if (8 == length) { - stream->value.as_uint64 = (uint64_t) va_arg(va->args, uint64_t); - extract = 'Q'; - } else { - DeepState_Abandon("Unsupported integer length."); - } - - common_stream_int: - DeepState_StreamUnpack(stream, extract); - _DeepState_StreamInt(level, format_buf, stream->unpack, - &(stream->value.as_uint64)); - goto done; - - /* Floating point, scientific notation, etc. */ - case 'f': - case 'F': - case 'e': - case 'E': - case 'g': - case 'G': - case 'a': - case 'A': - if (long_double) { - stream->value.as_fp64 = (double) va_arg(va->args, long double); - } else { - stream->value.as_fp64 = va_arg(va->args, double); - } - DeepState_StreamUnpack(stream, 'd'); - _DeepState_StreamFloat(level, format_buf, stream->unpack, - &(stream->value.as_fp64)); - goto done; - - case 's': { - const char *str = va_arg(va->args, const char *); - _DeepState_StreamString(level, format_buf, str); - goto done; - } - - default: - DeepState_Abandon("Unsupported format specifier."); - return 0; - } -done: - if (!i) { - DeepState_Abandon("Made no progress."); - } - return i; -} - -/* Holding buffer for a format string. If we have something like `foo%dbar` - * then we want to be able to pull out the `%d`, and so having the format - * string in a mutable buffer lets us conveniently NUL-out the `b` of `bar` - * following the `%d`. */ -static char DeepState_Format[DeepState_StreamSize + 1]; - -/* Stream some formatted input. This converts a `printf`-style format string - * into a */ -void DeepState_StreamVFormat(enum DeepState_LogLevel level, - const char *format_, va_list args) { - struct DeepState_VarArgs va; - va_copy(va.args, args); - - char *begin = NULL; - char *end = NULL; - char *format = &(DeepState_Format[0]); - int i = 0; - char ch = '\0'; - char next_ch = '\0'; - size_t len = strlen(format_); - - if (len >= DeepState_StreamSize) { - DeepState_Abandon("Format string is too long."); - } - - /* Concretize the string format. */ - memcpy(format, format_, len); - format[len] = '\0'; - DeepState_ConcretizeCStr(format); - - for (i = 0; '\0' != (ch = format[i]); ) { - if (!begin) { - begin = &(format[i]); - } - - if ('%' == ch) { - if ('%' == format[i + 1]) { - end = &(format[i]); - next_ch = end[1]; - end[1] = '\0'; - DeepState_StreamCStr(level, begin); - end[1] = next_ch; - begin = NULL; - end = NULL; - i += 2; - - } else { - if (end) { - next_ch = end[1]; - end[1] = '\0'; - DeepState_StreamCStr(level, begin); - end[1] = next_ch; - } - begin = NULL; - end = NULL; - i += DeepState_StreamFormatValue(level, &(format[i]), &va); - } - } else { - end = &(format[i]); - i += 1; - } - } - - if (begin && begin[0]) { - DeepState_StreamCStr(level, begin); - } -} - -/* Stream some formatted input */ -void DeepState_StreamFormat(enum DeepState_LogLevel level, - const char *format, ...) { - va_list args; - va_start(args, format); - DeepState_StreamVFormat(level, format, args); - va_end(args); -} - -DEEPSTATE_END_EXTERN_C +/* + * Copyright (c) 2019 Trail of Bits, Inc. + * + * 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 +#include +#include +#include +#include +#include +#include + +#include "deepstate/DeepState.h" +#include "deepstate/Log.h" + +DEEPSTATE_BEGIN_EXTERN_C + +enum { + DeepState_StreamSize = 1048576 +}; + +/* Formatting options available to the streaming API. */ +struct DeepState_StreamFormatOptions { + /* int radix; */ + int hex; + int oct; + int show_base; + /*int width; */ + int left_justify; + int add_sign; + char fill; +}; + +/* Stream type that accumulates formatted data to be printed. This loosely + * mirrors C++ I/O streams, not because I/O streams are good, but instead + * because the ability to stream in data to things like the C++-backed + * `ASSERT` and `CHECK` macros is really nice. */ +struct DeepState_Stream { + int size; + struct DeepState_StreamFormatOptions options; + char message[DeepState_StreamSize + 2]; + char staging[32]; + char format[32]; + char unpack[32]; + union { + uint64_t as_uint64; + double as_fp64; + } value; +}; + +/* Hard-coded streams for each log level. */ +static struct DeepState_Stream DeepState_Streams[DeepState_LogFatal + 1] = {}; + +/* Endian specifier for Python's `struct.pack` and `struct.unpack`. + * = Native endian + * < Little endian + * > Big endian + */ +static char DeepState_EndianSpecifier = '='; + +/* Figure out what the Python `struct` endianness specifier should be. */ +DEEPSTATE_INITIALIZER(DetectEndianness) { + static const int one = 1; + if ((const char *) &one) { + DeepState_EndianSpecifier = '<'; /* Little endian. */ + } else { + DeepState_EndianSpecifier = '>'; /* Big endian. */ + } +} + +/* Fills the `stream->unpack` character buffer with a Python `struct.unpack`- + * compatible format specifier. */ +static void DeepState_StreamUnpack(struct DeepState_Stream *stream, char type) { + stream->unpack[0] = DeepState_EndianSpecifier; + stream->unpack[1] = type; + stream->unpack[2] = '\0'; +} + +/* Fill in the format for when we want to stream an integer. */ +static void DeepState_StreamIntFormat(struct DeepState_Stream *stream, + size_t val_size, int is_unsigned) { + char *format = stream->format; + int i = 0; + + format[i++] = '%'; + if(stream->options.left_justify) { + format[i++] = '-'; + } + + if(stream->options.add_sign) { + format[i++] = '+'; + } + + if (stream->options.fill) { + format[i++] = stream->options.fill; + } + + if (stream->options.show_base) { + format[i++] = '#'; /* Show the radix. */ + } + + if (8 == val_size) { + format[i++] = 'l'; + format[i++] = 'l'; + + } else if (2 == val_size) { + format[i++] = 'h'; + + } else if (1 == val_size) { + if (is_unsigned) { + format[i++] = 'h'; + format[i++] = 'h'; + } + } + + if (stream->options.hex) { + format[i++] = 'x'; + } else if (stream->options.oct) { + format[i++] = 'o'; + } else if (is_unsigned) { + format[i++] = 'u'; + } else { + if (1 == val_size) { + format[i++] = 'c'; + } else { + format[i++] = 'd'; + } + } + + format[i++] = '\0'; +} + +/* Make sure that we don't exceed our formatting capacity when running. */ +static void CheckCapacity(struct DeepState_Stream *stream, + int num_chars_to_add) { + if (0 > num_chars_to_add) { + DeepState_Abandon("Can't add a negative number of characters to a stream."); + } else if ((stream->size + num_chars_to_add) >= DeepState_StreamSize) { + DeepState_Abandon("Exceeded capacity of stream buffer."); + } +} + +/* Stream an integer into the stream's message. This function is designed to + * be hooked by the symbolic executor, so that it can easily pull out the + * relevant data from `*val`, which may be symbolic, and defer the actual + * formatting until later. */ +DEEPSTATE_NOINLINE +void _DeepState_StreamInt(enum DeepState_LogLevel level, const char *format, + const char *unpack, uint64_t *val) { + struct DeepState_Stream *stream = &(DeepState_Streams[level]); + int size = 0; + int remaining_size = DeepState_StreamSize - stream->size; + if (unpack[1] == 'Q' || unpack[1] == 'q') { + size = snprintf(&(stream->message[stream->size]), + remaining_size, format, *val); + } else { + size = snprintf(&(stream->message[stream->size]), + remaining_size, format, (uint32_t) *val); + } + CheckCapacity(stream, size); + stream->size += size; +} + +/* Format a streamed-in float. This gets hooked. */ +DEEPSTATE_NOINLINE +void _DeepState_StreamFloat(enum DeepState_LogLevel level, const char *format, + const char *unpack, double *val) { + struct DeepState_Stream *stream = &(DeepState_Streams[level]); + int remaining_size = DeepState_StreamSize - stream->size; + int size = snprintf(&(stream->message[stream->size]), + remaining_size, format, *val); + CheckCapacity(stream, size); + stream->size += size; +} + +/* Format a streamed-in NUL-terminated string. This gets hooked. */ +DEEPSTATE_NOINLINE +void _DeepState_StreamString(enum DeepState_LogLevel level, const char *format, + const char *str) { + struct DeepState_Stream *stream = &(DeepState_Streams[level]); + int remaining_size = DeepState_StreamSize - stream->size; + int size = snprintf(&(stream->message[stream->size]), + remaining_size, format, str); + CheckCapacity(stream, size); + stream->size += size; +} + +void DeepState_StreamPointer(enum DeepState_LogLevel level, const void *val) { + struct DeepState_Stream *stream = &(DeepState_Streams[level]); + stream->format[0] = '0'; + stream->format[1] = 'x'; + stream->format[2] = '%'; + stream->format[3] = '0'; + if (sizeof(void *) == 4) { + stream->format[4] = '8'; + stream->format[5] = 'x'; + stream->format[6] = '\0'; + } else { + stream->format[4] = '1'; + stream->format[5] = '6'; + stream->format[6] = 'x'; + stream->format[7] = '\0'; + } + DeepState_StreamUnpack(stream, (sizeof(void *) == 8 ? 'Q' : 'I')); + stream->value.as_uint64 = (uintptr_t) val; + _DeepState_StreamInt(level, stream->format, stream->unpack, + &(stream->value.as_uint64)); +} + +#define MAKE_INT_STREAMER(Type, type, is_unsigned, pack_kind) \ + void DeepState_Stream ## Type(enum DeepState_LogLevel level, type val) { \ + struct DeepState_Stream *stream = &(DeepState_Streams[level]); \ + DeepState_StreamIntFormat(stream, sizeof(val), is_unsigned); \ + DeepState_StreamUnpack(stream, pack_kind); \ + stream->value.as_uint64 = (uint64_t) val; \ + _DeepState_StreamInt(level, stream->format, stream->unpack, \ + &(stream->value.as_uint64)); \ + } + +MAKE_INT_STREAMER(UInt64, uint64_t, 1, 'Q') +MAKE_INT_STREAMER(Int64, int64_t, 0, 'q') + +MAKE_INT_STREAMER(UInt32, uint32_t, 1, 'I') +MAKE_INT_STREAMER(Int32, int32_t, 0, 'i') + +MAKE_INT_STREAMER(UInt16, uint16_t, 1, 'h') +MAKE_INT_STREAMER(Int16, int16_t, 0, 'H') + +MAKE_INT_STREAMER(UInt8, uint8_t, 1, 'B') +MAKE_INT_STREAMER(Int8, int8_t, 0, 'c') + +#undef MAKE_INT_STREAMER + +/* Stream a C string into the stream's message. */ +void DeepState_StreamCStr(enum DeepState_LogLevel level, const char *begin) { + _DeepState_StreamString(level, "%s", begin); +} + +/* Stream a some data in the inclusive range `[begin, end]` into the + * stream's message. */ +/*void DeepState_StreamData(enum DeepState_LogLevel level, const void *begin, + const void *end) { + struct DeepState_Stream *stream = &(DeepState_Streams[level]); + int remaining_size = DeepState_StreamSize - stream->size; + int input_size = (int) ((uintptr_t) end - (uintptr_t) begin) + 1; + CheckCapacity(stream, input_size); + memcpy(&(stream->message[stream->size]), begin, (size_t) input_size); + stream->size += input_size; +}*/ + +/* Stream a `double` into the stream's message. This function is designed to + * be hooked by the symbolic executor, so that it can easily pull out the + * relevant data from `*val`, which may be symbolic, and defer the actual + * formatting until later. */ +void DeepState_StreamDouble(enum DeepState_LogLevel level, double val) { + struct DeepState_Stream *stream = &(DeepState_Streams[level]); + const char *format = "%f"; /* TODO(pag): Support more? */ + stream->value.as_fp64 = val; + DeepState_StreamUnpack(stream, 'd'); + _DeepState_StreamFloat(level, format, stream->unpack, &(stream->value.as_fp64)); +} + +/* Clear the contents of the stream and don't log it. */ +void DeepState_ClearStream(enum DeepState_LogLevel level) { + struct DeepState_Stream *stream = &(DeepState_Streams[level]); + if (stream->size) { + stream->size = 0; + } +} + +/* Flush the contents of the stream to a log. */ +void DeepState_LogStream(enum DeepState_LogLevel level) { + struct DeepState_Stream *stream = &(DeepState_Streams[level]); + if (stream->size) { + stream->message[stream->size] = '\0'; + stream->message[DeepState_StreamSize] = '\0'; + DeepState_Log(level, stream->message); + DeepState_ClearStream(level); + } +} + +/* Reset the formatting in a stream. */ +void DeepState_StreamResetFormatting(enum DeepState_LogLevel level) { + struct DeepState_Stream *stream = &(DeepState_Streams[level]); + DeepState_MemScrub(&(stream->options), sizeof(stream->options)); +} + +static int DeepState_NumLsInt64BitFormat = 2; + +/* `PRId64` will be "ld" or "lld" */ +DEEPSTATE_INITIALIZER(DeepState_NumLsFor64BitFormat) { + DeepState_NumLsInt64BitFormat = (PRId64)[1] == 'd' ? 1 : 2; +} + +/* Approximately do string format parsing and convert it into calls into our + * streaming API. */ +DEEPSTATE_NOINLINE +static int DeepState_StreamFormatValue(enum DeepState_LogLevel level, + const char *format, + struct DeepState_VarArgs *va) { + struct DeepState_Stream *stream = &(DeepState_Streams[level]); + char format_buf[32] = {'\0'}; + int i = 0; + int k = 0; + int length = 4; + char ch = '\0'; + int is_string = 0; + int is_unsigned = 0; + int is_float = 0; + int long_double = 0; + int num_ls = 0; + char extract = '\0'; + +#define READ_FORMAT_CHAR \ + ch = format[i]; \ + format_buf[i - k] = ch; \ + format_buf[i - k + 1] = '\0'; \ + i++ + + READ_FORMAT_CHAR; /* Read the '%' */ + + if ('%' != ch) { + DeepState_Abandon("Invalid format."); + return 0; + } + + /* Flags */ +get_flag_char: + READ_FORMAT_CHAR; + switch (ch) { + case '\0': + DeepState_Abandon("Incomplete format (flags)."); + return 0; + case '-': + case '+': + case ' ': + case '#': + case '0': + goto get_flag_char; + default: + break; + } + + /* Width */ +get_width_char: + switch (ch) { + case '\0': + DeepState_Abandon("Incomplete format (width)."); + return 0; + case '*': + DeepState_Abandon("Variable width printing not supported."); + return 0; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + READ_FORMAT_CHAR; + goto get_width_char; + default: + break; + } + + /* Precision */ + if ('.' == ch) { + get_precision_char: + READ_FORMAT_CHAR; + switch (ch) { + case '\0': + DeepState_Abandon("Incomplete format (precision)."); + return 0; + case '*': + DeepState_Abandon("Variable precision printing not supported."); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + goto get_precision_char; + default: + break; + } + } + + /* Length */ +get_length_char: + switch (ch) { + case '\0': + DeepState_Abandon("Incomplete format (length)."); + return 0; + case 'L': + long_double = 1; + k += 1; /* Overwrite the `L`. */ + READ_FORMAT_CHAR; + break; + case 'h': + length /= 2; + READ_FORMAT_CHAR; + goto get_length_char; + case 'l': + num_ls += 1; + READ_FORMAT_CHAR; + goto get_length_char; + case 'j': + length = (int) sizeof(intmax_t); + READ_FORMAT_CHAR; + break; + case 'z': + length = (int) sizeof(size_t); + READ_FORMAT_CHAR; + break; + case 't': + length = (int) sizeof(ptrdiff_t); + READ_FORMAT_CHAR; + break; + default: + break; + } + + if (!length) { + length = 1; + } else if (num_ls >= DeepState_NumLsInt64BitFormat) { + length = 8; + } + + format_buf[i] = '\0'; + + /* Specifier */ + switch(ch) { + case '\0': + DeepState_Abandon("Incomplete format (specifier)."); + return 0; + + case 'n': + return i; /* Nothing printed. */ + + /* Print a character. */ + case 'c': + stream->value.as_uint64 = (uint64_t) (char) va_arg(va->args, int); + extract = 'c'; + goto common_stream_int; + + /* Signed integer. */ + case 'd': + case 'i': + if (1 == length) { + stream->value.as_uint64 = (uint64_t) (int8_t) va_arg(va->args, int); + extract = 'b'; + } else if (2 == length) { + stream->value.as_uint64 = (uint64_t) (int16_t) va_arg(va->args, int); + extract = 'h'; + } else if (4 == length) { + stream->value.as_uint64 = (uint64_t) (int32_t) va_arg(va->args, int); + extract = 'i'; + } else if (8 == length) { + stream->value.as_uint64 = (uint64_t) va_arg(va->args, int64_t); + extract = 'q'; + } else { + DeepState_Abandon("Unsupported integer length."); + } + goto common_stream_int; + + /* Pointer. */ + case 'p': + length = (int) sizeof(void *); + format_buf[i - k - 1] = 'x'; + /* Note: Falls through. */ + + /* Unsigned, hex, octal */ + case 'u': + case 'o': + case 'x': + case 'X': + if (1 == length) { + stream->value.as_uint64 = (uint64_t) (uint8_t) va_arg(va->args, int); + extract = 'B'; + } else if (2 == length) { + stream->value.as_uint64 = (uint64_t) (uint16_t) va_arg(va->args, int); + extract = 'H'; + } else if (4 == length) { + stream->value.as_uint64 = (uint64_t) (uint32_t) va_arg(va->args, int); + extract = 'I'; + } else if (8 == length) { + stream->value.as_uint64 = (uint64_t) va_arg(va->args, uint64_t); + extract = 'Q'; + } else { + DeepState_Abandon("Unsupported integer length."); + } + + common_stream_int: + DeepState_StreamUnpack(stream, extract); + _DeepState_StreamInt(level, format_buf, stream->unpack, + &(stream->value.as_uint64)); + goto done; + + /* Floating point, scientific notation, etc. */ + case 'f': + case 'F': + case 'e': + case 'E': + case 'g': + case 'G': + case 'a': + case 'A': + if (long_double) { + stream->value.as_fp64 = (double) va_arg(va->args, long double); + } else { + stream->value.as_fp64 = va_arg(va->args, double); + } + DeepState_StreamUnpack(stream, 'd'); + _DeepState_StreamFloat(level, format_buf, stream->unpack, + &(stream->value.as_fp64)); + goto done; + + case 's': { + const char *str = va_arg(va->args, const char *); + _DeepState_StreamString(level, format_buf, str); + goto done; + } + + default: + DeepState_Abandon("Unsupported format specifier."); + return 0; + } +done: + if (!i) { + DeepState_Abandon("Made no progress."); + } + return i; +} + +/* Holding buffer for a format string. If we have something like `foo%dbar` + * then we want to be able to pull out the `%d`, and so having the format + * string in a mutable buffer lets us conveniently NUL-out the `b` of `bar` + * following the `%d`. */ +static char DeepState_Format[DeepState_StreamSize + 1]; + +/* Stream some formatted input. This converts a `printf`-style format string + * into a */ +void DeepState_StreamVFormat(enum DeepState_LogLevel level, + const char *format_, va_list args) { + struct DeepState_VarArgs va; + va_copy(va.args, args); + + char *begin = NULL; + char *end = NULL; + char *format = &(DeepState_Format[0]); + int i = 0; + char ch = '\0'; + char next_ch = '\0'; + size_t len = strlen(format_); + + if (len >= DeepState_StreamSize) { + DeepState_Abandon("Format string is too long."); + } + + /* Concretize the string format. */ + memcpy(format, format_, len); + format[len] = '\0'; + DeepState_ConcretizeCStr(format); + + for (i = 0; '\0' != (ch = format[i]); ) { + if (!begin) { + begin = &(format[i]); + } + + if ('%' == ch) { + if ('%' == format[i + 1]) { + end = &(format[i]); + next_ch = end[1]; + end[1] = '\0'; + DeepState_StreamCStr(level, begin); + end[1] = next_ch; + begin = NULL; + end = NULL; + i += 2; + + } else { + if (end) { + next_ch = end[1]; + end[1] = '\0'; + DeepState_StreamCStr(level, begin); + end[1] = next_ch; + } + begin = NULL; + end = NULL; + i += DeepState_StreamFormatValue(level, &(format[i]), &va); + } + } else { + end = &(format[i]); + i += 1; + } + } + + if (begin && begin[0]) { + DeepState_StreamCStr(level, begin); + } +} + +/* Stream some formatted input */ +void DeepState_StreamFormat(enum DeepState_LogLevel level, + const char *format, ...) { + va_list args; + va_start(args, format); + DeepState_StreamVFormat(level, format, args); + va_end(args); +} + +DEEPSTATE_END_EXTERN_C diff --git a/tests/deepstate_base.py b/tests/deepstate_base.py index 08cc1753..cc9b4b4d 100644 --- a/tests/deepstate_base.py +++ b/tests/deepstate_base.py @@ -1,41 +1,41 @@ -from __future__ import print_function -from unittest import TestCase - - -class DeepStateTestCase(TestCase): - ''' - # Remove for now, since it fails - - def test_angr(self): - self.run_deepstate("deepstate-angr") - ''' - - def test_builtin_fuzz(self): - self.run_deepstate("--fuzz") - - def test_manticore(self): - return # Right now manticore always times out, so just skip it - self.run_deepstate("deepstate-manticore") - - def run_deepstate(self, deepstate): - raise NotImplementedError("Define an actual test of DeepState in DeepStateTestCase:run_deepstate.") - - -class DeepStateFuzzerTestCase(TestCase): - def test_afl(self): - self.run_deepstate("deepstate-afl") - - def test_libfuzzer(self): - self.run_deepstate("deepstate-libfuzzer") - - def test_honggfuzz(self): - self.run_deepstate("deepstate-honggfuzz") - - def test_angora(self): - self.run_deepstate("deepstate-angora") - - def test_eclipser(self): - self.run_deepstate("deepstate-eclipser") - - def run_deepstate(self, deepstate): - raise NotImplementedError("Define an actual test of DeepState in DeepStateFuzzerTestCase:run_deepstate.") +from __future__ import print_function +from unittest import TestCase + + +class DeepStateTestCase(TestCase): + ''' + # Remove for now, since it fails + + def test_angr(self): + self.run_deepstate("deepstate-angr") + ''' + + def test_builtin_fuzz(self): + self.run_deepstate("--fuzz") + + def test_manticore(self): + return # Right now manticore always times out, so just skip it + self.run_deepstate("deepstate-manticore") + + def run_deepstate(self, deepstate): + raise NotImplementedError("Define an actual test of DeepState in DeepStateTestCase:run_deepstate.") + + +class DeepStateFuzzerTestCase(TestCase): + def test_afl(self): + self.run_deepstate("deepstate-afl") + + def test_libfuzzer(self): + self.run_deepstate("deepstate-libfuzzer") + + def test_honggfuzz(self): + self.run_deepstate("deepstate-honggfuzz") + + def test_angora(self): + self.run_deepstate("deepstate-angora") + + def test_eclipser(self): + self.run_deepstate("deepstate-eclipser") + + def run_deepstate(self, deepstate): + raise NotImplementedError("Define an actual test of DeepState in DeepStateFuzzerTestCase:run_deepstate.") diff --git a/tests/logrun.py b/tests/logrun.py index 852137ff..e663c4d0 100644 --- a/tests/logrun.py +++ b/tests/logrun.py @@ -1,99 +1,99 @@ -from __future__ import print_function -import subprocess -import time -import sys -from tempfile import mkdtemp -from shutil import rmtree -import psutil - - -def logrun(cmd, file, timeout, break_callback=None, filters=[]): - sys.stderr.write("\n\n" + ("=" * 80) + "\n") - sys.stderr.write("RUNNING: ") - sys.stderr.write(" ".join(cmd) + "\n\n") - sys.stderr.flush() - - tmp_out_dir = None - with open(file, 'w') as outf: - additional_args = [] - - # auto-create output dir - if set(cmd).isdisjoint({"-o", "--output_test_dir", "--out_test_name"}): - tmp_out_dir = mkdtemp(prefix="deepstate_logrun_") - additional_args.extend(["--output_test_dir", tmp_out_dir]) # create empty output dir - - # We need to set log_level so we see ALL messages, for testing - if "--min_log_level" not in cmd: - additional_args.extend(["--min_log_level", "0"]) - - proc = subprocess.Popen(cmd + additional_args, stdout=outf, stderr=outf) - - callback_break = False - oldContentLen = 0 - start = time.time() - lastOutput = time.time() - inf = open(file, 'r') - while (proc.poll() is None) and ((time.time() - start) < timeout): - inf.seek(0, 2) - newContentLen = inf.tell() - - if newContentLen > oldContentLen: - inf.seek(oldContentLen, 0) - newContent = inf.read() - printLines = newContent.split("\n") - for f in filters: - printLines = list(filter(lambda line: f not in line, printLines)) - printContent = "\n".join(printLines) - sys.stderr.write(printContent) - sys.stderr.flush() - oldContentLen = newContentLen - lastOutput = time.time() - - if break_callback and break_callback(newContent): - callback_break = True - break - - if (time.time() - lastOutput) > 300: - sys.stderr.write(".") - sys.stderr.flush() - lastOutput = time.time() - - time.sleep(0.5) - - totalTime = time.time() - start - sys.stderr.write("\n") - - inf.seek(oldContentLen, 0) - newContent = inf.read() - sys.stderr.write(newContent) - sys.stderr.flush() - inf.seek(0, 0) - contents = inf.read() - inf.close() - - rv = [proc.returncode, contents] - if callback_break: - rv[0] = "CALLBACK_BREAK" - elif proc.poll() is None: - rv[0] = "TIMEOUT" - elif "Traceback (most recent call last)" in contents: - rv[0] = "EXCEPTION RAISED" - elif "internal error" in contents: - rv[0] = "INTERNAL ERROR" - - try: - for some_proc in psutil.Process(proc.pid).children(recursive=True) + [proc]: - some_proc.terminate() - except psutil.NoSuchProcess: - pass - - sys.stderr.write("\nDONE\n\n") - sys.stderr.write("TOTAL EXECUTION TIME: " + str(totalTime) + "\n") - sys.stderr.write("RETURN VALUE: " + str(proc.returncode) + "\n") - sys.stderr.write("RETURNING AS RESULT: " + str(rv[0]) + "\n") - sys.stderr.write("=" * 80 + "\n") - - if tmp_out_dir: - rmtree(tmp_out_dir, ignore_errors=True) - - return rv +from __future__ import print_function +import subprocess +import time +import sys +from tempfile import mkdtemp +from shutil import rmtree +import psutil + + +def logrun(cmd, file, timeout, break_callback=None, filters=[]): + sys.stderr.write("\n\n" + ("=" * 80) + "\n") + sys.stderr.write("RUNNING: ") + sys.stderr.write(" ".join(cmd) + "\n\n") + sys.stderr.flush() + + tmp_out_dir = None + with open(file, 'w') as outf: + additional_args = [] + + # auto-create output dir + if set(cmd).isdisjoint({"-o", "--output_test_dir", "--out_test_name"}): + tmp_out_dir = mkdtemp(prefix="deepstate_logrun_") + additional_args.extend(["--output_test_dir", tmp_out_dir]) # create empty output dir + + # We need to set log_level so we see ALL messages, for testing + if "--min_log_level" not in cmd: + additional_args.extend(["--min_log_level", "0"]) + + proc = subprocess.Popen(cmd + additional_args, stdout=outf, stderr=outf) + + callback_break = False + oldContentLen = 0 + start = time.time() + lastOutput = time.time() + inf = open(file, 'r') + while (proc.poll() is None) and ((time.time() - start) < timeout): + inf.seek(0, 2) + newContentLen = inf.tell() + + if newContentLen > oldContentLen: + inf.seek(oldContentLen, 0) + newContent = inf.read() + printLines = newContent.split("\n") + for f in filters: + printLines = list(filter(lambda line: f not in line, printLines)) + printContent = "\n".join(printLines) + sys.stderr.write(printContent) + sys.stderr.flush() + oldContentLen = newContentLen + lastOutput = time.time() + + if break_callback and break_callback(newContent): + callback_break = True + break + + if (time.time() - lastOutput) > 300: + sys.stderr.write(".") + sys.stderr.flush() + lastOutput = time.time() + + time.sleep(0.5) + + totalTime = time.time() - start + sys.stderr.write("\n") + + inf.seek(oldContentLen, 0) + newContent = inf.read() + sys.stderr.write(newContent) + sys.stderr.flush() + inf.seek(0, 0) + contents = inf.read() + inf.close() + + rv = [proc.returncode, contents] + if callback_break: + rv[0] = "CALLBACK_BREAK" + elif proc.poll() is None: + rv[0] = "TIMEOUT" + elif "Traceback (most recent call last)" in contents: + rv[0] = "EXCEPTION RAISED" + elif "internal error" in contents: + rv[0] = "INTERNAL ERROR" + + try: + for some_proc in psutil.Process(proc.pid).children(recursive=True) + [proc]: + some_proc.terminate() + except psutil.NoSuchProcess: + pass + + sys.stderr.write("\nDONE\n\n") + sys.stderr.write("TOTAL EXECUTION TIME: " + str(totalTime) + "\n") + sys.stderr.write("RETURN VALUE: " + str(proc.returncode) + "\n") + sys.stderr.write("RETURNING AS RESULT: " + str(rv[0]) + "\n") + sys.stderr.write("=" * 80 + "\n") + + if tmp_out_dir: + rmtree(tmp_out_dir, ignore_errors=True) + + return rv diff --git a/tests/test_arithmetic.py b/tests/test_arithmetic.py index 81b85939..862d0e97 100644 --- a/tests/test_arithmetic.py +++ b/tests/test_arithmetic.py @@ -1,17 +1,17 @@ -from __future__ import print_function -import logrun -import deepstate_base - - -class ArithmeticTest(deepstate_base.DeepStateTestCase): - def run_deepstate(self, deepstate): - (r, output) = logrun.logrun([deepstate, "build/examples/IntegerArithmetic", "--num_workers", "4"], - "deepstate.out", 1800) - self.assertEqual(r, 0) - - self.assertTrue("Failed: Arithmetic_InvertibleMultiplication_CanFail" in output) - self.assertTrue("Passed: Arithmetic_AdditionIsCommutative" in output) - self.assertFalse("Failed: Arithmetic_AdditionIsCommutative" in output) - self.assertTrue("Passed: Arithmetic_AdditionIsAssociative" in output) - self.assertFalse("Failed: Arithmetic_AdditionIsAssociative" in output) - self.assertTrue("Passed: Arithmetic_InvertibleMultiplication_CanFail" in output) +from __future__ import print_function +import logrun +import deepstate_base + + +class ArithmeticTest(deepstate_base.DeepStateTestCase): + def run_deepstate(self, deepstate): + (r, output) = logrun.logrun([deepstate, "build/examples/IntegerArithmetic", "--num_workers", "4"], + "deepstate.out", 1800) + self.assertEqual(r, 0) + + self.assertTrue("Failed: Arithmetic_InvertibleMultiplication_CanFail" in output) + self.assertTrue("Passed: Arithmetic_AdditionIsCommutative" in output) + self.assertFalse("Failed: Arithmetic_AdditionIsCommutative" in output) + self.assertTrue("Passed: Arithmetic_AdditionIsAssociative" in output) + self.assertFalse("Failed: Arithmetic_AdditionIsAssociative" in output) + self.assertTrue("Passed: Arithmetic_InvertibleMultiplication_CanFail" in output) diff --git a/tests/test_boringdisabled.py b/tests/test_boringdisabled.py index a390b9eb..b70c61c8 100644 --- a/tests/test_boringdisabled.py +++ b/tests/test_boringdisabled.py @@ -1,12 +1,12 @@ -from __future__ import print_function -import logrun -import deepstate_base - -class BoringDisabledTest(deepstate_base.DeepStateTestCase): - def run_deepstate(self, deepstate): - (r, output) = logrun.logrun([deepstate, "build/examples/BoringDisabled"], - "deepstate.out", 1800) - self.assertEqual(r, 0) - - self.assertTrue("Passed: CharTest_BoringVerifyCheck" in output) - self.assertTrue("Failed: CharTest_VerifyCheck" in output) +from __future__ import print_function +import logrun +import deepstate_base + +class BoringDisabledTest(deepstate_base.DeepStateTestCase): + def run_deepstate(self, deepstate): + (r, output) = logrun.logrun([deepstate, "build/examples/BoringDisabled"], + "deepstate.out", 1800) + self.assertEqual(r, 0) + + self.assertTrue("Passed: CharTest_BoringVerifyCheck" in output) + self.assertTrue("Failed: CharTest_VerifyCheck" in output) diff --git a/tests/test_crash.py b/tests/test_crash.py index f00b5e41..ac6643cf 100644 --- a/tests/test_crash.py +++ b/tests/test_crash.py @@ -1,18 +1,18 @@ -from __future__ import print_function -import deepstate_base -import logrun - - -class CrashTest(deepstate_base.DeepStateTestCase): - def run_deepstate(self, deepstate): - (r, output) = logrun.logrun([deepstate, "build/examples/Crash"], - "deepstate.out", 1800) - self.assertEqual(r, 0) - - self.assertTrue("Passed: Crash_SegFault" in output) - foundCrashSave = False - for line in output.split("\n"): - if ("Saved test case" in line) and (".crash" in line): - foundCrashSave = True - self.assertTrue(foundCrashSave) - +from __future__ import print_function +import deepstate_base +import logrun + + +class CrashTest(deepstate_base.DeepStateTestCase): + def run_deepstate(self, deepstate): + (r, output) = logrun.logrun([deepstate, "build/examples/Crash"], + "deepstate.out", 1800) + self.assertEqual(r, 0) + + self.assertTrue("Passed: Crash_SegFault" in output) + foundCrashSave = False + for line in output.split("\n"): + if ("Saved test case" in line) and (".crash" in line): + foundCrashSave = True + self.assertTrue(foundCrashSave) + diff --git a/tests/test_fixture.py b/tests/test_fixture.py index 4d47bac1..7c22b283 100644 --- a/tests/test_fixture.py +++ b/tests/test_fixture.py @@ -1,16 +1,16 @@ -from __future__ import print_function -import logrun -import deepstate_base - - -class FixtureTest(deepstate_base.DeepStateTestCase): - def run_deepstate(self, deepstate): - (r, output) = logrun.logrun([deepstate, "build/examples/Fixture"], - "deepstate.out", 1800) - self.assertEqual(r, 0) - - self.assertTrue("Passed: MyTest_Something" in output) - self.assertFalse("Failed: MyTest_Something" in output) - - self.assertTrue("Setting up!" in output) - self.assertTrue("Tearing down!" in output) +from __future__ import print_function +import logrun +import deepstate_base + + +class FixtureTest(deepstate_base.DeepStateTestCase): + def run_deepstate(self, deepstate): + (r, output) = logrun.logrun([deepstate, "build/examples/Fixture"], + "deepstate.out", 1800) + self.assertEqual(r, 0) + + self.assertTrue("Passed: MyTest_Something" in output) + self.assertFalse("Failed: MyTest_Something" in output) + + self.assertTrue("Setting up!" in output) + self.assertTrue("Tearing down!" in output) diff --git a/tests/test_fuzzers.py b/tests/test_fuzzers.py index 70b3e4d0..97b239c5 100644 --- a/tests/test_fuzzers.py +++ b/tests/test_fuzzers.py @@ -1,78 +1,78 @@ -from __future__ import print_function -import deepstate_base -import logrun -from tempfile import mkdtemp, TemporaryDirectory, mkstemp -from pathlib import Path -from os import path -from glob import glob -import re - - -class CrashFuzzerTest(deepstate_base.DeepStateFuzzerTestCase): - def run_deepstate(self, deepstate): - def do_compile(tempdir, test_source_file): - """ - Compile test_source_file using frontend API - tempdir is a workspace - """ - # prepare args - output_test_name = path.join(tempdir, Path(test_source_file).stem) - _, output_log_file = mkstemp(dir=tempdir) - arguments = [ - "--compile_test", test_source_file, - "--out_test_name", output_test_name - ] - - # run command - (r, output) = logrun.logrun([deepstate] + arguments, output_log_file, 360) - compiled_files = glob(output_test_name + '*') - - # check output - self.assertEqual(r, 0) - for compiled_file in compiled_files: - self.assertTrue(path.isfile(compiled_file)) - - # return compiled file(s) - # if Angora fuzzer, file.taint should be before file.fast - if any([compiled_file.endswith('.taint.angora') for compiled_file in compiled_files]): - compiled_files = sorted(compiled_files, reverse=True) - return compiled_files - - - def crash_found(output): - """ - Check if some crash were found assuming that - fuzzer output is the deepstate one (--fuzzer_out == False) - """ - for crashes_stat in re.finditer(r"^FUZZ_STATS:.*:unique_crashes:(\d+)$", - output, re.MULTILINE): - if int(crashes_stat.group(1)) > 0: - return True - return False - - - def do_fuzz(tempdir, compiled_files): - """ - Fuzz compiled_files (single compiled test/harness or two files if Angora) - until first crash - """ - # prepare args - _, output_log_file = mkstemp(dir=tempdir) - output_test_dir = mkdtemp(dir=tempdir) - - arguments = [ - "--output_test_dir", output_test_dir - ] + compiled_files - - # run command - (r, output) = logrun.logrun([deepstate] + arguments, output_log_file, - 180, break_callback=crash_found) - - # check output - self.assertTrue(crash_found(output)) - - - test_source_file = "examples/SimpleCrash.cpp" - with TemporaryDirectory(prefix="deepstate_test_fuzzers_") as tempdir: - compiled_files = do_compile(tempdir, test_source_file) - do_fuzz(tempdir, compiled_files) +from __future__ import print_function +import deepstate_base +import logrun +from tempfile import mkdtemp, TemporaryDirectory, mkstemp +from pathlib import Path +from os import path +from glob import glob +import re + + +class CrashFuzzerTest(deepstate_base.DeepStateFuzzerTestCase): + def run_deepstate(self, deepstate): + def do_compile(tempdir, test_source_file): + """ + Compile test_source_file using frontend API + tempdir is a workspace + """ + # prepare args + output_test_name = path.join(tempdir, Path(test_source_file).stem) + _, output_log_file = mkstemp(dir=tempdir) + arguments = [ + "--compile_test", test_source_file, + "--out_test_name", output_test_name + ] + + # run command + (r, output) = logrun.logrun([deepstate] + arguments, output_log_file, 360) + compiled_files = glob(output_test_name + '*') + + # check output + self.assertEqual(r, 0) + for compiled_file in compiled_files: + self.assertTrue(path.isfile(compiled_file)) + + # return compiled file(s) + # if Angora fuzzer, file.taint should be before file.fast + if any([compiled_file.endswith('.taint.angora') for compiled_file in compiled_files]): + compiled_files = sorted(compiled_files, reverse=True) + return compiled_files + + + def crash_found(output): + """ + Check if some crash were found assuming that + fuzzer output is the deepstate one (--fuzzer_out == False) + """ + for crashes_stat in re.finditer(r"^FUZZ_STATS:.*:unique_crashes:(\d+)$", + output, re.MULTILINE): + if int(crashes_stat.group(1)) > 0: + return True + return False + + + def do_fuzz(tempdir, compiled_files): + """ + Fuzz compiled_files (single compiled test/harness or two files if Angora) + until first crash + """ + # prepare args + _, output_log_file = mkstemp(dir=tempdir) + output_test_dir = mkdtemp(dir=tempdir) + + arguments = [ + "--output_test_dir", output_test_dir + ] + compiled_files + + # run command + (r, output) = logrun.logrun([deepstate] + arguments, output_log_file, + 180, break_callback=crash_found) + + # check output + self.assertTrue(crash_found(output)) + + + test_source_file = "examples/SimpleCrash.cpp" + with TemporaryDirectory(prefix="deepstate_test_fuzzers_") as tempdir: + compiled_files = do_compile(tempdir, test_source_file) + do_fuzz(tempdir, compiled_files) diff --git a/tests/test_fuzzers_sync.py b/tests/test_fuzzers_sync.py index 7f378d6c..6ba28304 100644 --- a/tests/test_fuzzers_sync.py +++ b/tests/test_fuzzers_sync.py @@ -1,258 +1,258 @@ -from __future__ import print_function - -import os -import re -import subprocess -import sys -import time - -from base64 import b64decode -from glob import glob -from os import path -from pathlib import Path -from shutil import rmtree -from tempfile import mkdtemp -from tempfile import mkstemp -from time import sleep -from unittest import TestCase - -import psutil - - -class CrashFuzzerTest(TestCase): - def test_fuzzers_synchronization(self): - def do_compile(fuzzer, tempdir, test_source_file): - """ - Compile test_source_file using frontend API - temdir is a workspace - """ - print(f"Compiling testcase for fuzzer {fuzzer}") - - # prepare args - output_test_name = path.join(tempdir, Path(test_source_file).stem) - _, output_log_file = mkstemp(dir=tempdir) - arguments = [ - "--compile_test", test_source_file, - "--out_test_name", output_test_name - ] - - # run command - proc = subprocess.Popen([f"deepstate-{fuzzer}"] + arguments) - proc.communicate() - compiled_files = glob(output_test_name + f"*.{fuzzer}") - - # check output - self.assertEqual(proc.returncode, 0) - for compiled_file in compiled_files: - self.assertTrue(path.isfile(compiled_file)) - - # return compiled file(s) - # if Angora fuzzer, file.taint should be before file.fast - if any([compiled_file.endswith('.taint.angora') for compiled_file in compiled_files]): - compiled_files = sorted(compiled_files, reverse=True) - return compiled_files - - - def do_fuzz(fuzzer, workspace_dir, sync_dir, compiled_files, output_from_fuzzer=None): - """ - Fuzz compiled_files (single compiled test/harness or two files if Angora) - until first crash - """ - # prepare args - output_dir = mkdtemp(prefix=f"deepstate_{fuzzer}_", dir=workspace_dir) - - arguments = [ - "--output_test_dir", output_dir, - "--sync_dir", sync_dir, - "--sync_cycle", "5", - "--min_log_level", "0" - ] + compiled_files - - # run command - exe = f"deepstate-{fuzzer}" - cmd = ' '.join([exe] + arguments) - print(f"Running: `{cmd}`.") - if output_from_fuzzer and output_from_fuzzer == fuzzer: - proc = subprocess.Popen([exe] + arguments) - else: - proc = subprocess.Popen([exe] + arguments, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - return output_dir, proc - - - def crashes_found(fuzzer, output): - """ - Check if some crash were found assuming that - fuzzer output is the deepstate one (--fuzzer_out == False) - """ - no_crashes = 0 - for crashes_stat in re.finditer(r"^unique_crashes:(\d+)$", - output, re.MULTILINE): - no_crashes = int(crashes_stat.group(1)) - print(f"Crashes found by fuzzer {fuzzer} - {no_crashes}.") - return 0 - - - def wait_for_crashes(fuzzers, timeout): - for fuzzer in fuzzers: - fuzzers[fuzzer]["no_crashes"] = 0 - - start_time = int(time.time()) - - while any([v["no_crashes"] < 1 for _, v in fuzzers.items()]): - if timeout: - self.assertLess(time.time() - start_time, timeout, msg="TIMEOUT") - - for fuzzer, values in fuzzers.items(): - try: - stats = dict() - with open(values["stats_file"], "r") as f: - for line in f: - line = line.strip() - if ":" not in line: - continue - k, v = line.split(":", 1) - stats[k] = v - - print("{:10s}:".format(fuzzer), end="\t") - if values["proc"].poll() is None: - for stat in ["unique_crashes", "sync_dir_size", "execs_done", "paths_total"]: - if stat in stats: - print("{}: {:10s}".format(stat, stats[stat]), end=" |\t") - print("") - fuzzers[fuzzer]["no_crashes"] = int(stats["unique_crashes"]) - else: - if "unique_crashes" in stats: - print("unique_crashes: {:10s}".format(stats["unique_crashes"]), end=" |\t") - print("DEAD " + "OoOoo"*5 + "x...") - - except FileNotFoundError: - print(f" - stats not found (`{values['stats_file']}`).") - - for _ in range(3): - print("~*~"*5, end=" - ") - sys.stderr.flush() - sys.stdout.flush() - sleep(1) - print("") - - print("CRASHING - done") - print("-"*50) - - - def do_sync_test(output_from_fuzzer=None): - # start all fuzzers - for fuzzer in fuzzers.keys(): - output_dir, proc = do_fuzz(fuzzer, workspace_dir, sync_dir, - fuzzers[fuzzer]["compiled_files"], - output_from_fuzzer) - fuzzers[fuzzer]["output_dir"] = output_dir - fuzzers[fuzzer]["proc"] = proc - fuzzers[fuzzer]["stats_file"] = os.path.join(output_dir, "deepstate-stats.txt") - - # import Frontend classes so we can use PUSH/PULL/CRASH dirs - deepstate_python = os.path.join(os.path.dirname(__file__), "bin", "deepstate") - print(f"Adding deepstate python path: {deepstate_python}.") - sys.path.append(deepstate_python) - - if "afl" in fuzzers: - from deepstate.executors.fuzz.afl import AFL - fuzzers["afl"]["class"] = AFL - if "angora" in fuzzers: - from deepstate.executors.fuzz.angora import Angora - fuzzers["angora"]["class"] = Angora - if "honggfuzz" in fuzzers: - from deepstate.executors.fuzz.honggfuzz import Honggfuzz - fuzzers["honggfuzz"]["class"] = Honggfuzz - if "eclipser" in fuzzers: - from deepstate.executors.fuzz.eclipser import Eclipser - fuzzers["eclipser"]["class"] = Eclipser - if "libfuzzer" in fuzzers: - from deepstate.executors.fuzz.libfuzzer import LibFuzzer - fuzzers["libfuzzer"]["class"] = LibFuzzer - - # run them for a bit - wait_for_start = 2 - print(f"Fuzzers started, waiting {wait_for_start} seconds.") - for _ in range(wait_for_start): - sleep(1) - print('.', end="") - sys.stderr.flush() - sys.stdout.flush() - print("") - - # assert that all fuzzers started - print("Checking if fuzzers are up and running") - for fuzzer, values in fuzzers.items(): - try: - self.assertTrue(values["proc"].poll() is None) - except Exception as e: - print(f"Error for fuzzer {fuzzer}:") - if values["proc"] and values["proc"].stderr: - print(values["proc"].stderr.read().decode('utf8')) - raise e - push_dir = os.path.join(values["output_dir"], values["class"].PUSH_DIR) - self.assertTrue(os.path.isdir(push_dir)) - - # manually push crashing seeds to fuzzers local dirs - seeds = [b64decode("R3JvcyBwemRyIGZyb20gUEwu")] - fuzzer_id = 0 - for seed_no, seed in enumerate(seeds): - fuzzer_id %= len(fuzzers) - fuzzer = sorted(fuzzers.keys())[fuzzer_id] - values = fuzzers[fuzzer] - push_dir = os.path.join(values["output_dir"], values["class"].PUSH_DIR) - print(f"Pushing seed {seed_no} to {fuzzer}: `{push_dir}`") - with open(os.path.join(push_dir, "id:000201,the_crash"), "wb") as f: - f.write(seed) - fuzzer_id += 1 - - # check if all fuzzers find at least two crashes - # that is: the one pushed to its local dir and at least one other - wait_for_crashes(fuzzers, timeout) - - - # config - fuzzers_list = ["afl", "libfuzzer", "angora", "eclipser", "honggfuzz"] - output_from_fuzzer = None # or "afl" etc - timeout = None - - # init - fuzzers = dict() - test_source_file = "examples/EnsembledCrash.cpp" - sync_dir = mkdtemp(prefix="syncing_") - workspace_dir = mkdtemp(prefix="workspace_") - compiled_files_dir = mkdtemp(prefix="compiled_", dir=workspace_dir) - - # compile for all fuzzers - for fuzzer in fuzzers_list: - compiled_files = do_compile(fuzzer, compiled_files_dir, test_source_file) - fuzzers[fuzzer] = {"compiled_files": compiled_files} - - # do testing - try: - print("Starting synchronization run") - do_sync_test(output_from_fuzzer) - except Exception as e: - # cleanup - # hard kill processes - print('Killing spawned processes.') - for _, value in fuzzers.items(): - try: - proc = value["proc"] - for some_proc in psutil.Process(proc.pid).children(recursive=True) + [proc]: - some_proc.kill() - except: - pass - - # filesystem - print("Clearing tmp files.") - try: - sleep(1) - rmtree(workspace_dir, ignore_errors=True) - rmtree(sync_dir, ignore_errors=True) - except Exception as e2: - print(f"Error clearing: {e2}") - - # now can raise - raise e +from __future__ import print_function + +import os +import re +import subprocess +import sys +import time + +from base64 import b64decode +from glob import glob +from os import path +from pathlib import Path +from shutil import rmtree +from tempfile import mkdtemp +from tempfile import mkstemp +from time import sleep +from unittest import TestCase + +import psutil + + +class CrashFuzzerTest(TestCase): + def test_fuzzers_synchronization(self): + def do_compile(fuzzer, tempdir, test_source_file): + """ + Compile test_source_file using frontend API + temdir is a workspace + """ + print(f"Compiling testcase for fuzzer {fuzzer}") + + # prepare args + output_test_name = path.join(tempdir, Path(test_source_file).stem) + _, output_log_file = mkstemp(dir=tempdir) + arguments = [ + "--compile_test", test_source_file, + "--out_test_name", output_test_name + ] + + # run command + proc = subprocess.Popen([f"deepstate-{fuzzer}"] + arguments) + proc.communicate() + compiled_files = glob(output_test_name + f"*.{fuzzer}") + + # check output + self.assertEqual(proc.returncode, 0) + for compiled_file in compiled_files: + self.assertTrue(path.isfile(compiled_file)) + + # return compiled file(s) + # if Angora fuzzer, file.taint should be before file.fast + if any([compiled_file.endswith('.taint.angora') for compiled_file in compiled_files]): + compiled_files = sorted(compiled_files, reverse=True) + return compiled_files + + + def do_fuzz(fuzzer, workspace_dir, sync_dir, compiled_files, output_from_fuzzer=None): + """ + Fuzz compiled_files (single compiled test/harness or two files if Angora) + until first crash + """ + # prepare args + output_dir = mkdtemp(prefix=f"deepstate_{fuzzer}_", dir=workspace_dir) + + arguments = [ + "--output_test_dir", output_dir, + "--sync_dir", sync_dir, + "--sync_cycle", "5", + "--min_log_level", "0" + ] + compiled_files + + # run command + exe = f"deepstate-{fuzzer}" + cmd = ' '.join([exe] + arguments) + print(f"Running: `{cmd}`.") + if output_from_fuzzer and output_from_fuzzer == fuzzer: + proc = subprocess.Popen([exe] + arguments) + else: + proc = subprocess.Popen([exe] + arguments, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return output_dir, proc + + + def crashes_found(fuzzer, output): + """ + Check if some crash were found assuming that + fuzzer output is the deepstate one (--fuzzer_out == False) + """ + no_crashes = 0 + for crashes_stat in re.finditer(r"^unique_crashes:(\d+)$", + output, re.MULTILINE): + no_crashes = int(crashes_stat.group(1)) + print(f"Crashes found by fuzzer {fuzzer} - {no_crashes}.") + return 0 + + + def wait_for_crashes(fuzzers, timeout): + for fuzzer in fuzzers: + fuzzers[fuzzer]["no_crashes"] = 0 + + start_time = int(time.time()) + + while any([v["no_crashes"] < 1 for _, v in fuzzers.items()]): + if timeout: + self.assertLess(time.time() - start_time, timeout, msg="TIMEOUT") + + for fuzzer, values in fuzzers.items(): + try: + stats = dict() + with open(values["stats_file"], "r") as f: + for line in f: + line = line.strip() + if ":" not in line: + continue + k, v = line.split(":", 1) + stats[k] = v + + print("{:10s}:".format(fuzzer), end="\t") + if values["proc"].poll() is None: + for stat in ["unique_crashes", "sync_dir_size", "execs_done", "paths_total"]: + if stat in stats: + print("{}: {:10s}".format(stat, stats[stat]), end=" |\t") + print("") + fuzzers[fuzzer]["no_crashes"] = int(stats["unique_crashes"]) + else: + if "unique_crashes" in stats: + print("unique_crashes: {:10s}".format(stats["unique_crashes"]), end=" |\t") + print("DEAD " + "OoOoo"*5 + "x...") + + except FileNotFoundError: + print(f" - stats not found (`{values['stats_file']}`).") + + for _ in range(3): + print("~*~"*5, end=" - ") + sys.stderr.flush() + sys.stdout.flush() + sleep(1) + print("") + + print("CRASHING - done") + print("-"*50) + + + def do_sync_test(output_from_fuzzer=None): + # start all fuzzers + for fuzzer in fuzzers.keys(): + output_dir, proc = do_fuzz(fuzzer, workspace_dir, sync_dir, + fuzzers[fuzzer]["compiled_files"], + output_from_fuzzer) + fuzzers[fuzzer]["output_dir"] = output_dir + fuzzers[fuzzer]["proc"] = proc + fuzzers[fuzzer]["stats_file"] = os.path.join(output_dir, "deepstate-stats.txt") + + # import Frontend classes so we can use PUSH/PULL/CRASH dirs + deepstate_python = os.path.join(os.path.dirname(__file__), "bin", "deepstate") + print(f"Adding deepstate python path: {deepstate_python}.") + sys.path.append(deepstate_python) + + if "afl" in fuzzers: + from deepstate.executors.fuzz.afl import AFL + fuzzers["afl"]["class"] = AFL + if "angora" in fuzzers: + from deepstate.executors.fuzz.angora import Angora + fuzzers["angora"]["class"] = Angora + if "honggfuzz" in fuzzers: + from deepstate.executors.fuzz.honggfuzz import Honggfuzz + fuzzers["honggfuzz"]["class"] = Honggfuzz + if "eclipser" in fuzzers: + from deepstate.executors.fuzz.eclipser import Eclipser + fuzzers["eclipser"]["class"] = Eclipser + if "libfuzzer" in fuzzers: + from deepstate.executors.fuzz.libfuzzer import LibFuzzer + fuzzers["libfuzzer"]["class"] = LibFuzzer + + # run them for a bit + wait_for_start = 2 + print(f"Fuzzers started, waiting {wait_for_start} seconds.") + for _ in range(wait_for_start): + sleep(1) + print('.', end="") + sys.stderr.flush() + sys.stdout.flush() + print("") + + # assert that all fuzzers started + print("Checking if fuzzers are up and running") + for fuzzer, values in fuzzers.items(): + try: + self.assertTrue(values["proc"].poll() is None) + except Exception as e: + print(f"Error for fuzzer {fuzzer}:") + if values["proc"] and values["proc"].stderr: + print(values["proc"].stderr.read().decode('utf8')) + raise e + push_dir = os.path.join(values["output_dir"], values["class"].PUSH_DIR) + self.assertTrue(os.path.isdir(push_dir)) + + # manually push crashing seeds to fuzzers local dirs + seeds = [b64decode("R3JvcyBwemRyIGZyb20gUEwu")] + fuzzer_id = 0 + for seed_no, seed in enumerate(seeds): + fuzzer_id %= len(fuzzers) + fuzzer = sorted(fuzzers.keys())[fuzzer_id] + values = fuzzers[fuzzer] + push_dir = os.path.join(values["output_dir"], values["class"].PUSH_DIR) + print(f"Pushing seed {seed_no} to {fuzzer}: `{push_dir}`") + with open(os.path.join(push_dir, "id:000201,the_crash"), "wb") as f: + f.write(seed) + fuzzer_id += 1 + + # check if all fuzzers find at least two crashes + # that is: the one pushed to its local dir and at least one other + wait_for_crashes(fuzzers, timeout) + + + # config + fuzzers_list = ["afl", "libfuzzer", "angora", "eclipser", "honggfuzz"] + output_from_fuzzer = None # or "afl" etc + timeout = None + + # init + fuzzers = dict() + test_source_file = "examples/EnsembledCrash.cpp" + sync_dir = mkdtemp(prefix="syncing_") + workspace_dir = mkdtemp(prefix="workspace_") + compiled_files_dir = mkdtemp(prefix="compiled_", dir=workspace_dir) + + # compile for all fuzzers + for fuzzer in fuzzers_list: + compiled_files = do_compile(fuzzer, compiled_files_dir, test_source_file) + fuzzers[fuzzer] = {"compiled_files": compiled_files} + + # do testing + try: + print("Starting synchronization run") + do_sync_test(output_from_fuzzer) + except Exception as e: + # cleanup + # hard kill processes + print('Killing spawned processes.') + for _, value in fuzzers.items(): + try: + proc = value["proc"] + for some_proc in psutil.Process(proc.pid).children(recursive=True) + [proc]: + some_proc.kill() + except: + pass + + # filesystem + print("Clearing tmp files.") + try: + sleep(1) + rmtree(workspace_dir, ignore_errors=True) + rmtree(sync_dir, ignore_errors=True) + except Exception as e2: + print(f"Error clearing: {e2}") + + # now can raise + raise e diff --git a/tests/test_klee.py b/tests/test_klee.py index 6c030afd..5ac4a6ca 100644 --- a/tests/test_klee.py +++ b/tests/test_klee.py @@ -1,14 +1,14 @@ -from __future__ import print_function -import deepstate_base -import logrun - - -class KleeTest(deepstate_base.DeepStateTestCase): - def run_deepstate(self, deepstate): - (r, output) = logrun.logrun([deepstate, "build/examples/Klee", "--klee"], - "deepstate.out", 1800) - self.assertEqual(r, 0) - - self.assertTrue("zero" in output) - self.assertTrue("positive" in output) - self.assertTrue("negative" in output) +from __future__ import print_function +import deepstate_base +import logrun + + +class KleeTest(deepstate_base.DeepStateTestCase): + def run_deepstate(self, deepstate): + (r, output) = logrun.logrun([deepstate, "build/examples/Klee", "--klee"], + "deepstate.out", 1800) + self.assertEqual(r, 0) + + self.assertTrue("zero" in output) + self.assertTrue("positive" in output) + self.assertTrue("negative" in output) diff --git a/tests/test_lists.py b/tests/test_lists.py index 2c0b9db6..1fc64edc 100644 --- a/tests/test_lists.py +++ b/tests/test_lists.py @@ -1,15 +1,15 @@ -from __future__ import print_function -import logrun -import deepstate_base - - -class ListsTest(deepstate_base.DeepStateTestCase): - def run_deepstate(self, deepstate): - if deepstate == "deepstate-manticore": - return # Just skip for now, we know it's too slow - (r, output) = logrun.logrun([deepstate, "build/examples/Lists"], - "deepstate.out", 3000) - self.assertEqual(r, 0) - - self.assertTrue("Passed: Vector_DoubleReversal" in output) - self.assertFalse("Failed: Vector_DoubleReversal" in output) +from __future__ import print_function +import logrun +import deepstate_base + + +class ListsTest(deepstate_base.DeepStateTestCase): + def run_deepstate(self, deepstate): + if deepstate == "deepstate-manticore": + return # Just skip for now, we know it's too slow + (r, output) = logrun.logrun([deepstate, "build/examples/Lists"], + "deepstate.out", 3000) + self.assertEqual(r, 0) + + self.assertTrue("Passed: Vector_DoubleReversal" in output) + self.assertFalse("Failed: Vector_DoubleReversal" in output) diff --git a/tests/test_oneof.py b/tests/test_oneof.py index eae7b892..fada7502 100644 --- a/tests/test_oneof.py +++ b/tests/test_oneof.py @@ -1,16 +1,16 @@ -from __future__ import print_function -import logrun -import deepstate_base - - -class OneOfTest(deepstate_base.DeepStateTestCase): - def run_deepstate(self, deepstate): - if deepstate == "deepstate-manticore": - return # Just skip for now, we know it fails (#174) - - (r, output) = logrun.logrun([deepstate, "build/examples/OneOf"], - "deepstate.out", 1800) - self.assertEqual(r, 0) - - self.assertTrue("Failed: OneOfExample_ProduceSixtyOrHigher" in output) - self.assertTrue("Passed: OneOfExample_ProduceSixtyOrHigher" in output) +from __future__ import print_function +import logrun +import deepstate_base + + +class OneOfTest(deepstate_base.DeepStateTestCase): + def run_deepstate(self, deepstate): + if deepstate == "deepstate-manticore": + return # Just skip for now, we know it fails (#174) + + (r, output) = logrun.logrun([deepstate, "build/examples/OneOf"], + "deepstate.out", 1800) + self.assertEqual(r, 0) + + self.assertTrue("Failed: OneOfExample_ProduceSixtyOrHigher" in output) + self.assertTrue("Passed: OneOfExample_ProduceSixtyOrHigher" in output) diff --git a/tests/test_overflow.py b/tests/test_overflow.py index 935531e1..cf973e29 100644 --- a/tests/test_overflow.py +++ b/tests/test_overflow.py @@ -1,15 +1,15 @@ -from __future__ import print_function -import logrun -import deepstate_base - - -class OverflowTest(deepstate_base.DeepStateTestCase): - def run_deepstate(self, deepstate): - (r, output) = logrun.logrun([deepstate, "--timeout", "15", "build/examples/IntegerOverflow"], - "deepstate.out", 1800) - self.assertEqual(r, 0) - - self.assertTrue("Failed: SignedInteger_AdditionOverflow" in output) - self.assertTrue("Passed: SignedInteger_AdditionOverflow" in output) - self.assertTrue("Failed: SignedInteger_MultiplicationOverflow" in output) - self.assertTrue("Passed: SignedInteger_MultiplicationOverflow" in output) +from __future__ import print_function +import logrun +import deepstate_base + + +class OverflowTest(deepstate_base.DeepStateTestCase): + def run_deepstate(self, deepstate): + (r, output) = logrun.logrun([deepstate, "--timeout", "15", "build/examples/IntegerOverflow"], + "deepstate.out", 1800) + self.assertEqual(r, 0) + + self.assertTrue("Failed: SignedInteger_AdditionOverflow" in output) + self.assertTrue("Passed: SignedInteger_AdditionOverflow" in output) + self.assertTrue("Failed: SignedInteger_MultiplicationOverflow" in output) + self.assertTrue("Passed: SignedInteger_MultiplicationOverflow" in output) diff --git a/tests/test_primes.py b/tests/test_primes.py index fa37bbb3..d0e0d6d6 100644 --- a/tests/test_primes.py +++ b/tests/test_primes.py @@ -1,17 +1,17 @@ -from __future__ import print_function -import logrun -import deepstate_base - - -class PrimesTest(deepstate_base.DeepStateTestCase): - def run_deepstate(self, deepstate): - (r, output) = logrun.logrun([deepstate, "--timeout", "15", "build/examples/Primes"], - "deepstate.out", 1800) - self.assertEqual(r, 0) - - self.assertTrue("Failed: PrimePolynomial_OnlyGeneratesPrimes" in output) - self.assertTrue("Failed: PrimePolynomial_OnlyGeneratesPrimes_NoStreaming" in output) - - # TODO(alan): determine why passed cases aren't being logged to stdout - #self.assertTrue("Passed: PrimePolynomial_OnlyGeneratesPrimes" in output) - #self.assertTrue("Passed: PrimePolynomial_OnlyGeneratesPrimes_NoStreaming" in output) +from __future__ import print_function +import logrun +import deepstate_base + + +class PrimesTest(deepstate_base.DeepStateTestCase): + def run_deepstate(self, deepstate): + (r, output) = logrun.logrun([deepstate, "--timeout", "15", "build/examples/Primes"], + "deepstate.out", 1800) + self.assertEqual(r, 0) + + self.assertTrue("Failed: PrimePolynomial_OnlyGeneratesPrimes" in output) + self.assertTrue("Failed: PrimePolynomial_OnlyGeneratesPrimes_NoStreaming" in output) + + # TODO(alan): determine why passed cases aren't being logged to stdout + #self.assertTrue("Passed: PrimePolynomial_OnlyGeneratesPrimes" in output) + #self.assertTrue("Passed: PrimePolynomial_OnlyGeneratesPrimes_NoStreaming" in output) diff --git a/tests/test_runlen.py b/tests/test_runlen.py index 7a7e3e99..4233d052 100644 --- a/tests/test_runlen.py +++ b/tests/test_runlen.py @@ -1,20 +1,20 @@ -from __future__ import print_function -import deepstate_base -import logrun - - -class RunlenTest(deepstate_base.DeepStateTestCase): - def run_deepstate(self, deepstate): - if deepstate == "deepstate-manticore": - return # Just skip for now, we know it's too slow - (r, output) = logrun.logrun([deepstate, "build/examples/Runlen"], - "deepstate.out", 2900) - self.assertEqual(r, 0) - - self.assertTrue("Passed: Runlength_EncodeDecode" in output) - foundFailSave = False - for line in output.split("\n"): - if ("Saved test case" in line) and (".fail" in line): - foundFailSave = True - self.assertTrue(foundFailSave) - +from __future__ import print_function +import deepstate_base +import logrun + + +class RunlenTest(deepstate_base.DeepStateTestCase): + def run_deepstate(self, deepstate): + if deepstate == "deepstate-manticore": + return # Just skip for now, we know it's too slow + (r, output) = logrun.logrun([deepstate, "build/examples/Runlen"], + "deepstate.out", 2900) + self.assertEqual(r, 0) + + self.assertTrue("Passed: Runlength_EncodeDecode" in output) + foundFailSave = False + for line in output.split("\n"): + if ("Saved test case" in line) and (".fail" in line): + foundFailSave = True + self.assertTrue(foundFailSave) + diff --git a/tests/test_sanity_check.py b/tests/test_sanity_check.py index ae01bc79..98df81a9 100644 --- a/tests/test_sanity_check.py +++ b/tests/test_sanity_check.py @@ -1,35 +1,35 @@ -from __future__ import print_function -import os -import deepstate_base -import logrun - - -class SanityCheck(deepstate_base.DeepStateTestCase): - def run_deepstate(self, deepstate): - if deepstate != "--fuzz": - return - os.mkdir("OneOf_out") - (r, output) = logrun.logrun(["build/examples/OneOf", - "--fuzz", - "--timeout", "2", - "--no_fork", - "--output_test_dir", "OneOf_out", - "--min_log_level", "2", - ], - "deepstate.out", 1800, - filters=["Assumption", "Abandoned", "CRITICAL", "ERROR", "Saved"]) - - self.assertTrue("Failed: OneOfExample_ProduceSixtyOrHigher" in output) - self.assertTrue("Saved test case in file" in output) - foundFinish = False - for line in output.split("\n"): - if "Done fuzzing!" in line: - foundFinish = True - perSecond = int(line.split(" tests/second")[0].split("(")[1]) - self.assertTrue(perSecond > 20000) - passed = int(line.split(" passed/")[0].split(" failed/")[1]) - self.assertTrue(passed > 1000) - failed = int(line.split(" failed/")[0].split("with ")[1]) - self.assertTrue(failed > 1000) - self.assertTrue(foundFinish) - +from __future__ import print_function +import os +import deepstate_base +import logrun + + +class SanityCheck(deepstate_base.DeepStateTestCase): + def run_deepstate(self, deepstate): + if deepstate != "--fuzz": + return + os.mkdir("OneOf_out") + (r, output) = logrun.logrun(["build/examples/OneOf", + "--fuzz", + "--timeout", "2", + "--no_fork", + "--output_test_dir", "OneOf_out", + "--min_log_level", "2", + ], + "deepstate.out", 1800, + filters=["Assumption", "Abandoned", "CRITICAL", "ERROR", "Saved"]) + + self.assertTrue("Failed: OneOfExample_ProduceSixtyOrHigher" in output) + self.assertTrue("Saved test case in file" in output) + foundFinish = False + for line in output.split("\n"): + if "Done fuzzing!" in line: + foundFinish = True + perSecond = int(line.split(" tests/second")[0].split("(")[1]) + self.assertTrue(perSecond > 20000) + passed = int(line.split(" passed/")[0].split(" failed/")[1]) + self.assertTrue(passed > 1000) + failed = int(line.split(" failed/")[0].split("with ")[1]) + self.assertTrue(failed > 1000) + self.assertTrue(foundFinish) + diff --git a/tests/test_streamingandformatting.py b/tests/test_streamingandformatting.py index 9c2d889e..c9430611 100644 --- a/tests/test_streamingandformatting.py +++ b/tests/test_streamingandformatting.py @@ -1,26 +1,26 @@ -from __future__ import print_function -import logrun -import deepstate_base - - -class StreamingAndFormattingTest(deepstate_base.DeepStateTestCase): - def run_deepstate(self, deepstate): - (r, output) = logrun.logrun([deepstate, "build/examples/StreamingAndFormatting"], - "deepstate.out", 1800) - self.assertEqual(r, 0) - - self.assertTrue("Failed: Streaming_BasicLevels" in output) - self.assertTrue("This is a debug message" in output) - self.assertTrue("This is a trace message" in output) - self.assertTrue("This is an info message" in output) - self.assertTrue("This is a warning message" in output) - self.assertTrue("This is a error message" in output) - self.assertTrue("This is a trace message again" in output) - self.assertTrue(": 97" in output) - self.assertTrue(": 1" in output) - self.assertTrue(": 1.000000" in output) - self.assertTrue(": string" in output) - self.assertTrue("hello string=world" in output) - self.assertTrue("hello again!" in output) - self.assertTrue("Passed: Formatting_OverridePrintf" in output) - self.assertFalse("Failed: Formatting_OverridePrintf" in output) +from __future__ import print_function +import logrun +import deepstate_base + + +class StreamingAndFormattingTest(deepstate_base.DeepStateTestCase): + def run_deepstate(self, deepstate): + (r, output) = logrun.logrun([deepstate, "build/examples/StreamingAndFormatting"], + "deepstate.out", 1800) + self.assertEqual(r, 0) + + self.assertTrue("Failed: Streaming_BasicLevels" in output) + self.assertTrue("This is a debug message" in output) + self.assertTrue("This is a trace message" in output) + self.assertTrue("This is an info message" in output) + self.assertTrue("This is a warning message" in output) + self.assertTrue("This is a error message" in output) + self.assertTrue("This is a trace message again" in output) + self.assertTrue(": 97" in output) + self.assertTrue(": 1" in output) + self.assertTrue(": 1.000000" in output) + self.assertTrue(": string" in output) + self.assertTrue("hello string=world" in output) + self.assertTrue("hello again!" in output) + self.assertTrue("Passed: Formatting_OverridePrintf" in output) + self.assertFalse("Failed: Formatting_OverridePrintf" in output) diff --git a/tests/test_takeover.py b/tests/test_takeover.py index e824f54d..944f456b 100644 --- a/tests/test_takeover.py +++ b/tests/test_takeover.py @@ -1,20 +1,20 @@ -from __future__ import print_function -import logrun -import deepstate_base - - -class TakeOverTest(deepstate_base.DeepStateTestCase): - def run_deepstate(self, deepstate): - (r, output) = logrun.logrun([deepstate, "build/examples/TakeOver", "--take_over"], - "deepstate.out", 1800) - self.assertEqual(r, 0) - - self.assertTrue("hi" in output) - self.assertTrue("bye" in output) - self.assertTrue("was not greater than" in output) - - foundPassSave = False - for line in output.split("\n"): - if ("Saved test case" in line) and (".pass" in line): - foundPassSave = True - self.assertTrue(foundPassSave) +from __future__ import print_function +import logrun +import deepstate_base + + +class TakeOverTest(deepstate_base.DeepStateTestCase): + def run_deepstate(self, deepstate): + (r, output) = logrun.logrun([deepstate, "build/examples/TakeOver", "--take_over"], + "deepstate.out", 1800) + self.assertEqual(r, 0) + + self.assertTrue("hi" in output) + self.assertTrue("bye" in output) + self.assertTrue("was not greater than" in output) + + foundPassSave = False + for line in output.split("\n"): + if ("Saved test case" in line) and (".pass" in line): + foundPassSave = True + self.assertTrue(foundPassSave)