Coverage Report

Created: 2026-04-09 07:06

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