Skip to content
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