Coverage Report

Created: 2026-10-28 06:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libcups/ossfuzz/fuzzhttpconn.c
Line
Count
Source
1
/*
2
   Copyright The libcups Developers.
3
   Licensed under the Apache License, Version 2.0 (the "License");
4
   you may not use this file except in compliance with the License.
5
   You may obtain a copy of the License at
6
       http://www.apache.org/licenses/LICENSE-2.0
7
   Unless required by applicable law or agreed to in writing, software
8
   distributed under the License is distributed on an "AS IS" BASIS,
9
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
   See the License for the specific language governing permissions and
11
   limitations under the License.
12
*/
13
14
// Fuzzes the HTTP/1.x connection state machine in http.c over a local socket.
15
//
16
// Input layout: byte 0 selects the mode, the rest is the raw peer byte stream.
17
//   bit 0 clear: server mode - input is one or more client requests.
18
//   bit 0 set:   client mode - input is the server response to a GET request
19
//                (POST with a small chunked body if bit 1 is also set).
20
21
#include <errno.h>
22
#include <signal.h>
23
#include <stdint.h>
24
#include <stdio.h>
25
#include <stdlib.h>
26
#include <string.h>
27
#include <sys/socket.h>
28
#include <sys/un.h>
29
#include <unistd.h>
30
#include <zlib.h>
31
#include "cups.h"
32
#include "http-private.h"
33
34
5.21k
#define MAX_INPUT 65536
35
3.31k
#define MAX_REQUESTS  8
36
432
#define MAX_UPDATES 64
37
13.0k
#define MAX_BODY  (1024 * 1024)
38
39
static int    listen_fd = -1;
40
static char   sock_path[sizeof(((struct sockaddr_un *)0)->sun_path)];
41
static const char resource[] = "/ipp/print";
42
43
44
static const char *
45
password_cb(const char *prompt, http_t *http, const char *method, const char *res, void *cb_data)
46
5.98k
{
47
5.98k
  (void)prompt; (void)http; (void)method; (void)res; (void)cb_data;
48
49
5.98k
  return ("fuzz-password");
50
5.98k
}
51
52
53
static const char *
54
oauth_cb(http_t *http, const char *realm, const char *scope, const char *res, void *cb_data)
55
1.90k
{
56
1.90k
  (void)http; (void)realm; (void)res; (void)cb_data;
57
58
  // Decline when no scope so Basic/Digest after a Bearer challenge stay reachable.
59
1.90k
  return (scope ? "fuzz-token" : NULL);
60
1.90k
}
61
62
63
static void
64
remove_socket(void)
65
1
{
66
1
  if (sock_path[0])
67
1
    unlink(sock_path);
68
1
}
69
70
71
static void
72
make_addr(struct sockaddr_un *addr)
73
1.73k
{
74
1.73k
  memset(addr, 0, sizeof(*addr));
75
1.73k
  addr->sun_family = AF_LOCAL;
76
1.73k
  memcpy(addr->sun_path, sock_path, sizeof(addr->sun_path));
77
1.73k
}
78
79
80
static bool
81
init_once(void)
82
5.21k
{
83
5.21k
  struct sockaddr_un addr;
84
85
5.21k
  if (listen_fd >= 0)
86
5.21k
    return (true);
87
88
  // Library writes to a peer that may already be gone.
89
1
  signal(SIGPIPE, SIG_IGN);
90
91
1
  cupsSetPasswordCB(password_cb, NULL);
92
1
  cupsSetOAuthCB(oauth_cb, NULL);
93
94
1
  snprintf(sock_path, sizeof(sock_path), "/tmp/fuzzhttpconn-%d.sock", (int)getpid());
95
1
  unlink(sock_path);
96
97
1
  if ((listen_fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0)
98
0
    return (false);
99
100
1
  make_addr(&addr);
101
102
1
  if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) || listen(listen_fd, 16))
103
0
  {
104
0
    close(listen_fd);
105
0
    listen_fd = -1;
106
0
    return (false);
107
0
  }
108
109
1
  atexit(remove_socket);
110
111
1
  return (true);
112
1
}
113
114
115
// Queue the whole input on the peer socket, then half-close so reads hit EOF instead of blocking.
116
static bool
117
feed(int fd, const uint8_t *data, size_t size)
118
5.21k
{
119
10.4k
  while (size > 0)
120
5.21k
  {
121
5.21k
    ssize_t bytes = send(fd, data, size, MSG_NOSIGNAL);
122
123
5.21k
    if (bytes < 0)
124
0
    {
125
0
      if (errno == EINTR)
126
0
        continue;
127
128
0
      return (false);
129
0
    }
130
131
5.21k
    data += bytes;
132
5.21k
    size -= (size_t)bytes;
133
5.21k
  }
134
135
5.21k
  return (shutdown(fd, SHUT_WR) == 0);
136
5.21k
}
137
138
139
static void
140
check_fields(http_t *http)
141
5.67k
{
142
5.67k
  char value[256];
143
144
5.67k
  httpGetSubField(http, HTTP_FIELD_AUTHORIZATION, "username", value, sizeof(value));
145
5.67k
  httpGetSubField(http, HTTP_FIELD_CONTENT_TYPE, "charset", value, sizeof(value));
146
5.67k
  httpGetSubField(http, HTTP_FIELD_KEEP_ALIVE, "timeout", value, sizeof(value));
147
5.67k
  httpGetSubField(http, HTTP_FIELD_WWW_AUTHENTICATE, "realm", value, sizeof(value));
148
5.67k
  httpGetCookieValue(http, "_TOKEN", value, sizeof(value));
149
5.67k
  httpGetCookieValue(http, "_DEVGRANT", value, sizeof(value));
150
5.67k
}
151
152
153
static void
154
drain_body(http_t *http)
155
4.23k
{
156
4.23k
  char    buffer[8192];
157
4.23k
  ssize_t bytes;
158
4.23k
  size_t  total = 0;
159
4.23k
  int   i;
160
161
4.23k
  (void)httpPeek(http, buffer, sizeof(buffer));
162
163
13.0k
  for (i = 0; i < 1024 && total < MAX_BODY; i ++)
164
13.0k
  {
165
13.0k
    if ((bytes = httpRead(http, buffer, sizeof(buffer))) <= 0)
166
4.23k
      break;
167
168
8.83k
    total += (size_t)bytes;
169
8.83k
  }
170
4.23k
}
171
172
173
// httpClose() does not free an unfinished (de)compression stream, so release it first.
174
static void
175
close_http(http_t *http)
176
5.21k
{
177
5.21k
  if (http && http->stream)
178
339
  {
179
339
    if (http->coding == _HTTP_CODING_GZIP || http->coding == _HTTP_CODING_DEFLATE)
180
0
      deflateEnd((z_stream *)http->stream);
181
339
    else
182
339
      inflateEnd((z_stream *)http->stream);
183
184
339
    free(http->stream);
185
339
    free(http->sbuffer);
186
187
339
    http->stream  = NULL;
188
339
    http->sbuffer = NULL;
189
339
    http->coding  = _HTTP_CODING_IDENTITY;
190
339
  }
191
192
5.21k
  httpClose(http);
193
5.21k
}
194
195
196
static bool
197
is_recv_state(http_state_t state)
198
2.18k
{
199
2.18k
  return (state == HTTP_STATE_POST_RECV || state == HTTP_STATE_PUT_RECV || state == HTTP_STATE_LOCK_RECV || state == HTTP_STATE_PROPFIND_RECV || state == HTTP_STATE_PROPPATCH_RECV);
200
2.18k
}
201
202
203
// Mirrors respond_http() in tools/ippeveprinter.c.
204
static bool
205
server_respond(http_t *http, http_status_t code, const char *coding)
206
2.94k
{
207
2.94k
  static const char body[] = "fuzz response\n";
208
209
2.94k
  httpClearFields(http);
210
2.94k
  httpSetField(http, HTTP_FIELD_CONTENT_TYPE, "text/plain");
211
2.94k
  if (coding)
212
204
    httpSetField(http, HTTP_FIELD_CONTENT_ENCODING, coding);
213
2.94k
  httpSetLength(http, 0);
214
215
2.94k
  if (!httpWriteResponse(http, code))
216
0
    return (false);
217
218
2.94k
  if (httpGetState(http) == HTTP_STATE_WAITING)
219
1.20k
    return (true);
220
221
1.73k
  if (httpWrite(http, body, sizeof(body) - 1) < 0)
222
0
    return (false);
223
224
1.73k
  return (httpWrite(http, "", 0) >= 0);
225
1.73k
}
226
227
228
static void
229
fuzz_server(const uint8_t *data, size_t size)
230
1.72k
{
231
1.72k
  struct sockaddr_un  addr;
232
1.72k
  http_t    *http = NULL;
233
1.72k
  int     cfd, i;
234
235
1.72k
  if ((cfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0)
236
0
    return;
237
238
1.72k
  make_addr(&addr);
239
240
1.72k
  if (connect(cfd, (struct sockaddr *)&addr, sizeof(addr)))
241
0
    goto done;
242
243
1.72k
  if ((http = httpAcceptConnection(listen_fd, true)) == NULL)
244
0
    goto done;
245
246
1.72k
  if (!feed(cfd, data, size))
247
0
    goto done;
248
249
3.31k
  for (i = 0; i < MAX_REQUESTS; i ++)
250
3.26k
  {
251
3.26k
    char    uri[1024];
252
3.26k
    http_state_t  state;
253
3.26k
    http_status_t status;
254
3.26k
    const char    *coding;
255
3.26k
    int     tries = 0;
256
257
3.26k
    httpClearFields(http);
258
3.26k
    httpClearCookie(http);
259
260
3.63k
    while ((state = httpReadRequest(http, uri, sizeof(uri))) == HTTP_STATE_WAITING && ++ tries < 16);
261
262
3.26k
    if (state == HTTP_STATE_WAITING || state == HTTP_STATE_ERROR)
263
324
      break;
264
265
2.94k
    if (state == HTTP_STATE_UNKNOWN_METHOD || state == HTTP_STATE_UNKNOWN_VERSION)
266
740
    {
267
740
      server_respond(http, HTTP_STATUS_BAD_REQUEST, NULL);
268
740
      break;
269
740
    }
270
271
2.20k
    tries = 0;
272
2.20k
    do
273
2.20k
    {
274
2.20k
      status = httpUpdate(http);
275
2.20k
    }
276
2.20k
    while (status == HTTP_STATUS_CONTINUE && ++ tries < MAX_UPDATES);
277
278
2.20k
    if (status != HTTP_STATUS_OK)
279
14
    {
280
14
      server_respond(http, HTTP_STATUS_BAD_REQUEST, NULL);
281
14
      break;
282
14
    }
283
284
2.18k
    check_fields(http);
285
286
2.18k
    if (is_recv_state(httpGetState(http)))
287
748
    {
288
748
      if (httpGetExpect(http) == HTTP_STATUS_CONTINUE && !httpWriteResponse(http, HTTP_STATUS_CONTINUE))
289
0
        break;
290
291
748
      drain_body(http);
292
748
    }
293
294
2.18k
    coding = httpGetContentEncoding(http);
295
296
2.18k
    if (!server_respond(http, HTTP_STATUS_OK, coding) || httpGetState(http) != HTTP_STATE_WAITING)
297
604
      break;
298
2.18k
  }
299
300
1.72k
  done:
301
302
1.72k
  close_http(http);
303
1.72k
  close(cfd);
304
1.72k
}
305
306
307
static void
308
fuzz_client(const uint8_t *data, size_t size, bool post)
309
3.48k
{
310
3.48k
  http_t  *http;
311
3.48k
  http_status_t status;
312
3.48k
  const char  *method = post ? "POST" : "GET";
313
3.48k
  int   sfd = -1, tries = 0;
314
315
3.48k
  if ((http = httpConnect(sock_path, 0, NULL, AF_LOCAL, HTTP_ENCRYPTION_IF_REQUESTED, true, 1000, NULL)) == NULL)
316
0
    return;
317
318
3.48k
  if ((sfd = accept(listen_fd, NULL, NULL)) < 0)
319
0
    goto done;
320
321
3.48k
  if (!feed(sfd, data, size))
322
0
    goto done;
323
324
3.48k
  if (post)
325
628
  {
326
628
    httpSetField(http, HTTP_FIELD_CONTENT_TYPE, "application/ipp");
327
628
    httpSetLength(http, 0);
328
628
  }
329
330
3.48k
  if (!httpWriteRequest(http, method, resource))
331
0
    goto done;
332
333
3.48k
  if (post && (httpWrite(http, "fuzz", 4) < 0 || httpWrite(http, "", 0) < 0))
334
0
    goto done;
335
336
3.48k
  do
337
3.91k
  {
338
3.91k
    status = httpUpdate(http);
339
3.91k
  }
340
3.91k
  while (status == HTTP_STATUS_CONTINUE && ++ tries < MAX_UPDATES);
341
342
3.48k
  check_fields(http);
343
344
  // Never resend: http_send() would reconnect to a listener nobody accepts on and block.
345
3.48k
  if (status == HTTP_STATUS_UNAUTHORIZED)
346
921
    cupsDoAuthentication(http, method, resource);
347
348
3.48k
  drain_body(http);
349
350
3.48k
  done:
351
352
3.48k
  close_http(http);
353
3.48k
  if (sfd >= 0)
354
3.48k
    close(sfd);
355
356
  // WWW-Authenticate "username" parameters change the per-thread user name.
357
3.48k
  cupsSetUser(NULL);
358
3.48k
}
359
360
361
int
362
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
363
5.21k
{
364
5.21k
  if (size < 1 || size > MAX_INPUT + 1 || !init_once())
365
0
    return (0);
366
367
5.21k
  if (data[0] & 1)
368
3.48k
    fuzz_client(data + 1, size - 1, (data[0] & 2) != 0);
369
1.72k
  else
370
1.72k
    fuzz_server(data + 1, size - 1);
371
372
5.21k
  return (0);
373
5.21k
}