Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/fuzzing.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Fuzzing

on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]

jobs:
fuzz:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install Clang
run: |
sudo apt-get update
sudo apt-get install -y clang llvm

- name: Configure CMake for Fuzzing
run: |
cmake -S . -B build_fuzz -DENABLE_FUZZING=ON -DCMAKE_BUILD_TYPE=Debug
env:
CC: clang
CXX: clang++

- name: Build fuzz target
run: cmake --build build_fuzz --target fuzz_bitmap --config Debug

- name: Create corpus directory
run: mkdir -p build_fuzz/corpus_fuzzing # Separate from build-time corpus

- name: Run fuzzer
run: |
./build_fuzz/tests/fuzz_bitmap -max_total_time=60 -print_final_stats=1 -error_exitcode=1 build_fuzz/corpus_fuzzing/
# Optionally, if you want to use existing corpus from the repo (e.g., tests/fuzz/corpus)
# ./build_fuzz/tests/fuzz_bitmap -max_total_time=60 -print_final_stats=1 -error_exitcode=1 build_fuzz/corpus_fuzzing/ tests/fuzz/corpus/
# If the fuzzer finds a crash, error_exitcode=1 will make the step fail.
24 changes: 23 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
cmake_minimum_required(VERSION 3.10)
project(bitmap VERSION 1.0.0)

option(ENABLE_FUZZING "Enable fuzzing builds" OFF)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_EXTENSIONS OFF)

if(ENABLE_FUZZING)
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
find_program(CLANG_COMPILER NAMES clang++ clang)
if(CLANG_COMPILER)
set(CMAKE_CXX_COMPILER ${CLANG_COMPILER})
else()
message(WARNING "Clang compiler not found. Disabling fuzzing.")
set(ENABLE_FUZZING OFF)
endif()
endif()

if(ENABLE_FUZZING AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
message(STATUS "Fuzzing enabled. Using Clang compiler.")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fsanitize=address,fuzzer-no-link")
# For linking fuzz targets, we'll use -fsanitize=fuzzer later
else()
message(STATUS "Fuzzing disabled or Clang not used.")
endif()
endif()

include(CTest)
enable_testing()
Expand Down
7 changes: 7 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,10 @@ target_link_libraries(bitmap_tests PRIVATE bitmap gtest_main)
# Use CTest's test discovery for GoogleTest
include(GoogleTest)
gtest_discover_tests(bitmap_tests)

if(ENABLE_FUZZING AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_executable(fuzz_bitmap fuzz/fuzz_bitmap.cpp)
target_link_libraries(fuzz_bitmap PRIVATE bitmap)
set_target_properties(fuzz_bitmap PROPERTIES LINK_FLAGS "-fsanitize=address,fuzzer")
message(STATUS "Fuzz target fuzz_bitmap added.")
endif()
23 changes: 23 additions & 0 deletions tests/fuzz/fuzz_bitmap.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include "../../include/bitmap.hpp" // For BmpTool::load and BmpTool::BitmapError
#include <cstdint>
#include <vector> // Required by span
#include <span> // For std::span

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
// We are specifically testing 54-byte headers.
// The fuzzer might provide smaller or larger inputs.
// We truncate or ignore inputs not of this size to focus the fuzzing.
if (Size < 54) {
return 0; // Not enough data for a 54-byte header.
}

// Create a span for the 54-byte header.
std::span<const uint8_t> bmp_data(Data, 54);

// Call the function to be fuzzed.
// We don't need to check the result for fuzzing purposes,
// as ASan/libFuzzer will report crashes or memory errors.
[[maybe_unused]] auto result = BmpTool::load(bmp_data);

return 0; // Essential for libFuzzer to continue.
}
61 changes: 60 additions & 1 deletion tests/test_bitmap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@
#include <gtest/gtest.h>

// Include the header for the code to be tested
#include "../src/bitmap/bitmap.h"
#include "../src/bitmap/bitmap.h" // Original include for Pixel, etc.
#include "../../include/bitmap.hpp" // For BmpTool::load and BmpTool::BitmapError

#include <array> // For std::array
#include <span> // For std::span
#include <cstdint> // For fixed-width integer types
#include <cstring> // For std::memcpy

// Overload for Pixel struct comparison
bool operator==(const Pixel& p1, const Pixel& p2) {
Expand Down Expand Up @@ -492,3 +498,56 @@ TEST(BitmapTest, ChangePixelLuminanceCyan) {
Pixel p_not_cyan = {20, 30, 150, 255}; // R is dominant
EXPECT_EQ(p_not_cyan, ChangePixelLuminanceCyan(p_not_cyan, lum_factor));
}

// New Test Suite for BmpTool specific tests
TEST(BmpToolLoadTest, LoadWithInvalidMagicType) {
std::array<uint8_t, 54> header_data{}; // Zero-initialize

// Set invalid magic type
header_data[0] = 'X';
header_data[1] = 'Y';

// Fill in plausible values for other critical fields to avoid premature errors
// that might mask the magic type check.

// BITMAPFILEHEADER (14 bytes total)
// bfSize (offset 2, size 4): Total size of file. Let's say 54 (header only) + (16*16*3) for a dummy 16x16 24bpp image.
uint32_t dummy_file_size = 54 + (16 * 16 * 3);
std::memcpy(&header_data[2], &dummy_file_size, sizeof(dummy_file_size));
// bfOffBits (offset 10, size 4): Offset to pixel data. Standard is 54 for no palette.
uint32_t off_bits = 54;
std::memcpy(&header_data[10], &off_bits, sizeof(off_bits));

// BITMAPINFOHEADER (starts at byte 14, 40 bytes total)
// biSize (offset 14, size 4): Size of BITMAPINFOHEADER, should be 40.
uint32_t info_header_size = 40;
std::memcpy(&header_data[14], &info_header_size, sizeof(info_header_size));
// biWidth (offset 18, size 4): e.g., 16
int32_t width = 16;
std::memcpy(&header_data[18], &width, sizeof(width));
// biHeight (offset 22, size 4): e.g., 16
int32_t height = 16;
std::memcpy(&header_data[22], &height, sizeof(height));
// biPlanes (offset 26, size 2): must be 1
uint16_t planes = 1;
std::memcpy(&header_data[26], &planes, sizeof(planes));
// biBitCount (offset 28, size 2): e.g., 24
uint16_t bit_count = 24;
std::memcpy(&header_data[28], &bit_count, sizeof(bit_count));
// biCompression (offset 30, size 4): must be 0 (BI_RGB) for uncompressed
uint32_t compression = 0; // BI_RGB
std::memcpy(&header_data[30], &compression, sizeof(compression));
// biSizeImage (offset 34, size 4): image size in bytes. (16*16*3)
uint32_t image_size = 16 * 16 * 3;
std::memcpy(&header_data[34], &image_size, sizeof(image_size));
// biXPelsPerMeter (offset 38, size 4): Optional, can be 0
// biYPelsPerMeter (offset 42, size 4): Optional, can be 0
// biClrUsed (offset 46, size 4): Optional, can be 0 for 24bpp
// biClrImportant (offset 50, size 4): Optional, can be 0

std::span<const uint8_t> data_span(header_data.data(), header_data.size());
auto result = BmpTool::load(data_span);

ASSERT_TRUE(result.isError());
EXPECT_EQ(result.error(), BmpTool::BitmapError::NotABmp);
}