Skip to content

Commit 1e2f9df

Browse files
authored
ttf: coverity & other fixes. Added fuzz test (#166)
1 parent 6b3af71 commit 1e2f9df

4 files changed

Lines changed: 59 additions & 9 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ coverage-html/
1212
docs/html
1313
docs/latex
1414
doxygen.log
15+
tests/massive-file

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ check-fuzz-%: tests/fuzz-% FORCE
6666
mkdir -p fuzz-artifacts
6767
./$< -verbosity=0 -max_total_time=240 -max_len=8192 -rss_limit_mb=1024 -artifact_prefix="./fuzz-artifacts/"
6868

69-
fuzz-check: check-fuzz-image-data check-fuzz-image-file check-fuzz-header check-fuzz-text check-fuzz-dstr check-fuzz-barcode
69+
fuzz-check: check-fuzz-image-data check-fuzz-image-file check-fuzz-header check-fuzz-text check-fuzz-dstr check-fuzz-barcode check-fuzz-ttf
7070

7171
format: FORCE
7272
$(CLANG_FORMAT) -i pdfgen.c pdfgen.h tests/main.c tests/fuzz-*.c tests/massive-file.c

pdfgen.c

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,8 @@ static const uint8_t *ttf_find_table(const uint8_t *data, size_t data_len,
233233
if (data_len < 12)
234234
return NULL;
235235
uint16_t numTables = ttf_be16(data + 4);
236+
if (numTables > (data_len - 12) / 16)
237+
numTables = (uint16_t)((data_len - 12) / 16);
236238
for (uint16_t i = 0; i < numTables; i++) {
237239
if ((size_t)(12 + ((size_t)i + 1) * 16) > data_len)
238240
break;
@@ -312,6 +314,8 @@ static const uint8_t *ttf_find_cmap_subtable(const uint8_t *cmap,
312314
if (!cmap || cmap_len < 4)
313315
return NULL;
314316
uint16_t numTables = ttf_be16(cmap + 2);
317+
if (numTables > (cmap_len - 4) / 8)
318+
numTables = (uint16_t)((cmap_len - 4) / 8);
315319
const uint8_t *best_subtable = NULL;
316320
int best_priority = -1;
317321

@@ -323,7 +327,7 @@ static const uint8_t *ttf_find_cmap_subtable(const uint8_t *cmap,
323327
uint16_t platformID = ttf_be16(rec);
324328
uint16_t platEncID = ttf_be16(rec + 2);
325329
uint32_t subtable_offset = ttf_be32(rec + 4);
326-
if (subtable_offset + 4 > cmap_len)
330+
if ((size_t)subtable_offset + 4 > cmap_len)
327331
continue;
328332
const uint8_t *sub = cmap + subtable_offset;
329333
uint16_t fmt = ttf_be16(sub);
@@ -383,6 +387,8 @@ static void ttf_extract_name(const uint8_t *name_table, size_t table_len,
383387
if (!name_table || table_len < 6)
384388
return;
385389
uint16_t count = ttf_be16(name_table + 2);
390+
if (count > (table_len - 6) / 12)
391+
count = (uint16_t)((table_len - 6) / 12);
386392
uint16_t stringOffset = ttf_be16(name_table + 4);
387393

388394
for (uint16_t i = 0; i < count; i++) {
@@ -403,7 +409,10 @@ static void ttf_extract_name(const uint8_t *name_table, size_t table_len,
403409
if (platformID == 3) {
404410
// Windows UTF-16 BE: extract only ASCII characters
405411
size_t ascii_len = 0;
406-
for (uint16_t j = 0; j + 1 < length && ascii_len < out_len - 1;
412+
size_t safe_length = length;
413+
if (safe_length > table_len - str_off)
414+
safe_length = table_len - str_off;
415+
for (size_t j = 0; j + 1 < safe_length && ascii_len < out_len - 1;
407416
j += 2) {
408417
uint16_t cp = ttf_be16(str + j);
409418
if (cp < 128)
@@ -414,7 +423,11 @@ static void ttf_extract_name(const uint8_t *name_table, size_t table_len,
414423
return;
415424
} else if (platformID == 1) {
416425
// Mac Roman: ASCII-compatible
417-
size_t copy_len = length < out_len - 1 ? length : out_len - 1;
426+
size_t safe_length = length;
427+
if (safe_length > table_len - str_off)
428+
safe_length = table_len - str_off;
429+
size_t copy_len =
430+
safe_length < out_len - 1 ? safe_length : out_len - 1;
418431
memcpy(out, str, copy_len);
419432
out[copy_len] = '\0';
420433
return;
@@ -1285,8 +1298,8 @@ int pdf_set_font_ttf(struct pdf_doc *pdf, const char *path)
12851298
sizeof(font_name));
12861299
if (font_name[0] == '\0') {
12871300
const char *base = strrchr(path, '/');
1288-
base = base ? base + 1 : path;
1289-
strncpy(font_name, base, sizeof(font_name) - 1);
1301+
const char *name_src = base ? base + 1 : path;
1302+
strncpy(font_name, name_src, sizeof(font_name) - 1);
12901303
font_name[sizeof(font_name) - 1] = '\0';
12911304
char *dot = strrchr(font_name, '.');
12921305
if (dot)
@@ -1309,7 +1322,8 @@ int pdf_set_font_ttf(struct pdf_doc *pdf, const char *path)
13091322
uint16_t cmap_subtable_len = 0;
13101323
const uint8_t *cmap_subtable =
13111324
ttf_find_cmap_subtable(cmap, (size_t)cmap_len, &cmap_subtable_len);
1312-
if (!cmap_subtable) {
1325+
if (!cmap_subtable || cmap_subtable_len == 0 ||
1326+
cmap_subtable_len > font_data_len) {
13131327
free(font_data);
13141328
return pdf_set_err(pdf, -EINVAL,
13151329
"Font '%s' has no usable cmap subtable", path);
@@ -1321,8 +1335,22 @@ int pdf_set_font_ttf(struct pdf_doc *pdf, const char *path)
13211335
"Unable to allocate cmap subtable for font '%s'",
13221336
path);
13231337
}
1338+
// ensure subtable fits in cmap bounds safely before blind copy
1339+
if (cmap_subtable_len > (cmap + cmap_len) - cmap_subtable) {
1340+
free(cmap_copy);
1341+
free(font_data);
1342+
return pdf_set_err(pdf, -EINVAL,
1343+
"Font '%s' cmap subtable length invalid", path);
1344+
}
13241345
memcpy(cmap_copy, cmap_subtable, cmap_subtable_len);
13251346

1347+
if (hmtx_len == 0 || hmtx_len > font_data_len) {
1348+
free(cmap_copy);
1349+
free(font_data);
1350+
return pdf_set_err(pdf, -EINVAL, "Font '%s' has invalid hmtx_len",
1351+
path);
1352+
}
1353+
13261354
// Make a copy of the hmtx table for runtime advance-width lookups
13271355
uint8_t *hmtx_copy = (uint8_t *)malloc(hmtx_len);
13281356
if (!hmtx_copy) {
@@ -1338,12 +1366,12 @@ int pdf_set_font_ttf(struct pdf_doc *pdf, const char *path)
13381366
// Widths are in PDF thousandths-of-em units. We store one entry per
13391367
// glyph ID in 0..numberOfHMetrics-1; glyphs beyond that share the last
13401368
// advance width (/DW).
1341-
if (numberOfHMetrics == 0) {
1369+
if (numberOfHMetrics == 0 || numberOfHMetrics > font_data_len / 2) {
13421370
free(hmtx_copy);
13431371
free(cmap_copy);
13441372
free(font_data);
13451373
return pdf_set_err(pdf, -EINVAL,
1346-
"Font '%s' has numberOfHMetrics == 0", path);
1374+
"Font '%s' has invalid numberOfHMetrics", path);
13471375
}
13481376
uint16_t *glyph_widths =
13491377
(uint16_t *)calloc(numberOfHMetrics, sizeof(uint16_t));

tests/fuzz-ttf.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#include <stdio.h>
2+
#include <string.h>
3+
4+
#include "pdfgen.h"
5+
6+
#define filename "./fuzz-ttf.ttf"
7+
int LLVMFuzzerTestOneInput(char *data, int size)
8+
{
9+
FILE *temfile = fopen(filename, "w");
10+
if (!temfile)
11+
return 0;
12+
fwrite(data, 1, size, temfile);
13+
fclose(temfile);
14+
15+
struct pdf_doc *pdf = pdf_create(PDF_A4_WIDTH, PDF_A4_HEIGHT, NULL);
16+
pdf_append_page(pdf);
17+
pdf_set_font_ttf(pdf, filename);
18+
pdf_save(pdf, "fuzz-ttf.pdf");
19+
pdf_destroy(pdf);
20+
return 0;
21+
}

0 commit comments

Comments
 (0)