Coverage Report

Created: 2026-10-17 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pupnp/fuzzer/FuzzIxmlOps.c
Line
Count
Source
1
/* Sequence-driven fuzzer for the ixml DOM mutation API.
2
 *
3
 * The input is a list of 4-byte operations over a small pool of nodes that all
4
 * belong to one document. After every operation the child and attribute lists
5
 * of the document tree and of every pool node are verified (no cycles,
6
 * parent/prev/next consistency), so corruption is reported at the offending
7
 * call. Nodes that end up detached (created but never attached, or returned by
8
 * remove/replace) are freed at the end, once per floating root.
9
 *
10
 * Only members of IXML_Node are read directly. They all come before the
11
 * optional ctag member, so the layout does not depend on
12
 * IXML_HAVE_SCRIPTSUPPORT.
13
 */
14
#include "ixml.h"
15
16
#include <stddef.h>
17
#include <stdint.h>
18
#include <stdio.h>
19
#include <stdlib.h>
20
21
36.4k
#define POOL 48
22
2.23k
#define MAX_OPS 160
23
3.56M
#define WALK_LIMIT 4096
24
/* Nodes that cloneNode may create in one run. Every other operation creates at
25
 * most one node, so no tree ever gets near WALK_LIMIT without a cycle. */
26
2.20k
#define CLONE_BUDGET 2048
27
51.0k
#define NUM_OPS 20
28
153k
#define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
29
30
static IXML_Node *pool[POOL];
31
static int npool;
32
static IXML_Document *doc;
33
static int cur_op;
34
static int clone_budget;
35
36
static const char *g_names[] = {
37
  "a", "b", "c", "d", "p:e", "xmlns:p", "x", "q:r"};
38
static const char *g_values[] = {"1", "2", "", "v w", "<&>"};
39
static const char *g_ns[] = {"urn:x",
40
  "urn:y",
41
  "http://www.w3.org/2000/xmlns/",
42
  "http://www.w3.org/XML/1998/namespace"};
43
44
static void die(const char *why)
45
0
{
46
0
  fprintf(stderr, "INVARIANT VIOLATION: %s (after op %d)\n", why, cur_op);
47
0
  abort();
48
0
}
49
50
/* Track a detached node. When the pool is full the node is freed instead. */
51
static void reg(IXML_Node *n)
52
16.9k
{
53
16.9k
  int i;
54
55
16.9k
  if (!n) {
56
1.29k
    return;
57
1.29k
  }
58
194k
  for (i = 0; i < npool; i++) {
59
180k
    if (pool[i] == n) {
60
1.99k
      return;
61
1.99k
    }
62
180k
  }
63
13.6k
  if (npool < POOL) {
64
13.4k
    pool[npool++] = n;
65
13.4k
  } else {
66
202
    ixmlNode_free(n);
67
202
  }
68
13.6k
}
69
70
static IXML_Node *pick(uint8_t idx)
71
32.4k
{
72
32.4k
  if (npool == 0) {
73
4.05k
    return NULL;
74
4.05k
  }
75
28.3k
  return pool[idx % npool];
76
32.4k
}
77
78
static IXML_Node *pick_type(uint8_t idx, int type)
79
28.0k
{
80
28.0k
  int i;
81
82
70.5k
  for (i = 0; i < npool; i++) {
83
64.5k
    IXML_Node *n = pool[(idx + i) % npool];
84
85
64.5k
    if (ixmlNode_getNodeType(n) == type) {
86
21.9k
      return n;
87
21.9k
    }
88
64.5k
  }
89
6.09k
  return NULL;
90
28.0k
}
91
92
static void check_attrs(IXML_Node *e)
93
521k
{
94
521k
  IXML_Node *a;
95
521k
  IXML_Node *prev = NULL;
96
521k
  int steps = 0;
97
98
618k
  for (a = e->firstAttr; a; a = a->nextSibling) {
99
96.4k
    if (++steps > WALK_LIMIT) {
100
0
      die("attribute chain too long (cycle?)");
101
0
    }
102
96.4k
    if (e->nodeType != eELEMENT_NODE) {
103
0
      die("attribute chain on a non-element");
104
0
    }
105
96.4k
    if (a->nodeType != eATTRIBUTE_NODE) {
106
0
      die("non-attribute in attribute chain");
107
0
    }
108
96.4k
    if (a->prevSibling != prev) {
109
0
      die("attr->prevSibling mismatch");
110
0
    }
111
96.4k
    if (a->parentNode != e) {
112
0
      die("attr->parentNode mismatch");
113
0
    }
114
96.4k
    prev = a;
115
96.4k
  }
116
521k
}
117
118
static void check_children(IXML_Node *parent)
119
521k
{
120
521k
  IXML_Node *c;
121
521k
  IXML_Node *prev = NULL;
122
521k
  int steps = 0;
123
124
521k
  check_attrs(parent);
125
739k
  for (c = parent->firstChild; c; c = c->nextSibling) {
126
218k
    if (++steps > WALK_LIMIT) {
127
0
      die("child list too long (cycle?)");
128
0
    }
129
218k
    if (c->parentNode != parent) {
130
0
      die("child->parentNode mismatch");
131
0
    }
132
218k
    if (c->prevSibling != prev) {
133
0
      die("child->prevSibling mismatch");
134
0
    }
135
218k
    prev = c;
136
218k
  }
137
521k
}
138
139
/* Walk the whole tree below the document. */
140
static void check_tree(void)
141
51.0k
{
142
51.0k
  IXML_Node *stack[WALK_LIMIT];
143
51.0k
  int sp = 0;
144
51.0k
  int visited = 0;
145
146
51.0k
  stack[sp++] = (IXML_Node *)doc;
147
181k
  while (sp > 0) {
148
130k
    IXML_Node *n = stack[--sp];
149
130k
    IXML_Node *c;
150
151
130k
    if (++visited > WALK_LIMIT) {
152
0
      die("tree too large (cycle?)");
153
0
    }
154
130k
    check_children(n);
155
210k
    for (c = n->firstChild; c; c = c->nextSibling) {
156
79.7k
      if (sp >= WALK_LIMIT - 1) {
157
0
        die("stack overflow (cycle?)");
158
0
      }
159
79.7k
      stack[sp++] = c;
160
79.7k
    }
161
130k
  }
162
51.0k
}
163
164
/* Does the subtree of root (children and attributes) contain target? */
165
static int subtree_contains(IXML_Node *root, IXML_Node *target)
166
317k
{
167
317k
  IXML_Node *stack[WALK_LIMIT];
168
317k
  int sp = 0;
169
317k
  int visited = 0;
170
171
317k
  stack[sp++] = root;
172
1.99M
  while (sp > 0) {
173
1.67M
    IXML_Node *n = stack[--sp];
174
1.67M
    IXML_Node *c;
175
176
1.67M
    if (n == target) {
177
2.38k
      return 1;
178
2.38k
    }
179
1.67M
    if (++visited > WALK_LIMIT) {
180
0
      die("subtree too large (cycle?)");
181
0
    }
182
2.76M
    for (c = n->firstChild; c; c = c->nextSibling) {
183
1.08M
      if (sp >= WALK_LIMIT - 1) {
184
0
        die("stack overflow (cycle?)");
185
0
      }
186
1.08M
      stack[sp++] = c;
187
1.08M
    }
188
1.95M
    for (c = n->firstAttr; c; c = c->nextSibling) {
189
277k
      if (sp >= WALK_LIMIT - 1) {
190
0
        die("stack overflow (cycle?)");
191
0
      }
192
277k
      stack[sp++] = c;
193
277k
    }
194
1.67M
  }
195
315k
  return 0;
196
317k
}
197
198
/* Number of nodes in the subtree of n (children and attributes). */
199
static int subtree_size(IXML_Node *n)
200
138k
{
201
138k
  IXML_Node *c;
202
138k
  int total = 1;
203
204
243k
  for (c = n->firstChild; c; c = c->nextSibling) {
205
104k
    total += subtree_size(c);
206
104k
  }
207
166k
  for (c = n->firstAttr; c; c = c->nextSibling) {
208
28.4k
    total += subtree_size(c);
209
28.4k
  }
210
138k
  return total;
211
138k
}
212
213
static void do_op(const uint8_t *op)
214
51.0k
{
215
51.0k
  uint8_t code = op[0] % NUM_OPS;
216
51.0k
  uint8_t a = op[1];
217
51.0k
  uint8_t b = op[2];
218
51.0k
  uint8_t c = op[3];
219
51.0k
  const char *name = g_names[c % ARRAY_LEN(g_names)];
220
51.0k
  const char *val = g_values[b % ARRAY_LEN(g_values)];
221
51.0k
  const char *ns = g_ns[a % ARRAY_LEN(g_ns)];
222
51.0k
  const char *local = (c & 1) ? "e" : "a";
223
51.0k
  IXML_Node *x;
224
51.0k
  IXML_Node *y;
225
51.0k
  IXML_Node *z;
226
51.0k
  IXML_Node *ret = NULL;
227
51.0k
  IXML_Attr *aret = NULL;
228
229
51.0k
  switch (code) {
230
5.22k
  case 0: {
231
5.22k
    IXML_Element *e = NULL;
232
233
5.22k
    if (npool < POOL && ixmlDocument_createElementEx(
234
4.68k
              doc, name, &e) == IXML_SUCCESS) {
235
4.68k
      reg((IXML_Node *)e);
236
4.68k
    }
237
5.22k
    break;
238
0
  }
239
1.42k
  case 1: {
240
1.42k
    IXML_Node *t = NULL;
241
242
1.42k
    if (npool < POOL && ixmlDocument_createTextNodeEx(
243
1.34k
              doc, val, &t) == IXML_SUCCESS) {
244
1.34k
      reg(t);
245
1.34k
    }
246
1.42k
    break;
247
0
  }
248
1.94k
  case 2: {
249
1.94k
    IXML_Attr *at = NULL;
250
1.94k
    int rc;
251
252
1.94k
    if (npool >= POOL) {
253
75
      break;
254
75
    }
255
1.87k
    if (b & 0x80) {
256
797
      rc = ixmlDocument_createAttributeNSEx(
257
797
        doc, ns, name, &at);
258
1.07k
    } else {
259
1.07k
      rc = ixmlDocument_createAttributeEx(doc, name, &at);
260
1.07k
    }
261
1.87k
    if (rc == IXML_SUCCESS && at) {
262
1.87k
      ixmlNode_setNodeValue((IXML_Node *)at, val);
263
1.87k
      reg((IXML_Node *)at);
264
1.87k
    }
265
1.87k
    break;
266
1.94k
  }
267
1.12k
  case 3: {
268
1.12k
    IXML_CDATASection *cd = NULL;
269
270
1.12k
    if (npool < POOL && ixmlDocument_createCDATASectionEx(
271
1.01k
              doc, val, &cd) == IXML_SUCCESS) {
272
1.01k
      reg((IXML_Node *)cd);
273
1.01k
    }
274
1.12k
    break;
275
1.94k
  }
276
4.72k
  case 4: /* appendChild */
277
4.72k
    x = (b & 1) ? (IXML_Node *)doc : pick(a);
278
4.72k
    y = pick(c);
279
4.72k
    if (x && y) {
280
4.32k
      ixmlNode_appendChild(x, y);
281
4.32k
    }
282
4.72k
    break;
283
3.43k
  case 5: /* insertBefore */
284
3.43k
    x = (b & 1) ? (IXML_Node *)doc : pick(a);
285
3.43k
    y = pick(c);
286
3.43k
    z = (b & 2) ? NULL : pick(b >> 2);
287
3.43k
    if (x && y) {
288
2.83k
      ixmlNode_insertBefore(x, y, z);
289
2.83k
    }
290
3.43k
    break;
291
2.76k
  case 6: /* replaceChild */
292
2.76k
    x = (b & 1) ? (IXML_Node *)doc : pick(a);
293
2.76k
    y = pick(c);
294
2.76k
    z = pick(b >> 1);
295
2.76k
    if (x && y && z &&
296
2.37k
      ixmlNode_replaceChild(x, y, z, &ret) == IXML_SUCCESS) {
297
408
      reg(ret);
298
408
    }
299
2.76k
    break;
300
873
  case 7: /* removeChild */
301
873
    x = (b & 1) ? (IXML_Node *)doc : pick(a);
302
873
    y = pick(c);
303
873
    if (x && y &&
304
485
      ixmlNode_removeChild(x, y, &ret) == IXML_SUCCESS) {
305
95
      reg(ret);
306
95
    }
307
873
    break;
308
5.56k
  case 8: /* cloneNode */
309
5.56k
    x = pick(a);
310
5.56k
    if (x && npool < POOL) {
311
5.21k
      int size;
312
313
5.21k
      ret = ixmlNode_cloneNode(x, b & 1);
314
5.21k
      if (!ret) {
315
0
        break;
316
0
      }
317
      /* Deep clones double a tree, so a few of them would
318
       * pass WALK_LIMIT with no cycle at all. */
319
5.21k
      size = subtree_size(ret);
320
5.21k
      if (size > clone_budget) {
321
1.15k
        ixmlNode_free(ret);
322
1.15k
        break;
323
1.15k
      }
324
4.05k
      clone_budget -= size;
325
4.05k
      reg(ret);
326
4.05k
    }
327
4.40k
    break;
328
4.40k
  case 9: /* setAttribute */
329
2.23k
    x = pick_type(a, eELEMENT_NODE);
330
2.23k
    if (x) {
331
1.77k
      ixmlElement_setAttribute((IXML_Element *)x, name, val);
332
1.77k
    }
333
2.23k
    break;
334
1.43k
  case 10: /* removeAttribute */
335
1.43k
    x = pick_type(a, eELEMENT_NODE);
336
1.43k
    if (x) {
337
983
      ixmlElement_removeAttribute((IXML_Element *)x, name);
338
983
    }
339
1.43k
    break;
340
2.13k
  case 11: /* setAttributeNode */
341
2.13k
    x = pick_type(a, eELEMENT_NODE);
342
2.13k
    y = pick_type(c, eATTRIBUTE_NODE);
343
2.13k
    if (x && y &&
344
1.43k
      ixmlElement_setAttributeNode((IXML_Element *)x,
345
1.43k
        (IXML_Attr *)y,
346
1.43k
        &aret) == IXML_SUCCESS) {
347
923
      reg((IXML_Node *)aret);
348
923
    }
349
2.13k
    break;
350
2.71k
  case 12: /* removeAttributeNode */
351
2.71k
    x = pick_type(a, eELEMENT_NODE);
352
2.71k
    y = pick_type(c, eATTRIBUTE_NODE);
353
2.71k
    if (x && y &&
354
1.84k
      ixmlElement_removeAttributeNode((IXML_Element *)x,
355
1.84k
        (IXML_Attr *)y,
356
1.84k
        &aret) == IXML_SUCCESS) {
357
868
      reg((IXML_Node *)aret);
358
868
    }
359
2.71k
    break;
360
3.83k
  case 13: /* setAttributeNS */
361
3.83k
    x = pick_type(a, eELEMENT_NODE);
362
3.83k
    if (x) {
363
3.37k
      ixmlElement_setAttributeNS(
364
3.37k
        (IXML_Element *)x, ns, name, val);
365
3.37k
    }
366
3.83k
    break;
367
1.58k
  case 14: /* removeAttributeNS */
368
1.58k
    x = pick_type(a, eELEMENT_NODE);
369
1.58k
    if (x) {
370
1.14k
      ixmlElement_removeAttributeNS(
371
1.14k
        (IXML_Element *)x, ns, local);
372
1.14k
    }
373
1.58k
    break;
374
1.98k
  case 15: /* getAttributeNS / hasAttributeNS / getAttributeNodeNS */
375
1.98k
    x = pick_type(a, eELEMENT_NODE);
376
1.98k
    if (x) {
377
1.37k
      (void)ixmlElement_getAttributeNS(
378
1.37k
        (IXML_Element *)x, ns, local);
379
1.37k
      (void)ixmlElement_hasAttributeNS(
380
1.37k
        (IXML_Element *)x, ns, local);
381
1.37k
      (void)ixmlElement_getAttributeNodeNS(
382
1.37k
        (IXML_Element *)x, ns, local);
383
1.37k
    }
384
1.98k
    break;
385
2.80k
  case 16: /* setAttributeNodeNS */
386
2.80k
    x = pick_type(a, eELEMENT_NODE);
387
2.80k
    y = pick_type(c, eATTRIBUTE_NODE);
388
2.80k
    if (x && y &&
389
1.91k
      ixmlElement_setAttributeNodeNS((IXML_Element *)x,
390
1.91k
        (IXML_Attr *)y,
391
1.91k
        &aret) == IXML_SUCCESS) {
392
1.64k
      reg((IXML_Node *)aret);
393
1.64k
    }
394
2.80k
    break;
395
2.38k
  case 17: { /* serialise */
396
2.38k
    DOMString s;
397
398
2.38k
    x = (b & 1) ? (IXML_Node *)doc : pick(a);
399
2.38k
    if (x) {
400
2.19k
      s = ixmlPrintNode(x);
401
2.19k
      ixmlFreeDOMString(s);
402
2.19k
      s = ixmlNodetoString(x);
403
2.19k
      ixmlFreeDOMString(s);
404
2.19k
    }
405
2.38k
    break;
406
5.56k
  }
407
1.24k
  case 18: { /* setNodeValue / query lists */
408
1.24k
    IXML_NodeList *l;
409
410
1.24k
    x = pick(a);
411
1.24k
    if (x) {
412
1.04k
      ixmlNode_setNodeValue(x, val);
413
1.04k
    }
414
1.24k
    l = ixmlDocument_getElementsByTagName(doc, name);
415
1.24k
    ixmlNodeList_free(l);
416
1.24k
    if (x && ixmlNode_getNodeType(x) == eELEMENT_NODE) {
417
820
      l = ixmlElement_getElementsByTagName(
418
820
        (IXML_Element *)x, name);
419
820
      ixmlNodeList_free(l);
420
820
    }
421
1.24k
    break;
422
5.56k
  }
423
1.66k
  case 19: { /* attribute map access */
424
1.66k
    IXML_NamedNodeMap *m;
425
1.66k
    unsigned long i;
426
1.66k
    unsigned long n;
427
428
1.66k
    x = pick_type(a, eELEMENT_NODE);
429
1.66k
    if (x) {
430
1.22k
      m = ixmlNode_getAttributes(x);
431
1.22k
      n = ixmlNamedNodeMap_getLength(m);
432
3.91k
      for (i = 0; i < n; i++) {
433
2.68k
        (void)ixmlNamedNodeMap_item(m, i);
434
2.68k
      }
435
1.22k
      (void)ixmlNamedNodeMap_getNamedItem(m, name);
436
1.22k
      ixmlNamedNodeMap_free(m);
437
1.22k
    }
438
1.66k
    break;
439
5.56k
  }
440
51.0k
  }
441
51.0k
}
442
443
extern int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
444
2.20k
{
445
2.20k
  size_t i;
446
2.20k
  int j;
447
2.20k
  IXML_Node *roots[POOL];
448
2.20k
  int owned[POOL];
449
2.20k
  int nroots = 0;
450
451
2.20k
  if (Size < 4) {
452
2
    return 0;
453
2
  }
454
2.20k
  if (Size > 4 * MAX_OPS) {
455
25
    Size = 4 * MAX_OPS;
456
25
  }
457
458
2.20k
  npool = 0;
459
2.20k
  clone_budget = CLONE_BUDGET;
460
2.20k
  doc = NULL;
461
2.20k
  if (ixmlDocument_createDocumentEx(&doc) != IXML_SUCCESS || !doc) {
462
0
    return 0;
463
0
  }
464
465
53.3k
  for (i = 0; i + 4 <= Size; i += 4) {
466
51.0k
    cur_op = (int)(i / 4);
467
51.0k
    do_op(Data + i);
468
51.0k
    check_tree();
469
442k
    for (j = 0; j < npool; j++) {
470
391k
      check_children(pool[j]);
471
391k
    }
472
51.0k
  }
473
474
  /* Ownership by reachability: nodes reachable from the document are
475
   * freed with it; among the rest, free only the maximal roots. */
476
15.6k
  for (j = 0; j < npool; j++) {
477
13.4k
    owned[j] = subtree_contains((IXML_Node *)doc, pool[j]);
478
13.4k
  }
479
15.6k
  for (j = 0; j < npool; j++) {
480
13.4k
    int k;
481
13.4k
    int nested = 0;
482
483
13.4k
    if (owned[j]) {
484
911
      continue;
485
911
    }
486
329k
    for (k = 0; k < npool && !nested; k++) {
487
317k
      if (k == j || owned[k]) {
488
14.5k
        continue;
489
14.5k
      }
490
302k
      if (subtree_contains(pool[k], pool[j]) &&
491
1.46k
        !subtree_contains(pool[j], pool[k])) {
492
1.46k
        nested = 1;
493
1.46k
      }
494
302k
    }
495
12.5k
    if (!nested) {
496
11.0k
      roots[nroots++] = pool[j];
497
11.0k
    }
498
12.5k
  }
499
2.20k
  ixmlDocument_free(doc);
500
13.2k
  for (j = 0; j < nroots; j++) {
501
11.0k
    ixmlNode_free(roots[j]);
502
11.0k
  }
503
504
2.20k
  return 0;
505
2.20k
}