Skip to content

Adds a fuzz harness and oss-fuzz build configuration #1877

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,11 @@ conanbuildinfo.txt
conaninfo.txt
graph_info.json
build/*

# Fuzzing artifacts
crash-*
leak-*
oom-*

# Clangd files
.clangd
4 changes: 3 additions & 1 deletion include/PrintFeature.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ enum class PrintFeatureType: unsigned char
MoveRetraction = 9,
SupportInterface = 10,
PrimeTower = 11,
NumPrintFeatureTypes = 12 // this number MUST be the last one because other modules will
NumPrintFeatureTypes = 12, // this number MUST be the last one because other modules will
// use this symbol to get the total number of types, which can
// be used to create an array or so
// Internal use only. Used for fuzzing.
kMaxValue = NumPrintFeatureTypes,
};


Expand Down
6 changes: 6 additions & 0 deletions include/settings/EnumSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,13 @@ enum class EGCodeFlavor
* Real RepRap GCode suitable for printers using RepRap firmware (e.g. Duet controllers)
**/
REPRAP = 8,

PLUGIN = 9,

/**
* For internal fuzz-testing use only.
**/
kMaxValue = PLUGIN,
};

/*!
Expand Down
55 changes: 55 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

message(STATUS "Building tests...")
include(GoogleTest)
include(CheckCXXCompilerFlag)
include(CMakeDependentOption)

set(TESTS_SRC_BASE
ClipperTest
Expand Down Expand Up @@ -40,6 +42,28 @@ set(TESTS_SRC_UTILS
UnionFindTest
)

set(TESTS_HELPERS_SRC ReadTestPolygons.cpp)

set(TESTS_SRC_ARCUS)
if (ENABLE_ARCUS)
list(APPEND TESTS_SRC_ARCUS
ArcusCommunicationTest
ArcusCommunicationPrivateTest)
list(APPEND TESTS_HELPERS_SRC
arcus/MockSocket.cpp
arcus/MockSocket.h
arcus/MockCommunication.h
)
endif ()

add_library(test_helpers ${TESTS_HELPERS_SRC})
target_compile_definitions(test_helpers PUBLIC $<$<BOOL:${BUILD_TESTING}>:BUILD_TESTS> $<$<BOOL:${ENABLE_ARCUS}>:ARCUS>)
target_include_directories(test_helpers PUBLIC "../include")
target_link_libraries(test_helpers PRIVATE _CuraEngine GTest::gtest GTest::gmock clipper::clipper)
if (ENABLE_ARCUS)
target_link_libraries(test_helpers PUBLIC arcus::arcus protobuf::libprotobuf)
endif ()

foreach (test ${TESTS_SRC_BASE})
add_executable(${test} main.cpp ${test}.cpp)
add_test(NAME ${test} COMMAND "${test}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
Expand Down Expand Up @@ -70,3 +94,34 @@ foreach (test ${TESTS_SRC_UTILS})
add_test(NAME ${test} COMMAND "${test}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
target_link_libraries(${test} PRIVATE _CuraEngine test_helpers GTest::gtest GTest::gmock clipper::clipper)
endforeach ()


# Ensure that basic sanitizer flags are supported before adding fuzzers;
# - Address sanitizer is often used in conjunction with fuzzing as it will detect
# common high severity bugs. This sanitizer is used as a "default" for fuzzing
# when the sanitizer isn't otherwise specified.
# - Fuzzer sanitizer will link against libfuzzer and is currently only supported
# on clang/msvc and isn't supported with GCC. If you need to use these fuzzers
# with a GCC based project you should consider looking into the LIB_FUZZING_ENGINE
# env variable defined in `test/fuzz/CMakeLists.txt`.
set(CMAKE_REQUIRED_LINK_OPTIONS "-fsanitize=fuzzer")
set(CMAKE_REQUIRED_FLAGS "-fsanitize=fuzzer-no-link")
check_cxx_source_compiles([[
#include <cstdint>
#include <cstddef>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, std::size_t Size) {
return 0;
}
]] HAS_FUZZ_FLAGS)

cmake_dependent_option(
WITH_TEST_FUZZ "Build fuzz tests" ON
HAS_FUZZ_FLAGS OFF
)

if (WITH_TEST_FUZZ)
message(STATUS "Building fuzz tests enabled")
add_subdirectory(fuzz)
else ()
message(STATUS "Building fuzz tests disabled")
endif ()
27 changes: 27 additions & 0 deletions tests/fuzz/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# By default we are going to use the libfuzzer engine. However if
# LIB_FUZZING_ENGINE is declared you can override the fuzzing engine to one of;
# - Centipede
# - Hongfuzz
# - AFL++
# - etc.
set(LIB_FUZZING_ENGINE "$ENV{LIB_FUZZING_ENGINE}"
CACHE STRING "Compiler flags necessary to link the fuzzing engine of choice e.g. libfuzzer, afl etc.")

set(FUZZ_TEST_SRC
FuzzGcodeExport
)

foreach (test ${FUZZ_TEST_SRC})
add_executable(${test} ${test}.cpp)
target_link_libraries(${test} PRIVATE _CuraEngine clipper::clipper arcus::arcus test_helpers)
# Optionally allow OSS-fuzz to manage engine flags directly.
if (LIB_FUZZING_ENGINE)
message(STATUS "Using custom fuzzing engine.")
target_link_libraries(${test} PRIVATE "${LIB_FUZZING_ENGINE}")
else ()
# By default just build with address-sanitizers/libfuzzer for local testing
message(STATUS "Using default fuzzing configuration libfuzzer+address-sanitizer.")
target_compile_options(${test} PRIVATE "-fsanitize=fuzzer,address,undefined")
target_link_libraries(${test} PRIVATE "-fsanitize=fuzzer,address,undefined")
endif ()
endforeach ()
255 changes: 255 additions & 0 deletions tests/fuzz/FuzzGcodeExport.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
// Copyright (c) 2022 Ultimaker B.V.
// CuraEngine is released under the terms of the AGPLv3 or higher.

#include "Application.h" // To set up a slice with settings.
#include "RetractionConfig.h" // For extruder switch tests.
#include "Slice.h" // To set up a slice with settings.
#include "WipeScriptConfig.h" // For wipe script tests.
#include "communication/Communication.h" //The interface we're implementing.
#include "gcodeExport.h" // The unit under test.
#include "settings/Settings.h"
#include "settings/types/LayerIndex.h"
#include "utils/Coord_t.h"
#include "utils/Date.h" // To check the Griffin header.
#include <cmath>
#include <fuzzer/FuzzedDataProvider.h> // To create structured fuzz data.
#include <limits>
#include <string>

namespace cura {

class FuzzedCommunication : public Communication {
public:
constexpr explicit FuzzedCommunication(FuzzedDataProvider *fdp) : fdp_(fdp) {}

[[nodiscard]] bool hasSlice() const override { return fdp_->ConsumeBool(); }
[[nodiscard]] bool isSequential() const override { return fdp_->ConsumeBool(); }
void sendProgress(const float &progress) const override{};
void sendLayerComplete(const LayerIndex &layer_nr, const coord_t &z,
const coord_t &thickness) override{};
void sendPolygons(const PrintFeatureType &type,
const Polygons &polygons, const coord_t &line_width,
const coord_t &line_thickness,
const Velocity &velocity) override {}
void sendPolygon(const PrintFeatureType &type,
const ConstPolygonRef &polygon,
const coord_t &line_width,
const coord_t &line_thickness,
const Velocity &velocity) override {}
void sendLineTo(const PrintFeatureType &type, const Point &to,
const coord_t &line_width,
const coord_t &line_thickness,
const Velocity &velocity) override {}
void sendCurrentPosition(const Point &position) override {}
void setExtruderForSend(const ExtruderTrain &extruder) override {}
void setLayerForSend(const LayerIndex &layer_nr) override {}
void sendOptimizedLayerData() override {}
void sendPrintTimeMaterialEstimates() const override {};
void beginGCode() override {}
void flushGCode() override {}
void sendGCodePrefix(const std::string &prefix) const override {}
void sendSliceUUID(const std::string &slice_uuid) const override {}
void sendFinishedSlicing() const override {}
void sliceNext() override {}

private:
FuzzedDataProvider *fdp_;
};

enum GcodeExporterFunction {
kSetSliceUUID,
kSetLayerNumber,
kSetFlavor,
kSetZ,
kSetFlowRateExtrusionSettings,
kSetFilamentDiameter,
kResetTotalPrintTimeAndFilament,
kWriteComment,
kWriteTypeComment,
kWriteExtrusionMode,
kResetExtrusionMode,
kWriteTimeComment,
kWriteLayerComment,
kWriteLayerCountComment,
kWriteLine,
kResetExtrusionValue,
kWriteDelay,
kWriteTravel,
kWriteExtrusion,
kInitializeExtruderTrain,
kProcessInitialLayerTemperature,
kMaxValue = kProcessInitialLayerTemperature,
};

int initSettings(FuzzedDataProvider *fdp) {
double layer_height = std::abs(fdp->ConsumeFloatingPoint<double>());
if (!std::isfinite(layer_height)) {
return 1;
}
Application::getInstance()
.current_slice->scene.current_mesh_group->settings.add(
"layer_height", std::to_string(layer_height));
int number_of_extruders = fdp->ConsumeIntegralInRange<int>(1, MAX_EXTRUDERS);
for (int i = 0; i < number_of_extruders; i++) {
Scene &scene = Application::getInstance().current_slice->scene;
scene.extruders.emplace_back(
i, &Application::getInstance()
.current_slice->scene.current_mesh_group->settings);
ExtruderTrain &train = scene.extruders.back();
train.settings.add(
"machine_nozzle_size",
std::to_string(std::abs(fdp->ConsumeFloatingPoint<double>())));
train.settings.add("machine_nozzle_id", "TestNozzle-" + std::to_string(i));
train.settings.add("machine_firmware_retract", "false");
}
return 0;
}

int fuzzGcodeExporter(FuzzedDataProvider *fdp) {

std::stringstream output;
GCodeExport gcode;
gcode.setOutputStream(&output);
int max_iterations = fdp->ConsumeIntegralInRange<int>(1, 2048);

// Extruders must have a defined non-zero diameter to avoid devide by zeros.
for (int i = 0; i < MAX_EXTRUDERS; i++) {
gcode.setFilamentDiameter(0, 1);
}
for (int i = 0; i < max_iterations; i++) {
if (fdp->remaining_bytes() == 0) {
return 0;
}
switch (fdp->ConsumeEnum<GcodeExporterFunction>()) {
case kSetSliceUUID: {
constexpr int kUUIDLength = 32;
gcode.setSliceUUID(fdp->ConsumeRandomLengthString(kUUIDLength));
break;
}
case kSetLayerNumber:
gcode.setLayerNr(fdp->ConsumeIntegral<unsigned int>());
break;
case kSetFlavor:
gcode.setFlavor(fdp->ConsumeEnum<EGCodeFlavor>());
break;
case kSetZ:
gcode.setZ(fdp->ConsumeIntegral<int>());
break;
case kSetFlowRateExtrusionSettings:
gcode.setFlowRateExtrusionSettings(fdp->ConsumeFloatingPoint<double>(),
fdp->ConsumeFloatingPoint<double>());
break;
case kSetFilamentDiameter:
gcode.setFilamentDiameter(
fdp->ConsumeIntegralInRange<size_t>(0, MAX_EXTRUDERS - 1),
fdp->ConsumeIntegralInRange<coord_t>(
1, std::numeric_limits<coord_t>::max()));
break;
case kResetTotalPrintTimeAndFilament:
gcode.resetTotalPrintTimeAndFilament();
break;
case kWriteComment:
gcode.writeComment(fdp->ConsumeRandomLengthString(256));
break;
case kWriteTypeComment:
gcode.writeTypeComment(fdp->ConsumeEnum<PrintFeatureType>());
break;
case kWriteExtrusionMode:
gcode.writeExtrusionMode(fdp->ConsumeBool());
break;
case kResetExtrusionMode:
gcode.resetExtrusionMode();
break;
case kWriteTimeComment:
gcode.writeTimeComment(std::abs(fdp->ConsumeFloatingPoint<double>()));
break;
case kWriteLayerComment:
gcode.writeLayerComment(
fdp->ConsumeIntegralInRange(0, std::numeric_limits<int>::max()));
break;
case kWriteLayerCountComment:
gcode.writeLayerCountComment(
fdp->ConsumeIntegralInRange(0, std::numeric_limits<int>::max()));
break;
case kWriteLine:
gcode.writeLine(fdp->ConsumeRandomLengthString(256).c_str());
break;
case kResetExtrusionValue:
gcode.resetExtrusionValue();
break;
case kWriteDelay:
gcode.writeDelay(std::abs(fdp->ConsumeFloatingPoint<double>()));
break;
case kWriteTravel: {
Point3 current_position = gcode.getPosition();
// Total travel distance can't be > 1000.
const int max_translation = 9;
Point3 translation = Point3(
fdp->ConsumeIntegralInRange<coord_t>(MM2INT(-max_translation / 2),
MM2INT(max_translation / 2)),
fdp->ConsumeIntegralInRange<coord_t>(MM2INT(-max_translation / 2),
MM2INT(max_translation / 2)),
fdp->ConsumeIntegralInRange<coord_t>(MM2INT(-max_translation / 2),
MM2INT(max_translation / 2)));
gcode.writeTravel(
current_position + translation,
Velocity(
std::abs(fdp->ConsumeFloatingPointInRange<double>(1.1, 999.9))));
break;
}
case kWriteExtrusion: {
Point3 current_position = gcode.getPosition();
// Total travel distance can't be > 1000.
const int max_translation = 9;
Point3 translation = Point3(
fdp->ConsumeIntegralInRange<coord_t>(MM2INT(-max_translation / 2),
MM2INT(max_translation / 2)),
fdp->ConsumeIntegralInRange<coord_t>(MM2INT(-max_translation / 2),
MM2INT(max_translation / 2)),
fdp->ConsumeIntegralInRange<coord_t>(MM2INT(-max_translation / 2),
MM2INT(max_translation / 2)));
constexpr double max_extrusion_rate = 1000.0;
gcode.writeExtrusion(
current_position + translation,
Velocity(
std::abs(fdp->ConsumeFloatingPointInRange<double>(1.1, 999.9))),
fdp->ConsumeFloatingPointInRange<double>(0.0, max_extrusion_rate),
fdp->ConsumeEnum<PrintFeatureType>(), fdp->ConsumeBool());
break;
}
case kInitializeExtruderTrain:
// TODO: Implement a fuzzed version of the storage arg.
break;
case kProcessInitialLayerTemperature:
// TODO: Implement a fuzzed version of the storage arg.
break;
}
}
return 0;
}

class App {
public:
explicit App(FuzzedDataProvider *fdp) {
Application::getInstance().current_slice = new Slice(1);
Application::getInstance().communication = new FuzzedCommunication(fdp);
}
~App() {
delete Application::getInstance().current_slice;
delete Application::getInstance().communication;
Application::getInstance().communication = nullptr;
}
};

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
FuzzedDataProvider fdp(data, size);
App app(&fdp);
if (initSettings(&fdp) != 0) {
return 1;
}
int result = fuzzGcodeExporter(&fdp);

return result;
}

} // namespace cura
Loading