Coverage Report

Created: 2026-07-24 07:44

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