Coverage Report

Created: 2026-09-14 07:34

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.64k
{
58
4.64k
    size_t to_copy = (size_t)len < g_size ? (size_t)len : g_size;
59
4.64k
    if (to_copy > (size_t)INT_MAX)
60
0
        to_copy = (size_t)INT_MAX;
61
4.64k
    memcpy(buf, g_data, to_copy);
62
4.64k
    g_data += to_copy;
63
4.64k
    g_size -= to_copy;
64
4.64k
    return (int)to_copy;
65
4.64k
}
66
67
static int gs_stdnull(void *inst, const char *buf, int len)
68
19.9k
{
69
19.9k
    return len;
70
19.9k
}
71
72
2.76k
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.76k
    if (size < 129)
75
13
        return 0;
76
77
    /* First byte selects which default ICC profile to replace */
78
2.75k
    int profile_type = data[0] % 3;
79
2.75k
    data++;
80
2.75k
    size--;
81
82
    /* Write fuzz data as ICC profile to temp file */
83
2.75k
    char iccfile[256];
84
2.75k
    sprintf(iccfile, "/tmp/fuzz_icc.%d.icc", getpid());
85
2.75k
    FILE *f = fopen(iccfile, "wb");
86
2.75k
    if (!f)
87
0
        return 0;
88
2.75k
    fwrite(data, 1, size, f);
89
2.75k
    fclose(f);
90
91
    /* Select profile argument based on type */
92
2.75k
    char profilearg[300];
93
2.75k
    switch (profile_type) {
94
505
        case 0:
95
505
            sprintf(profilearg, "-sDefaultGrayProfile=%s", iccfile);
96
505
            break;
97
1.86k
        case 1:
98
1.86k
            sprintf(profilearg, "-sDefaultRGBProfile=%s", iccfile);
99
1.86k
            break;
100
384
        case 2:
101
384
            sprintf(profilearg, "-sDefaultCMYKProfile=%s", iccfile);
102
384
            break;
103
2.75k
    }
104
105
    /* Set up PS program as stdin data */
106
2.75k
    g_data = (const unsigned char *)ps_program;
107
2.75k
    g_size = strlen(ps_program);
108
109
2.75k
    void *gs = NULL;
110
2.75k
    int ret = gsapi_new_instance(&gs, NULL);
111
2.75k
    if (ret < 0) {
112
0
        unlink(iccfile);
113
0
        return 0;
114
0
    }
115
116
2.75k
    gsapi_set_stdio(gs, gs_stdin, gs_stdnull, gs_stdnull);
117
2.75k
    gsapi_set_arg_encoding(gs, GS_ARG_ENCODING_UTF8);
118
119
2.75k
    char *args[] = {
120
2.75k
        (char *)"gs",
121
2.75k
        (char *)"-K1048576",
122
2.75k
        (char *)"-dNOPAUSE",
123
2.75k
        (char *)"-dBATCH",
124
2.75k
        (char *)"-dQUIET",
125
2.75k
        (char *)"-dSAFER",
126
2.75k
        (char *)"-sDEVICE=png16m",
127
2.75k
        (char *)"-sOutputFile=/dev/null",
128
2.75k
        (char *)"-r72x72",
129
2.75k
        (char *)"-dNOINTERPOLATE",
130
2.75k
        profilearg,
131
2.75k
        (char *)"-_",
132
2.75k
    };
133
2.75k
    int argc = sizeof(args) / sizeof(args[0]);
134
135
2.75k
    ret = gsapi_init_with_args(gs, argc, args);
136
2.75k
    gsapi_exit(gs);
137
2.75k
    gsapi_delete_instance(gs);
138
139
2.75k
    unlink(iccfile);
140
2.75k
    return 0;
141
2.75k
}