/src/postgis/fuzzers/raster_deserialize_fuzzer.c
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: PostGIS |
4 | | * Purpose: Raster serialized input fuzzer |
5 | | * |
6 | | ****************************************************************************** |
7 | | * Copyright (C) 2026 Darafei Praliaskouski <me@komzpa.net> |
8 | | * |
9 | | * This program is free software; you can redistribute it and/or |
10 | | * modify it under the terms of the GNU General Public License |
11 | | * as published by the Free Software Foundation; either version 2 |
12 | | * of the License, or (at your option) any later version. |
13 | | * |
14 | | ****************************************************************************/ |
15 | | |
16 | | #include <setjmp.h> |
17 | | #include <stddef.h> |
18 | | #include <stdint.h> |
19 | | #include <stdlib.h> |
20 | | #include <string.h> |
21 | | |
22 | | #include "librtcore.h" |
23 | | #include "rt_serialize.h" |
24 | | |
25 | | static jmp_buf jmpBuf; |
26 | | static void *current_serialized = NULL; |
27 | | |
28 | | static void |
29 | | cleanup_serialized(void) |
30 | 199 | { |
31 | 199 | free(current_serialized); |
32 | 199 | current_serialized = NULL; |
33 | 199 | } |
34 | | |
35 | | static void |
36 | | rt_message_handler_noop(const char *fmt, va_list ap) |
37 | 0 | { |
38 | 0 | (void)fmt; |
39 | 0 | (void)ap; |
40 | 0 | } |
41 | | |
42 | | static void |
43 | | rt_error_handler(const char *fmt, va_list ap) |
44 | 18 | { |
45 | 18 | (void)fmt; |
46 | 18 | (void)ap; |
47 | 18 | cleanup_serialized(); |
48 | 18 | longjmp(jmpBuf, 1); |
49 | 18 | } |
50 | | |
51 | | int |
52 | | LLVMFuzzerInitialize(int *argc, char ***argv) |
53 | 18 | { |
54 | 18 | (void)argc; |
55 | 18 | (void)argv; |
56 | 18 | rt_set_handlers(malloc, realloc, free, rt_error_handler, rt_message_handler_noop, rt_message_handler_noop); |
57 | 18 | return 0; |
58 | 18 | } |
59 | | |
60 | | static int |
61 | | checked_add(size_t *total, size_t value, size_t limit) |
62 | 86.4k | { |
63 | 86.4k | if (value > limit || *total > limit - value) |
64 | 158 | return 0; |
65 | 86.3k | *total += value; |
66 | 86.3k | return 1; |
67 | 86.4k | } |
68 | | |
69 | | static int |
70 | | has_complete_serialized_raster(const uint8_t *buf, size_t len) |
71 | 451 | { |
72 | 451 | struct rt_raster_serialized_t header; |
73 | 451 | size_t pos = sizeof(header); |
74 | | |
75 | | /* rt_raster_deserialize() accepts a pointer but no buffer length. Before |
76 | | * calling it, walk the serialized layout far enough to prove each band, |
77 | | * nodata value, optional out-db path, pixel payload, and 8-byte padding byte |
78 | | * is inside the fuzzer-provided buffer. |
79 | | */ |
80 | 451 | if (len < sizeof(header)) |
81 | 10 | return 0; |
82 | | |
83 | 441 | memcpy(&header, buf, sizeof(header)); |
84 | | |
85 | 441 | if (header.numBands > 256) |
86 | 8 | return 0; |
87 | | |
88 | 13.3k | for (uint16_t i = 0; i < header.numBands; i++) |
89 | 13.2k | { |
90 | 13.2k | if (pos >= len) |
91 | 27 | return 0; |
92 | | |
93 | 13.1k | const uint8_t band_type = buf[pos]; |
94 | 13.1k | const rt_pixtype pixtype = (rt_pixtype)(band_type & BANDTYPE_PIXTYPE_MASK); |
95 | 13.1k | const int pixbytes = rt_pixtype_size(pixtype); |
96 | 13.1k | if (pixbytes <= 0) |
97 | 0 | return 0; |
98 | | |
99 | | /* Band type byte, nodata padding bytes, and nodata value. */ |
100 | 13.1k | if (!checked_add(&pos, 1, len)) |
101 | 0 | return 0; |
102 | 13.1k | if (!checked_add(&pos, (size_t)(pixbytes - 1), len)) |
103 | 25 | return 0; |
104 | 13.1k | if (!checked_add(&pos, (size_t)pixbytes, len)) |
105 | 20 | return 0; |
106 | | |
107 | 13.1k | if (BANDTYPE_IS_OFFDB(band_type)) |
108 | 2.55k | { |
109 | | /* Out-db band number plus NUL-terminated path. */ |
110 | 2.55k | if (!checked_add(&pos, 1, len)) |
111 | 20 | return 0; |
112 | | |
113 | 2.53k | const void *nul = memchr(buf + pos, '\0', len - pos); |
114 | 2.53k | if (nul == NULL) |
115 | 10 | return 0; |
116 | | |
117 | 2.52k | const size_t path_len = (const uint8_t *)nul - (buf + pos); |
118 | 2.52k | if (!checked_add(&pos, path_len + 1, len)) |
119 | 0 | return 0; |
120 | 2.52k | } |
121 | 10.5k | else |
122 | 10.5k | { |
123 | | /* Inline raster data is width * height pixels in this band's |
124 | | * pixel type. |
125 | | */ |
126 | 10.5k | const size_t pixels = (size_t)header.width * (size_t)header.height; |
127 | 10.5k | if (header.width != 0 && pixels / header.width != header.height) |
128 | 0 | return 0; |
129 | 10.5k | if (pixbytes > 0 && pixels > len / (size_t)pixbytes) |
130 | 57 | return 0; |
131 | 10.5k | if (!checked_add(&pos, pixels * (size_t)pixbytes, len)) |
132 | 37 | return 0; |
133 | 10.5k | } |
134 | | |
135 | 44.3k | while (pos % 8 != 0) |
136 | 31.4k | { |
137 | 31.4k | if (!checked_add(&pos, 1, len)) |
138 | 56 | return 0; |
139 | 31.4k | } |
140 | 13.0k | } |
141 | | |
142 | 181 | return 1; |
143 | 433 | } |
144 | | |
145 | | static void |
146 | | destroy_raster_with_bands(rt_raster raster) |
147 | 163 | { |
148 | 163 | uint16_t num_bands; |
149 | | |
150 | 163 | if (raster == NULL) |
151 | 0 | return; |
152 | | |
153 | 163 | num_bands = rt_raster_get_num_bands(raster); |
154 | 4.96k | for (uint16_t i = 0; i < num_bands; i++) |
155 | 4.80k | { |
156 | 4.80k | rt_band band = rt_raster_get_band(raster, i); |
157 | 4.80k | if (band != NULL) |
158 | 4.80k | rt_band_destroy(band); |
159 | 4.80k | } |
160 | | |
161 | 163 | rt_raster_destroy(raster); |
162 | 163 | } |
163 | | |
164 | | int |
165 | | LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) |
166 | 451 | { |
167 | 451 | if (setjmp(jmpBuf)) |
168 | 18 | { |
169 | 18 | cleanup_serialized(); |
170 | 18 | return 0; |
171 | 18 | } |
172 | | |
173 | 433 | if (!has_complete_serialized_raster(buf, len)) |
174 | 270 | return 0; |
175 | | |
176 | 163 | current_serialized = malloc(len); |
177 | 163 | if (current_serialized == NULL) |
178 | 0 | return 0; |
179 | | |
180 | 163 | memcpy(current_serialized, buf, len); |
181 | 163 | rt_raster raster = rt_raster_deserialize(current_serialized, 0); |
182 | 163 | destroy_raster_with_bands(raster); |
183 | 163 | cleanup_serialized(); |
184 | 163 | return 0; |
185 | 163 | } |