Skip to content

Commit d524920

Browse files
committed
libs: Fuzzer for the file loader
1 parent 62a40c0 commit d524920

6 files changed

Lines changed: 190 additions & 21 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ jobs:
193193
--suppress=missingIncludeSystem \
194194
--suppress='*:acutest.h' \
195195
-i postscriptbarcode_fuzzer.c \
196+
-i postscriptbarcode_fuzzer_load.c \
196197
.
197198
198199
#

libs/c/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@ example
66
postscriptbarcode_test_shared
77
postscriptbarcode_test_static
88
postscriptbarcode_fuzzer
9+
postscriptbarcode_fuzzer_load
910
test_barcode.ps
1011
corpus/
12+
corpus_load/

libs/c/Makefile

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ VERSION := $(shell head -n 1 ../CHANGES)
44
MAJOR := $(firstword $(subst ., ,$(VERSION)))
55

66
FUZZER = $(NAME)_fuzzer
7+
FUZZER_LOAD = $(NAME)_fuzzer_load
78
FUZZER_CORPUS = corpus
9+
FUZZER_LOAD_CORPUS = corpus_load
810

911
# Fuzzer implies sanitizers
10-
ifneq ($(filter fuzzer fuzzer-corpus-seeds,$(MAKECMDGOALS)),)
12+
ifneq ($(filter fuzzer fuzzer-corpus-seeds fuzzer-load fuzzer-load-corpus-seeds,$(MAKECMDGOALS)),)
1113
SANITIZE = yes
1214
FUZZER_SAN_OPT = ,fuzzer
1315
endif
@@ -60,13 +62,19 @@ TEST_STATIC = $(NAME)_test_static
6062
PREFIX = /usr/local
6163
LIBDIR = $(PREFIX)/lib
6264

63-
.PHONY: default all clean lib libstatic libshared test test-static test-valgrind fuzzer fuzzer-corpus-seeds clean-fuzzer-corpus install install-static install-shared uninstall
64-
65+
.PHONY: default
6566
default: lib
67+
68+
.PHONY: all
6669
all: default test
6770

71+
.PHONY: lib
6872
lib: libshared libstatic
73+
74+
.PHONY: libshared
6975
libshared: lib$(NAME).so.$(VERSION) lib$(NAME).so lib$(NAME).so.$(MAJOR)
76+
77+
.PHONY: libstatic
7078
libstatic: lib$(NAME).a
7179

7280
lib$(NAME).so: lib$(NAME).so.$(VERSION) lib$(NAME).so.$(MAJOR)
@@ -91,10 +99,12 @@ $(TEST_SHARED): lib$(NAME).so $(NAME)_test.c
9199
$(TEST_STATIC): $(NAME).o $(NAME)_test.c
92100
$(CC) $(CFLAGS) $(NAME).o $(NAME)_test.c -o $@
93101

102+
.PHONY: test
94103
test: $(TEST_SHARED) $(TEST_STATIC)
95104
$(SAN_ENV) LD_LIBRARY_PATH=.:$$LD_LIBRARY_PATH ./$(TEST_SHARED)
96105
$(SAN_ENV) ./$(TEST_STATIC)
97106

107+
.PHONY: test-static
98108
test-static: $(TEST_STATIC)
99109
$(SAN_ENV) ./$(TEST_STATIC)
100110

@@ -103,6 +113,7 @@ test-static: $(TEST_STATIC)
103113
#
104114
# make test-valgrind
105115
#
116+
.PHONY: test-valgrind
106117
test-valgrind: $(TEST_SHARED) $(TEST_STATIC)
107118
LD_LIBRARY_PATH=.:$$LD_LIBRARY_PATH valgrind --leak-check=full --error-exitcode=1 ./$(TEST_SHARED)
108119
valgrind --leak-check=full --error-exitcode=1 ./$(TEST_STATIC)
@@ -120,6 +131,7 @@ $(FUZZER): $(NAME).o $(NAME)_fuzzer.c
120131
$(FUZZER_CORPUS)/:
121132
mkdir -p $@
122133

134+
.PHONY: fuzzer
123135
fuzzer: $(FUZZER) | $(FUZZER_CORPUS)/
124136
@echo
125137
@echo "Fuzzer built: ./$(FUZZER)"
@@ -130,6 +142,7 @@ fuzzer: $(FUZZER) | $(FUZZER_CORPUS)/
130142
@echo "Seed the corpus first with:"
131143
@echo " make fuzzer-corpus-seeds"
132144

145+
.PHONY: fuzzer-corpus-seeds
133146
fuzzer-corpus-seeds: $(FUZZER) | $(FUZZER_CORPUS)/
134147
@if [ -z "$$(ls -A $(FUZZER_CORPUS)/ 2>/dev/null)" ]; then \
135148
printf 'qrcode\0TESTING123\0version=20' > $(FUZZER_CORPUS)/qrcode_opts; \
@@ -147,30 +160,80 @@ fuzzer-corpus-seeds: $(FUZZER) | $(FUZZER_CORPUS)/
147160
echo "$(FUZZER_CORPUS)/ already populated; skipping seed"; \
148161
fi
149162

163+
.PHONY: clean-fuzzer-corpus
150164
clean-fuzzer-corpus:
151-
$(RM) -r $(FUZZER_CORPUS)/
165+
$(RM) -r $(FUZZER_CORPUS)/ $(FUZZER_LOAD_CORPUS)/
166+
167+
#
168+
# Load fuzzer: fuzzes the barcode.ps parser via fmemopen.
169+
# Requires clang.
170+
#
171+
# make fuzzer-load - Build and print run instructions
172+
# make fuzzer-load-corpus-seeds - Seed the corpus from barcode.ps
173+
# make clean-fuzzer-corpus - Remove corpus directories
174+
#
175+
$(FUZZER_LOAD): $(NAME).o $(NAME)_fuzzer_load.c
176+
$(CC) $(CFLAGS) $(NAME).o $(NAME)_fuzzer_load.c -o $@
177+
178+
$(FUZZER_LOAD_CORPUS)/:
179+
mkdir -p $@
180+
181+
.PHONY: fuzzer-load
182+
fuzzer-load: $(FUZZER_LOAD) | $(FUZZER_LOAD_CORPUS)/
183+
@echo
184+
@echo "Load fuzzer built: ./$(FUZZER_LOAD)"
185+
@echo
186+
@echo "Run with:"
187+
@echo " cd libs/c && ./$(FUZZER_LOAD) -max_len=4096 $(FUZZER_LOAD_CORPUS)/"
188+
@echo
189+
@echo "Seed the corpus first with:"
190+
@echo " make fuzzer-load-corpus-seeds"
191+
192+
.PHONY: fuzzer-load-corpus-seeds
193+
fuzzer-load-corpus-seeds: $(FUZZER_LOAD) | $(FUZZER_LOAD_CORPUS)/
194+
@if [ -z "$$(ls -A $(FUZZER_LOAD_CORPUS)/ 2>/dev/null)" ]; then \
195+
printf '' > $(FUZZER_LOAD_CORPUS)/empty; \
196+
printf '%%!PS\n' > $(FUZZER_LOAD_CORPUS)/header_only; \
197+
printf '%% Barcode Writer in Pure PostScript - Version 2099-01-01\n' > $(FUZZER_LOAD_CORPUS)/version_only; \
198+
printf '%% Barcode Writer in Pure PostScript - Version 2099-01-01\n%% --BEGIN TEMPLATE--\n%% --END TEMPLATE--\n' > $(FUZZER_LOAD_CORPUS)/empty_template; \
199+
printf '%% Barcode Writer in Pure PostScript - Version 2099-01-01\n%% --BEGIN TEMPLATE--\n%% --BEGIN RESOURCE foo--\ncode\n%% --END RESOURCE foo--\n%% --END TEMPLATE--\n' > $(FUZZER_LOAD_CORPUS)/one_resource; \
200+
printf '%% Barcode Writer in Pure PostScript - Version 2099-01-01\n%% --BEGIN TEMPLATE--\n%% --BEGIN ENCODER bar--\n%% --REQUIRES foo--\n%% --DESC: Test\n%% --EXAM: data\n%% --EXOP: opts\n%% --RNDR: renlinear\n%% --FMLY: Test Family\ncode\n%% --END ENCODER bar--\n%% --END TEMPLATE--\n' > $(FUZZER_LOAD_CORPUS)/encoder_metadata; \
201+
printf '%% --BEGIN TEMPLATE--\n%% --BEGIN RESOURCE a--\nA\n%% --END RESOURCE a--\n%% --BEGIN RESOURCE b--\nB\n%% --END RESOURCE b--\n%% --BEGIN RESOURCE c--\nC\n%% --END RESOURCE c--\n%% --END TEMPLATE--\n' > $(FUZZER_LOAD_CORPUS)/multi_resource; \
202+
sed 's/$$/\r/' $(FUZZER_LOAD_CORPUS)/encoder_metadata > $(FUZZER_LOAD_CORPUS)/encoder_crlf; \
203+
sed 's/$$/\r/' $(FUZZER_LOAD_CORPUS)/multi_resource > $(FUZZER_LOAD_CORPUS)/multi_crlf; \
204+
printf '%% --BEGIN TEMPLATE--\n%% --BEGIN RESOURCE foo--\n' > $(FUZZER_LOAD_CORPUS)/truncated; \
205+
echo "Seeded $(FUZZER_LOAD_CORPUS)/ with $$(ls $(FUZZER_LOAD_CORPUS)/ | wc -l) inputs"; \
206+
else \
207+
echo "$(FUZZER_LOAD_CORPUS)/ already populated; skipping seed"; \
208+
fi
152209

210+
.PHONY: clean
153211
clean:
154-
$(RM) $(TEST_SHARED) $(TEST_STATIC) $(FUZZER) *.o *.so* *.a *.d
212+
$(RM) $(TEST_SHARED) $(TEST_STATIC) $(FUZZER) $(FUZZER_LOAD) *.o *.so* *.a *.d
155213

214+
.PHONY: install
156215
install: install-static install-shared
157216

217+
.PHONY: install-headers
158218
install-headers:
159219
install -d $(DESTDIR)$(PREFIX)/include
160220
install -m 0644 $(NAME).h $(DESTDIR)$(PREFIX)/include
161221
install -m 0644 ../bindings/cpp/$(NAME).hpp $(DESTDIR)$(PREFIX)/include
162222

223+
.PHONY: install-static
163224
install-static: libstatic install-headers
164225
install -d $(DESTDIR)$(LIBDIR)
165226
install -m 0644 lib$(NAME).a $(DESTDIR)$(LIBDIR)
166227

228+
.PHONY: install-shared
167229
install-shared: libshared install-headers
168230
install -d $(DESTDIR)$(LIBDIR)
169231
install -m 0644 lib$(NAME).so.$(VERSION) $(DESTDIR)$(LIBDIR)
170232
cd $(DESTDIR)$(LIBDIR) && ln -sf lib$(NAME).so.$(VERSION) lib$(NAME).so
171233
cd $(DESTDIR)$(LIBDIR) && ln -sf lib$(NAME).so.$(VERSION) lib$(NAME).so.$(MAJOR)
172234
-ldconfig
173235

236+
.PHONY: uninstall
174237
uninstall:
175238
$(RM) $(DESTDIR)$(PREFIX)/include/$(NAME).h
176239
$(RM) $(DESTDIR)$(PREFIX)/include/$(NAME).hpp

libs/c/postscriptbarcode.c

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -343,12 +343,10 @@ BWIPP_API BWIPP *bwipp_load(void) {
343343
return bwipp_load_ex(NULL);
344344
}
345345

346-
BWIPP_API BWIPP *bwipp_load_ex(const bwipp_load_init_opts_t *opts) {
346+
/* Core loader: takes an open FILE* (binary mode) */
347+
BWIPP *_bwipp_load_from_fp(FILE *f, bwipp_load_init_flags_t flags,
348+
unsigned int hexify_width) {
347349
BWIPP *ctx;
348-
FILE *f;
349-
const char *filename = NULL;
350-
bwipp_load_init_flags_t flags = bwipp_iDEFAULT;
351-
unsigned int hexify_width = 0;
352350

353351
ResourceList **tail;
354352
Resource *resource = NULL;
@@ -359,12 +357,6 @@ BWIPP_API BWIPP *bwipp_load_ex(const bwipp_load_init_opts_t *opts) {
359357
bool skip;
360358
bool lazy;
361359

362-
EXTRACT_OPT(filename);
363-
EXTRACT_OPT(flags);
364-
EXTRACT_OPT(hexify_width);
365-
if (!filename)
366-
filename = default_filename;
367-
368360
lazy = (flags & bwipp_iLAZY_LOAD) != 0;
369361

370362
ctx = malloc(sizeof(BWIPP));
@@ -381,10 +373,6 @@ BWIPP_API BWIPP *bwipp_load_ex(const bwipp_load_init_opts_t *opts) {
381373
ctx->hexify_width = hexify_width;
382374
tail = &ctx->resourcelist;
383375

384-
f = fopen(filename, "rb");
385-
if (!f)
386-
goto error;
387-
388376
if (!lazy) {
389377
code = malloc(MAX_CODE);
390378
if (!code)
@@ -634,6 +622,25 @@ BWIPP_API BWIPP *bwipp_load_ex(const bwipp_load_init_opts_t *opts) {
634622
return NULL;
635623
}
636624

625+
BWIPP_API BWIPP *bwipp_load_ex(const bwipp_load_init_opts_t *opts) {
626+
FILE *f;
627+
const char *filename = NULL;
628+
bwipp_load_init_flags_t flags = bwipp_iDEFAULT;
629+
unsigned int hexify_width = 0;
630+
631+
EXTRACT_OPT(filename);
632+
EXTRACT_OPT(flags);
633+
EXTRACT_OPT(hexify_width);
634+
if (!filename)
635+
filename = default_filename;
636+
637+
f = fopen(filename, "rb");
638+
if (!f)
639+
return NULL;
640+
641+
return _bwipp_load_from_fp(f, flags, hexify_width); /* Takes ownership of f */
642+
}
643+
637644
BWIPP_API BWIPP *bwipp_load_from_file(const char *filename) {
638645
bwipp_load_init_opts_t opts = {
639646
.struct_size = sizeof(opts),
@@ -960,7 +967,10 @@ BWIPP_API char *bwipp_emit_all_resources(BWIPP *ctx) {
960967

961968
curr = ctx->resourcelist;
962969

963-
assert(ctx->resourcelist);
970+
if (!curr) {
971+
tmp = strdup("");
972+
return tmp; /* No resources */
973+
}
964974

965975
code = malloc(MAX_CODE);
966976
if (!code)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* libpostscriptbarcode - postscriptbarcode_fuzzer_load.c
3+
*
4+
* Fuzzer for the barcode.ps parser and lazy loader.
5+
*
6+
* @file postscriptbarcode_fuzzer_load.c
7+
* @author Copyright (c) 2004-2026 Terry Burton.
8+
*
9+
* Permission is hereby granted, free of charge, to any
10+
* person obtaining a copy of this software and associated
11+
* documentation files (the "Software"), to deal in the
12+
* Software without restriction, including without
13+
* limitation the rights to use, copy, modify, merge,
14+
* publish, distribute, sublicense, and/or sell copies of
15+
* the Software, and to permit persons to whom the Software
16+
* is furnished to do so, subject to the following
17+
* conditions:
18+
*
19+
* The above copyright notice and this permission notice
20+
* shall be included in all copies or substantial portions
21+
* of the Software.
22+
*
23+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
24+
* KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
25+
* THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
26+
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
27+
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
28+
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
29+
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
30+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
31+
* IN THE SOFTWARE.
32+
*
33+
*/
34+
35+
#include <stdint.h>
36+
#include <stdio.h>
37+
#include <string.h>
38+
39+
#include "postscriptbarcode.h"
40+
#include "postscriptbarcode_private.h" /* _bwipp_load_from_fp */
41+
42+
int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
43+
44+
FILE *f;
45+
BWIPP *ctx;
46+
char *out;
47+
48+
f = fmemopen((void *)buf, len, "rb");
49+
if (!f)
50+
return 0;
51+
52+
/* Eager load */
53+
ctx = _bwipp_load_from_fp(f, bwipp_iDEFAULT, 0);
54+
if (ctx) {
55+
unsigned int count;
56+
const char **list;
57+
58+
(void)bwipp_get_version(ctx);
59+
60+
list = bwipp_list_encoders(ctx, &count);
61+
bwipp_free((void *)list);
62+
63+
list = bwipp_list_families(ctx, &count);
64+
bwipp_free((void *)list);
65+
66+
out = bwipp_emit_all_resources(ctx);
67+
bwipp_free(out);
68+
69+
bwipp_unload(ctx);
70+
}
71+
72+
/* Lazy load */
73+
f = fmemopen((void *)buf, len, "rb");
74+
if (!f)
75+
return 0;
76+
77+
ctx = _bwipp_load_from_fp(f, bwipp_iLAZY_LOAD, 0);
78+
if (ctx) {
79+
(void)bwipp_get_version(ctx);
80+
81+
out = bwipp_emit_all_resources(ctx);
82+
bwipp_free(out);
83+
84+
bwipp_unload(ctx);
85+
}
86+
87+
return 0;
88+
}

libs/c/postscriptbarcode_private.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535

3636
#include "postscriptbarcode.h"
3737
#include <stddef.h>
38+
#include <stdio.h>
3839

3940
#ifdef _MSC_VER
4041
#define strdup _strdup
@@ -75,4 +76,8 @@ typedef struct FamilyList {
7576
struct FamilyList *next;
7677
} FamilyList;
7778

79+
/* Private API for testing/fuzzing — takes ownership of f */
80+
BWIPP *_bwipp_load_from_fp(FILE *f, bwipp_load_init_flags_t flags,
81+
unsigned int hexify_width);
82+
7883
#endif /* BWIPP_PRIVATE_H */

0 commit comments

Comments
 (0)