Skip to content

Commit da1e0dd

Browse files
authored
OSSFuzz Integration (#190)
1 parent bacfd51 commit da1e0dd

File tree

11 files changed

+287
-7
lines changed

11 files changed

+287
-7
lines changed

.github/workflows/cifuzz.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: CIFuzz
2+
on:
3+
push:
4+
branches:
5+
- main
6+
pull_request:
7+
permissions: { }
8+
jobs:
9+
Fuzzing:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
security-events: write
13+
steps:
14+
- name: Build Fuzzers
15+
id: build
16+
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
17+
with:
18+
oss-fuzz-project-name: 'jpegoptim'
19+
language: c
20+
- name: Run Fuzzers
21+
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
22+
with:
23+
oss-fuzz-project-name: 'jpegoptim'
24+
language: c
25+
fuzz-seconds: 800
26+
output-sarif: true
27+
- name: Upload Crash
28+
uses: actions/upload-artifact@v4
29+
if: failure() && steps.build.outcome == 'success'
30+
with:
31+
name: artifacts
32+
path: ./out/artifacts
33+
- name: Upload Sarif
34+
if: always() && steps.build.outcome == 'success'
35+
uses: github/codeql-action/upload-sarif@v3
36+
with:
37+
# Path to SARIF file relative to the root of the repository
38+
sarif_file: cifuzz-sarif/results.sarif
39+
checkout_path: cifuzz-sarif

CMakeLists.txt

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ project(jpegoptim C)
55
# LIBJPEG_INCLUDE_DIR and LIBJPEG_LIBRARY must both be specified if a custom libjpeg implementation is desired.
66
option(WITH_ARITH "Enable arithmetic coding (if supported by the libjpeg implementation)" 1)
77
option(USE_MOZJPEG "Download, build, and link with MozJPEG rather than the system libjpeg. Build with NASM installed for SIMD support." 1)
8+
option(BUILD_FUZZERS "Build harnesses with instrumentation" 0)
9+
810
set(LIBJPEG_INCLUDE_DIR "" CACHE PATH "Custom libjpeg header directory")
911
set(LIBJPEG_LIBRARY "" CACHE FILEPATH "Custom libjpeg library binary")
1012
if(MSVC)
@@ -83,7 +85,6 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON)
8385

8486

8587
# Source groups
86-
8788
set(SOURCE_FILES
8889
jpegoptim.c
8990
jpegsrc.c
@@ -93,10 +94,20 @@ set(SOURCE_FILES
9394
)
9495
source_group("Source Files" FILES ${SOURCE_FILES})
9596

96-
9797
# Target
98-
99-
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
98+
if(BUILD_FUZZERS)
99+
add_compile_definitions(BUILD_FOR_OSS_FUZZ=1)
100+
add_compile_options(-Wno-implicit-function-declaration)
101+
add_library(${PROJECT_NAME} ${SOURCE_FILES})
102+
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
103+
104+
# Temporarily remove fuzzing flags that would break compiler checks
105+
set(ORIG_C_FLAGS "${CMAKE_C_FLAGS}")
106+
string(REGEX REPLACE "-fsanitize=[^ ]+" "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
107+
string(REGEX REPLACE "-fsanitize=[^ ]+" "" $ENV{CFLAGS} "${CMAKE_C_FLAGS}")
108+
else()
109+
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
110+
endif()
100111

101112
if(MSVC)
102113
use_props(${PROJECT_NAME} "${CMAKE_CONFIGURATION_TYPES}" "${DEFAULT_CXX_PROPS}")
@@ -308,6 +319,7 @@ endif()
308319
# Dependencies
309320

310321
if(USE_MOZJPEG)
322+
311323
# Link with mozjpeg.
312324
# Version tree: https://github.com/mozilla/mozjpeg/tree/fd569212597dcc249752bd38ea58a4e2072da24f
313325

@@ -318,11 +330,17 @@ if(USE_MOZJPEG)
318330
set(JPEGLIB_SUPPORTS_ARITH_CODE 1)
319331
endif()
320332

333+
if (BUILD_FUZZERS)
334+
set(MOZJPEG_EXTENDED_CMAKE_FLAGS -DCMAKE_C_FLAGS="")
335+
else()
336+
set(MOZJPEG_EXTENDED_CMAKE_FLAGS "")
337+
endif()
338+
321339
ExternalProject_Add(mozjpeg_lib
322340
GIT_REPOSITORY https://github.com/mozilla/mozjpeg.git
323341
GIT_TAG fd569212597dcc249752bd38ea58a4e2072da24f
324342
PREFIX ${CMAKE_CURRENT_BINARY_DIR}/mozjpeg
325-
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/mozjpeg -DPNG_SUPPORTED=0 -DWITH_TURBOJPEG=0 -DENABLE_SHARED=0 ${ARITH_FLAGS}
343+
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/mozjpeg -DPNG_SUPPORTED=0 -DWITH_TURBOJPEG=0 -DENABLE_SHARED=0 ${ARITH_FLAGS} ${MOZJPEG_EXTENDED_CMAKE_FLAGS}
326344
)
327345

328346

@@ -476,6 +494,11 @@ if (Python3_FOUND)
476494
endif()
477495

478496

497+
if (BUILD_FUZZERS AND DEFINED ENV{LIB_FUZZING_ENGINE})
498+
set(CMAKE_C_FLAGS "${ORIG_C_FLAGS}")
499+
add_subdirectory(fuzz)
500+
endif()
501+
479502
install(TARGETS ${PROJECT_NAME})
480503
install(FILES jpegoptim.1 TYPE MAN)
481504
install(FILES README COPYRIGHT LICENSE TYPE DOC)

fuzz/CMakeLists.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Fuzz process is dependent upon a few environment variables provided by OSSFuzz during the build process
2+
# For more information, see google.github.io/oss-fuzz/getting-started/new-project-guide/#buildsh-script-environment
3+
4+
add_definitions(-DNDEBUG) # Do not want assertions for fuzz-testing
5+
6+
if (DEFINED JPEG_ENGINE)
7+
set(FUZZER_NAME "${JPEG_ENGINE}_compression_fuzzer")
8+
else()
9+
set(FUZZER_NAME "unknown_compression_fuzzer")
10+
endif()
11+
12+
add_executable(${FUZZER_NAME} fuzz_compression.c fuzz_manager.c)
13+
14+
# Configure compilation options
15+
target_compile_options(${FUZZER_NAME} PRIVATE "$ENV{CMAKE_C_FLAGS}")
16+
set(CMAKE_C_COMPILER "$ENV{CC}" CACHE STRING "C compiler" FORCE)
17+
18+
# Configure linking options
19+
target_link_options(${FUZZER_NAME} PRIVATE "$ENV{LIB_FUZZING_ENGINE}")
20+
target_link_libraries(${FUZZER_NAME} PRIVATE ${PROJECT_NAME})
21+
22+
# Install the built fuzzer(s) to output directory
23+
if (DEFINED ENV{OUT})
24+
install(TARGETS ${FUZZER_NAME} DESTINATION $ENV{OUT})
25+
else()
26+
message(FATAL_ERROR "Cannot install if $OUT is not defined!")
27+
endif()

fuzz/build.sh

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
cd $SRC/jpegoptim
2+
3+
mkdir -p build
4+
5+
export ASAN_OPTIONS=detect_leaks=0
6+
7+
# Build for libjpeg
8+
cmake -S . -B build-libjpeg \
9+
-DJPEG_ENGINE=libjpeg \
10+
-DBUILD_FUZZERS=On \
11+
-DCMAKE_C_COMPILER_WORKS=1 \
12+
-DUSE_MOZJPEG=0 \
13+
-DBUILD_SHARED_LIBS=OFF \
14+
-DLIBJPEG_INCLUDE_DIR=/usr/include \
15+
-DLIBJPEG_LIBRARY=/usr/lib/x86_64-linux-gnu/libjpeg.a \
16+
&& cmake --build build-libjpeg --target install
17+
18+
# Build for libjpeg-turbo
19+
cmake -S . -B build-libjpegturbo \
20+
-DJPEG_ENGINE=libjpegturbo \
21+
-DBUILD_FUZZERS=ON \
22+
-DCMAKE_C_COMPILER_WORKS=1 \
23+
-DUSE_MOZJPEG=0 \
24+
-DBUILD_SHARED_LIBS=OFF \
25+
-DLIBJPEG_INCLUDE_DIR=/opt/libjpeg-turbo/include \
26+
-DLIBJPEG_LIBRARY=/opt/libjpeg-turbo/lib64/libjpeg.a \
27+
&& cmake --build build-libjpegturbo --target install
28+
29+
# Build for mozjpeg
30+
cmake -S . -B build-mozjpeg \
31+
-DJPEG_ENGINE=mozjpeg \
32+
-DBUILD_FUZZERS=On \
33+
-DCMAKE_C_COMPILER_WORKS=1 \
34+
-DUSE_MOZJPEG=1 \
35+
-DBUILD_SHARED_LIBS=OFF \
36+
&& cmake --build build-mozjpeg --target install
37+
38+
# Prepare corpora
39+
mkdir -p fuzz/corpus
40+
find . -name "*.jpg" -exec cp {} fuzz/corpus \;
41+
42+
# Save corpora per build
43+
zip -q $OUT/libjpeg_compression_fuzzer_seed_corpus.zip fuzz/corpus/*
44+
cp $OUT/libjpeg_compression_fuzzer_seed_corpus.zip $OUT/libjpegturbo_compression_fuzzer_seed_corpus.zip
45+
cp $OUT/libjpeg_compression_fuzzer_seed_corpus.zip $OUT/mozjpeg_compression_fuzzer_seed_corpus.zip

fuzz/fuzz_compression.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#include <stdint.h>
2+
#include "fuzz_manager.h"
3+
#include "jpegoptim.h"
4+
5+
int LLVMFuzzerTestOneInput(const uint8_t *data, const size_t size)
6+
{
7+
int rc = -1;
8+
struct stat file_stat;
9+
10+
fuzz_manager_init();
11+
12+
if (0 != lstat(fuzz_manager.fuzz_file_name, &file_stat)) {
13+
// Getting file stat failed
14+
goto end;
15+
}
16+
17+
// Write data to fuzz file
18+
FILE *fuzz_file = fopen(fuzz_manager.fuzz_file_name, "wb+");
19+
20+
if (!fuzz_file) {
21+
goto end;
22+
}
23+
24+
if (size != fwrite(data, sizeof(uint8_t), size, fuzz_file)) {
25+
goto end;
26+
}
27+
fclose(fuzz_file);
28+
29+
optimize(
30+
fuzz_manager.log_fh,
31+
fuzz_manager.fuzz_file_name,
32+
fuzz_manager.new_fuzz_file_name,
33+
fuzz_manager.tmp_dir,
34+
&file_stat,
35+
&fuzz_manager.rate,
36+
&fuzz_manager.saved
37+
);
38+
39+
rc = 0;
40+
41+
end:
42+
return rc;
43+
}

fuzz/fuzz_manager.c

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#include <libgen.h>
2+
#include <stdlib.h>
3+
#include <string.h>
4+
#include <unistd.h>
5+
#include "fuzz_manager.h"
6+
#include "jpegoptim.h"
7+
8+
fuzz_manager_t fuzz_manager = {0};
9+
10+
void generate_temp_file_name(char *buf, size_t size)
11+
{
12+
int fd;
13+
strncpy(buf, "/dev/shm/fuzz_file_XXXXXX", size);
14+
if (-1 == (fd = mkstemp(buf)))
15+
{
16+
perror("mkstemp");
17+
exit(EXIT_FAILURE);
18+
}
19+
close(fd);
20+
}
21+
22+
void fuzz_manager_init()
23+
{
24+
if (fuzz_manager.is_init)
25+
{
26+
return;
27+
}
28+
29+
fuzz_set_target_size(-75);
30+
31+
fuzz_manager.is_init = true;
32+
fuzz_manager.log_fh = fopen("/dev/null", "a"); // Do not log
33+
34+
generate_temp_file_name(fuzz_manager.fuzz_file_name, PATH_MAX);
35+
generate_temp_file_name(fuzz_manager.new_fuzz_file_name, PATH_MAX);
36+
37+
strncpy(fuzz_manager.tmp_dir, fuzz_manager.fuzz_file_name, PATH_MAX);
38+
dirname(fuzz_manager.tmp_dir);
39+
// Ensure the temp path ends with a /
40+
size_t tmp_len = strnlen(fuzz_manager.tmp_dir, PATH_MAX);
41+
42+
if (tmp_len < PATH_MAX - 1 && fuzz_manager.tmp_dir[tmp_len - 1] != '/')
43+
{
44+
fuzz_manager.tmp_dir[tmp_len] = '/';
45+
fuzz_manager.tmp_dir[tmp_len + 1] = '\0';
46+
}
47+
}

fuzz/fuzz_manager.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#ifndef FUZZ_MANAGER_H
2+
#define FUZZ_MANAGER_H
3+
#include <linux/limits.h>
4+
#include <stdbool.h>
5+
#include <stdio.h>
6+
7+
/**
8+
* Manages necessary state across fuzz iterations, should exist as singleton
9+
*/
10+
typedef struct fuzz_manager
11+
{
12+
bool is_init;
13+
double rate, saved;
14+
FILE *log_fh;
15+
char fuzz_file_name[PATH_MAX + 1];
16+
char new_fuzz_file_name[PATH_MAX + 1];
17+
char tmp_dir[PATH_MAX + 1];
18+
} fuzz_manager_t;
19+
20+
extern fuzz_manager_t fuzz_manager;
21+
22+
/**
23+
* Initializes the fuzz manager singleton, if it has not already been
24+
*/
25+
void fuzz_manager_init();
26+
27+
#endif //FUZZ_MANAGER_H
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/bash
2+
3+
DEBIAN_FRONTEND=noninteractive apt install -y libjpeg-dev
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/bash
2+
3+
# This script builds libjpeg-turbo from source, as a static library
4+
cd $SRC
5+
git clone https://github.com/libjpeg-turbo/libjpeg-turbo.git
6+
cd libjpeg-turbo
7+
mkdir -p build
8+
cmake -G"Unix Makefiles" -S . -B build -DBUILD_SHARED_LIBS=OFF -DWITH_JPEG8=1 -DCMAKE_INSTALL_PREFIX=/opt/libjpeg-turbo
9+
cmake --build build --target install

jpegoptim.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1341,6 +1341,7 @@ int wait_for_worker(FILE *log_fh)
13411341
#endif
13421342

13431343

1344+
#ifndef BUILD_FOR_OSS_FUZZ // Libfuzzer provides its own fuzzer
13441345
/****************************************************************************/
13451346
int main(int argc, char **argv)
13461347
{
@@ -1551,5 +1552,10 @@ int main(int argc, char **argv)
15511552

15521553
return (decompress_err_count > 0 || compress_err_count > 0 ? 1 : 0);;
15531554
}
1554-
1555+
#else
1556+
void fuzz_set_target_size(const int new_target_size)
1557+
{
1558+
target_size = new_target_size;
1559+
}
1560+
#endif /* BUILD_FOR_OSS_FUZZ */
15551561
/* eof :-) */

0 commit comments

Comments
 (0)