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 | } |