Coverage Report

Created: 2026-10-17 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}