Coverage Report

Created: 2026-10-28 08:05

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