Coverage Report

Created: 2026-08-08 08:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gs_icc_fuzzer.cc
Line
Count
Source
1
/* Copyright 2026 Google LLC
2
Licensed under the Apache License, Version 2.0 (the "License");
3
you may not use this file except in compliance with the License.
4
You may obtain a copy of the License at
5
      http://www.apache.org/licenses/LICENSE-2.0
6
Unless required by applicable law or agreed to in writing, software
7
distributed under the License is distributed on an "AS IS" BASIS,
8
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9
See the License for the specific language governing permissions and
10
limitations under the License.
11
*/
12
13
/*
14
 * Fuzzer targeting ICC profile parsing in Ghostscript.
15
 *
16
 * The existing fuzzers feed full PS/PDF/PCL documents through the GS
17
 * interpreter but never directly stress-test ICC profile parsing.
18
 * Coverage data shows gsicc_manage.c (45%), gsicc_create.c (19%),
19
 * gsicc_cache.c (42%), and several gsicc_*.c files at 0% coverage.
20
 *
21
 * This harness writes fuzz data as an ICC profile file and tells
22
 * Ghostscript to use it as the default color profile (Gray, RGB, or
23
 * CMYK, selected by the first byte). A small PostScript program with
24
 * color operations is processed to trigger ICC profile loading,
25
 * parsing, validation, and color conversion through the ICC pipeline.
26
 *
27
 * Attack surface: ICC profiles are embedded in untrusted PDF/PS
28
 * documents, making the ICC parser a high-value target for memory
29
 * corruption bugs.
30
 */
31
32
#include <base/gserrors.h>
33
#include <psi/iapi.h>
34
35
#include <limits.h>
36
#include <stdint.h>
37
#include <stdio.h>
38
#include <stdlib.h>
39
#include <string.h>
40
#include <unistd.h>
41
42
/* PostScript program that exercises color operations.
43
 * Uses gray, RGB, and CMYK color spaces to force ICC profile
44
 * loading and color space conversions through the ICC pipeline. */
45
static const char ps_program[] =
46
    "0.5 setgray 10 10 moveto 90 90 lineto stroke\n"
47
    "0.5 setgray 10 10 80 80 rectfill\n"
48
    "0.8 0.2 0.3 setrgbcolor 20 20 moveto 80 80 lineto stroke\n"
49
    "0.1 0.9 0.5 setrgbcolor 20 20 60 60 rectfill\n"
50
    "0.1 0.2 0.3 0.4 setcmykcolor 30 30 moveto 70 70 lineto stroke\n"
51
    "showpage\n";
52
53
static const unsigned char *g_data;
54
static size_t g_size;
55
56
static int gs_stdin(void *inst, char *buf, int len)
57
4.33k
{
58
4.33k
    size_t to_copy = (size_t)len < g_size ? (size_t)len : g_size;
59
4.33k
    if (to_copy > (size_t)INT_MAX)
60
0
        to_copy = (size_t)INT_MAX;
61
4.33k
    memcpy(buf, g_data, to_copy);
62
4.33k
    g_data += to_copy;
63
4.33k
    g_size -= to_copy;
64
4.33k
    return (int)to_copy;
65
4.33k
}
66
67
static int gs_stdnull(void *inst, const char *buf, int len)
68
19.2k
{
69
19.2k
    return len;
70
19.2k
}
71
72
2.55k
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
73
    /* ICC header is 128 bytes; need at least that plus selector byte */
74
2.55k
    if (size < 129)
75
13
        return 0;
76
77
    /* First byte selects which default ICC profile to replace */
78
2.54k
    int profile_type = data[0] % 3;
79
2.54k
    data++;
80
2.54k
    size--;
81
82
    /* Write fuzz data as ICC profile to temp file */
83
2.54k
    char iccfile[256];
84
2.54k
    sprintf(iccfile, "/tmp/fuzz_icc.%d.icc", getpid());
85
2.54k
    FILE *f = fopen(iccfile, "wb");
86
2.54k
    if (!f)
87
0
        return 0;
88
2.54k
    fwrite(data, 1, size, f);
89
2.54k
    fclose(f);
90
91
    /* Select profile argument based on type */
92
2.54k
    char profilearg[300];
93
2.54k
    switch (profile_type) {
94
435
        case 0:
95
435
            sprintf(profilearg, "-sDefaultGrayProfile=%s", iccfile);
96
435
            break;
97
1.76k
        case 1:
98
1.76k
            sprintf(profilearg, "-sDefaultRGBProfile=%s", iccfile);
99
1.76k
            break;
100
347
        case 2:
101
347
            sprintf(profilearg, "-sDefaultCMYKProfile=%s", iccfile);
102
347
            break;
103
2.54k
    }
104
105
    /* Set up PS program as stdin data */
106
2.54k
    g_data = (const unsigned char *)ps_program;
107
2.54k
    g_size = strlen(ps_program);
108
109
2.54k
    void *gs = NULL;
110
2.54k
    int ret = gsapi_new_instance(&gs, NULL);
111
2.54k
    if (ret < 0) {
112
0
        unlink(iccfile);
113
0
        return 0;
114
0
    }
115
116
2.54k
    gsapi_set_stdio(gs, gs_stdin, gs_stdnull, gs_stdnull);
117
2.54k
    gsapi_set_arg_encoding(gs, GS_ARG_ENCODING_UTF8);
118
119
2.54k
    char *args[] = {
120
2.54k
        (char *)"gs",
121
2.54k
        (char *)"-K1048576",
122
2.54k
        (char *)"-dNOPAUSE",
123
2.54k
        (char *)"-dBATCH",
124
2.54k
        (char *)"-dQUIET",
125
2.54k
        (char *)"-dSAFER",
126
2.54k
        (char *)"-sDEVICE=png16m",
127
2.54k
        (char *)"-sOutputFile=/dev/null",
128
2.54k
        (char *)"-r72x72",
129
2.54k
        (char *)"-dNOINTERPOLATE",
130
2.54k
        profilearg,
131
2.54k
        (char *)"-_",
132
2.54k
    };
133
2.54k
    int argc = sizeof(args) / sizeof(args[0]);
134
135
2.54k
    ret = gsapi_init_with_args(gs, argc, args);
136
2.54k
    gsapi_exit(gs);
137
2.54k
    gsapi_delete_instance(gs);
138
139
2.54k
    unlink(iccfile);
140
2.54k
    return 0;
141
2.54k
}