/src/kea-fuzzer/pgmock.cc
Line | Count | Source |
1 | | // Copyright (C) 2025 Ada Logcis Ltd. |
2 | | // |
3 | | // This Source Code Form is subject to the terms of the Mozilla Public |
4 | | // License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | // file, You can obtain one at http://mozilla.org/MPL/2.0/. |
6 | | //////////////////////////////////////////////////////////////////////////////// |
7 | | #include <fuzzer/FuzzedDataProvider.h> |
8 | | |
9 | | #include <cstdlib> |
10 | | #include <cstring> |
11 | | #include <cstddef> |
12 | | #include <cstdint> |
13 | | |
14 | | struct pg_conn {}; |
15 | | using PGconn = pg_conn; |
16 | | |
17 | | struct pg_result { |
18 | | int status; |
19 | | int ntuples; |
20 | | int nfields; |
21 | | char** field_names; |
22 | | char** values; |
23 | | }; |
24 | | using PGresult = pg_result; |
25 | | |
26 | | enum { |
27 | | PGRES_EMPTY_QUERY = 0, PGRES_COMMAND_OK = 1, |
28 | | PGRES_TUPLES_OK = 2, PGRES_FATAL_ERROR = 7 |
29 | | }; |
30 | | |
31 | | static thread_local FuzzedDataProvider* g_fdp = nullptr; |
32 | | |
33 | 5.21k | extern "C" void pgmock_load_bytes(const uint8_t* data, size_t size) { |
34 | 5.21k | delete g_fdp; |
35 | 5.21k | g_fdp = new FuzzedDataProvider(data, size); |
36 | 5.21k | } |
37 | | |
38 | | // Helper to duplcate string and drop const for return |
39 | 218k | static char* dupstr(const char* s) { |
40 | 218k | if (!s) { |
41 | 0 | return nullptr; |
42 | 0 | } |
43 | | |
44 | 218k | size_t n = std::strlen(s) + 1; |
45 | 218k | char* p = static_cast<char*>(std::malloc(n)); |
46 | 218k | if (p) { |
47 | 218k | std::memcpy(p, s, n); |
48 | 218k | } |
49 | | |
50 | 218k | return p; |
51 | 218k | } |
52 | | |
53 | | // Helper to make a fuzz row result |
54 | 22.2k | static PGresult* make_fuzz_result() { |
55 | 22.2k | int nfields = g_fdp->ConsumeIntegralInRange<int>(2, 8); |
56 | | |
57 | 22.2k | PGresult* r = static_cast<PGresult*>(std::calloc(1, sizeof(PGresult))); |
58 | 22.2k | r->status = PGRES_TUPLES_OK; |
59 | 22.2k | r->ntuples = 1; |
60 | 22.2k | r->nfields = nfields; |
61 | | |
62 | 22.2k | r->field_names = static_cast<char**>(std::calloc(nfields, sizeof(char*))); |
63 | 22.2k | r->values = static_cast<char**>(std::calloc((size_t)nfields, sizeof(char*))); |
64 | | |
65 | 90.2k | for (int i = 0; i < nfields; ++i) { |
66 | 67.9k | r->field_names[i] = dupstr(g_fdp->ConsumeRandomLengthString(32).c_str()); |
67 | | |
68 | | // Provide random type of data return |
69 | 67.9k | int kind = g_fdp->ConsumeIntegralInRange<int>(0, 3); |
70 | 67.9k | if (kind == 0) { |
71 | 45.6k | unsigned v = g_fdp->ConsumeIntegralInRange<unsigned>(0, 9999u); |
72 | 45.6k | char buf[32]; |
73 | 45.6k | std::snprintf(buf, sizeof(buf), "%u", v); |
74 | 45.6k | r->values[i] = dupstr(buf); |
75 | 45.6k | } else if (kind == 1) { |
76 | 7.00k | r->values[i] = dupstr(g_fdp->ConsumeBool() ? "1" : "0"); |
77 | 15.2k | } else if (kind == 2) { |
78 | | // IPv4 loopback as text |
79 | 6.46k | r->values[i] = dupstr("127.0.0.1"); |
80 | 8.82k | } else { |
81 | | // IPv6 loopback as text |
82 | 8.82k | r->values[i] = dupstr("::1"); |
83 | 8.82k | } |
84 | 67.9k | } |
85 | 22.2k | return r; |
86 | 22.2k | } |
87 | | |
88 | | // Helper to make fixed version query for kea |
89 | 20.7k | static PGresult* make_version_result() { |
90 | 20.7k | PGresult* r = static_cast<PGresult*>(std::calloc(1, sizeof(PGresult))); |
91 | 20.7k | r->status = PGRES_TUPLES_OK; |
92 | 20.7k | r->ntuples = 1; |
93 | 20.7k | r->nfields = 2; |
94 | | |
95 | 20.7k | r->field_names = static_cast<char**>(std::calloc(2, sizeof(char*))); |
96 | 20.7k | r->field_names[0] = dupstr("version"); |
97 | 20.7k | r->field_names[1] = dupstr("minor"); |
98 | | |
99 | 20.7k | r->values = static_cast<char**>(std::calloc(2, sizeof(char*))); |
100 | 20.7k | r->values[0] = dupstr("31"); |
101 | 20.7k | r->values[1] = dupstr("0"); |
102 | | |
103 | 20.7k | return r; |
104 | 20.7k | } |
105 | | |
106 | | // Helper to make success reply to update or delete query |
107 | 298k | static PGresult* make_command_ok_result() { |
108 | 298k | PGresult* r = static_cast<PGresult*>(std::calloc(1, sizeof(PGresult))); |
109 | 298k | r->status = PGRES_COMMAND_OK; |
110 | 298k | r->ntuples = 0; |
111 | 298k | r->nfields = 0; |
112 | 298k | r->field_names = nullptr; |
113 | 298k | r->values = nullptr; |
114 | | |
115 | 298k | return r; |
116 | 298k | } |
117 | | |
118 | | // List of mock functions |
119 | | extern "C" { |
120 | 14.7k | PGconn* PQconnectdb(const char*) { |
121 | 14.7k | return static_cast<PGconn*>(std::calloc(1, sizeof(PGconn))); |
122 | 14.7k | } |
123 | | |
124 | 29.4k | int PQstatus(const PGconn* c) { |
125 | 29.4k | return c ? 0 : 1; |
126 | 29.4k | } |
127 | | |
128 | 14.7k | void PQfinish(PGconn* c) { |
129 | 14.7k | std::free(c); |
130 | 14.7k | } |
131 | | |
132 | 17.3k | char* PQerrorMessage(const PGconn*) { |
133 | 17.3k | return const_cast<char*>(""); |
134 | 17.3k | } |
135 | | |
136 | 26.1k | PGresult* PQexec(PGconn* , const char* query) { |
137 | 26.1k | if (g_fdp->ConsumeBool()) { |
138 | 13.6k | return make_version_result(); |
139 | 13.6k | } |
140 | 12.4k | return make_fuzz_result(); |
141 | 26.1k | } |
142 | | |
143 | | PGresult* PQexecParams(PGconn*, const char* cmd, int, const void*, |
144 | 0 | const char* const*, const int*, const int*, int) { |
145 | 0 | if (g_fdp->ConsumeBool()) { |
146 | 0 | return make_version_result(); |
147 | 0 | } |
148 | 0 | return make_fuzz_result(); |
149 | 0 | } |
150 | | |
151 | 298k | PGresult* PQprepare(PGconn*, const char*, const char*, int, const unsigned int*) { |
152 | 298k | return make_command_ok_result(); |
153 | 298k | } |
154 | | |
155 | | PGresult* PQexecPrepared(PGconn*, const char* name, int, const char* const*, |
156 | 16.8k | const int*, const int*, int) { |
157 | 16.8k | if (g_fdp->ConsumeBool()) { |
158 | 7.09k | return make_version_result(); |
159 | 7.09k | } |
160 | 9.80k | return make_fuzz_result(); |
161 | 16.8k | } |
162 | | |
163 | 341k | int PQresultStatus(const PGresult* r) { |
164 | 341k | return r ? r->status : PGRES_FATAL_ERROR; |
165 | 341k | } |
166 | | |
167 | 341k | int PQntuples(const PGresult* r) { |
168 | 341k | return r ? r->ntuples : 0; |
169 | 341k | } |
170 | | |
171 | 341k | int PQnfields(const PGresult* r) { |
172 | 341k | return r ? r->nfields : 0; |
173 | 341k | } |
174 | | |
175 | 2.09k | char* PQfname(const PGresult* r, int i) { |
176 | 2.09k | if (r && i >= 0 && i < r->nfields) { |
177 | 2.09k | return r->field_names[i]; |
178 | 2.09k | } |
179 | 0 | return const_cast<char*>(""); |
180 | 2.09k | } |
181 | | |
182 | 28.3k | char* PQgetvalue(const PGresult* r, int row, int col) { |
183 | 28.3k | if (r && row == 0 && col >= 0 && col < r->nfields) { |
184 | 28.3k | return r->values[col]; |
185 | 28.3k | } |
186 | 0 | return const_cast<char*>(""); |
187 | 28.3k | } |
188 | | |
189 | 0 | int PQgetlength(const PGresult* r, int, int col) { |
190 | 0 | if (r && col >= 0 && col < r->nfields && r->values && r->values[col]) { |
191 | 0 | return static_cast<int>(std::strlen(r->values[col])); |
192 | 0 | } |
193 | 0 | return 0; |
194 | 0 | } |
195 | | |
196 | 0 | int PQgetisnull(const PGresult*, int, int) { |
197 | 0 | return 0; |
198 | 0 | } |
199 | | |
200 | 0 | int PQbinaryTuples(const PGresult*) { |
201 | 0 | return 0; |
202 | 0 | } |
203 | | |
204 | 0 | int PQfformat(const PGresult*, int) { |
205 | 0 | return 0; |
206 | 0 | } |
207 | | |
208 | 0 | int PQfsize(const PGresult*, int) { |
209 | 0 | return -1; |
210 | 0 | } |
211 | | |
212 | 341k | void PQclear(PGresult* r) { |
213 | 341k | if (!r) { |
214 | 0 | return; |
215 | 0 | } |
216 | | |
217 | 341k | if (r->field_names) { |
218 | 152k | for (int i=0; i<r->nfields; ++i) { |
219 | 109k | if (r->field_names[i]) { |
220 | 109k | std::free(r->field_names[i]); |
221 | 109k | } |
222 | 109k | } |
223 | 43.0k | std::free(r->field_names); |
224 | 43.0k | r->field_names = nullptr; |
225 | 43.0k | } |
226 | | |
227 | 341k | const int count = (r->ntuples > 0 && r->nfields > 0) |
228 | 341k | ? r->ntuples * r->nfields : 0; |
229 | 341k | if (r->values) { |
230 | 152k | for (int i = 0; i < count; ++i) { |
231 | 109k | if (r->values[i]) { |
232 | 109k | std::free(r->values[i]); |
233 | 109k | } |
234 | 109k | } |
235 | 43.0k | std::free(r->values); |
236 | 43.0k | r->values = nullptr; |
237 | 43.0k | } |
238 | | |
239 | 341k | std::free(r); |
240 | 341k | } |
241 | | |
242 | 2.89k | const char* PQcmdTuples(const PGresult*) { |
243 | 2.89k | return "0"; |
244 | 2.89k | } |
245 | | |
246 | 0 | char* PQresultErrorField(const PGresult*, int) { |
247 | 0 | return const_cast<char*>(""); |
248 | 0 | } |
249 | | |
250 | 0 | unsigned char* PQunescapeBytea(const unsigned char*, size_t* to_length) { |
251 | 0 | if (to_length) { |
252 | 0 | *to_length = 0; |
253 | 0 | } |
254 | |
|
255 | 0 | unsigned char* p = static_cast<unsigned char*>(std::malloc(1)); |
256 | 0 | if (p) { |
257 | 0 | p[0] = 0; |
258 | 0 | } |
259 | |
|
260 | 0 | return p; |
261 | 0 | } |
262 | | |
263 | 0 | void PQfreemem(void* p) { |
264 | 0 | std::free(p); |
265 | 0 | } |
266 | | } |