Skip to content

Commit e244e64

Browse files
gdk-pixbuf: fix memory leaks and API misuse in pixbuf_cons_fuzzer (google#15080)
## Summary This is **not** a bug in gdk-pixbuf. It is a bug in the fuzz harness `pixbuf_cons_fuzzer` that causes **memory leaks** (1704 bytes in 8 allocations per iteration) detectable by LeakSanitizer, and an **API contract violation** (`gdk_pixbuf_scale` with same src and dest). ### False Positive Impact If left unfixed, any leak report from this fuzzer will be a **false positive** — the leaks originate in the harness, not in gdk-pixbuf. This can lead to: - **Wasted developer time**: Maintainers investigating leak reports that are not real gdk-pixbuf bugs - **Noise in OSS-Fuzz dashboards**: Persistent unfixed "bugs" that are actually harness defects - **Amplified impact in the AI era**: AI-assisted fuzzing tools increasingly reference existing OSS-Fuzz harnesses as ground truth. A buggy harness pattern can be copied and propagated by LLM-based harness generators, multiplying false positives across downstream projects ### Bugs Fixed **P1 (Harness Logic)**: Multiple intermediate `GdkPixbuf` objects leaked — `tmp` is overwritten repeatedly without freeing previous values: ```c tmp = gdk_pixbuf_rotate_simple(pixbuf, rot_amount * 90); // rotated leaked tmp = gdk_pixbuf_flip(pixbuf, TRUE); // flipped leaked tmp = gdk_pixbuf_composite_color_simple(pixbuf, ...); // composite leaked ``` Also, `GBytes` from `g_bytes_new_static()` is never freed. **P2 (API Protocol)**: `gdk_pixbuf_scale(pixbuf, pixbuf, ...)` uses the same pixbuf as both source and destination. The [API documentation](https://docs.gtk.org/gdk-pixbuf/method.Pixbuf.scale.html) requires they be different. ### LSan Report ``` ==14==ERROR: LeakSanitizer: detected memory leaks Direct leak of 96 byte(s) in 1 object(s) allocated from: #0 calloc #1 g_malloc0 (gmem.c:133) ... #5 gdk_pixbuf_new_from_data (gdk-pixbuf-data.c:79) #6 LLVMFuzzerTestOneInput (pixbuf_cons_fuzzer.c:65) SUMMARY: AddressSanitizer: 1704 byte(s) leaked in 8 allocation(s). ``` The leak triggers on the **first seed corpus input** and is 100% reproducible. ### Fix 1. Free each intermediate `tmp` with `g_object_unref()` before reassignment 2. Add `g_bytes_unref(bytes)` on all paths 3. Replace `gdk_pixbuf_scale(src==dest)` with `gdk_pixbuf_scale_simple()` Co-authored-by: DavidKorczynski <david@adalogics.com>
1 parent e946b5e commit e244e64

1 file changed

Lines changed: 18 additions & 9 deletions

File tree

projects/gdk-pixbuf/targets/pixbuf_cons_fuzzer.c

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
2424
return 0;
2525
}
2626
const gchar *profile;
27-
GdkPixbuf *pixbuf, *tmp;
27+
GdkPixbuf *pixbuf, *tmp, *tmp2;
2828
GBytes *bytes;
2929
bytes = g_bytes_new_static(data, size);
3030
pixbuf = g_object_new(GDK_TYPE_PIXBUF,
@@ -36,25 +36,32 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
3636
"pixel-bytes", bytes,
3737
NULL);
3838
if (pixbuf == NULL) {
39+
g_bytes_unref(bytes);
3940
return 0;
4041
}
41-
gdk_pixbuf_scale(pixbuf, pixbuf,
42-
0, 0,
43-
gdk_pixbuf_get_width(pixbuf) / 4,
42+
43+
tmp = gdk_pixbuf_scale_simple(pixbuf,
44+
gdk_pixbuf_get_width(pixbuf) / 4,
4445
gdk_pixbuf_get_height(pixbuf) / 4,
45-
0, 0, 0.5, 0.5,
4646
GDK_INTERP_NEAREST);
47+
if (tmp) g_object_unref(tmp);
48+
4749
unsigned int rot_amount = ((unsigned int) data[0]) % 4;
4850
tmp = gdk_pixbuf_rotate_simple(pixbuf, rot_amount * 90);
51+
if (tmp) g_object_unref(tmp);
52+
4953
tmp = gdk_pixbuf_flip(pixbuf, TRUE);
54+
if (tmp) g_object_unref(tmp);
55+
5056
tmp = gdk_pixbuf_composite_color_simple(pixbuf,
51-
gdk_pixbuf_get_width(pixbuf) / 4,
57+
gdk_pixbuf_get_width(pixbuf) / 4,
5258
gdk_pixbuf_get_height(pixbuf) / 4,
5359
GDK_INTERP_NEAREST,
5460
128,
5561
8,
5662
G_MAXUINT32,
5763
G_MAXUINT32/2);
64+
if (tmp) g_object_unref(tmp);
5865

5966
char *buf = (char *) calloc(size + 1, sizeof(char));
6067
memcpy(buf, data, size);
@@ -66,15 +73,17 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
6673
GDK_COLORSPACE_RGB,
6774
FALSE,
6875
gdk_pixbuf_get_bits_per_sample(pixbuf),
69-
gdk_pixbuf_get_width(pixbuf),
76+
gdk_pixbuf_get_width(pixbuf),
7077
gdk_pixbuf_get_height(pixbuf),
7178
gdk_pixbuf_get_rowstride(pixbuf),
7279
NULL,
7380
NULL);
74-
tmp = gdk_pixbuf_flip(tmp, TRUE);
81+
tmp2 = gdk_pixbuf_flip(tmp, TRUE);
82+
if (tmp) g_object_unref(tmp);
7583

7684
free(buf);
85+
g_bytes_unref(bytes);
7786
g_object_unref(pixbuf);
78-
g_object_unref(tmp);
87+
if (tmp2) g_object_unref(tmp2);
7988
return 0;
8089
}

0 commit comments

Comments
 (0)