Skip to content

ext/gd: imageavif() leaks native C heap memory (~1.6 MB per call) bypassing Zend memory manager #25442

Closed as not planned

Description

@Bugfunder

Description

Repeated calls to imageavif() in a loop result in a linear, monotonic growth of the process Resident Set Size (VmRSS), leaking approximately 1.5–1.8 MB of native C heap memory per invocation.

Because the leak occurs entirely within native C heap allocations (via libgd / libavif / libaom system malloc), PHP's Zend Memory Manager (memory_get_usage()) remains completely flat at ~2.0 MB, and ini_set('memory_limit', ...) is never triggered.

Calling imagedestroy() immediately after encoding does not free this memory. Furthermore, invoking malloc_trim(0) through FFI after 50 iterations only recovers ~4 MB out of 89 MB allocated, showing that actual unreleased heap allocations remain pinned in the process.

By comparison, identical 50-iteration and 100-iteration loops using imagewebp() and imagejpeg() maintain a rock-solid flat VmRSS (0 bytes leaked).

The following minimal code reproduces the issue:

<?php
function getVmRssMb(): float {
    $status = @file_get_contents('/proc/self/status');
    return preg_match('/VmRSS:\s+(\d+)\s+kB/', $status, $m) ? round($m[1] / 1024, 2) : 0.0;
}

echo "Initial VmRSS: " . getVmRssMb() . " MB\n";

for ($i = 1; $i <= 100; $i++) {
    $img = imagecreatetruecolor(800, 800);
    $tmp = "/tmp/test_{$i}.avif";
    imageavif($img, $tmp, 80);
    imagedestroy($img);
    @unlink($tmp);

    if ($i % 20 === 0) {
        echo sprintf(
            "Iter %3d: VmRSS = %6.2f MB | Zend emalloc = %4.2f MB\n",
            $i,
            getVmRssMb(),
            memory_get_usage(true) / 1024 / 1024
        );
    }
}

Resulted in this output:

Initial VmRSS: 37.54 MB
Iter  20: VmRSS =  79.98 MB | Zend emalloc = 2.00 MB
Iter  40: VmRSS = 111.75 MB | Zend = 2.00 MB
Iter  60: VmRSS = 141.78 MB | Zend = 2.00 MB
Iter  80: VmRSS = 172.30 MB | Zend = 2.00 MB
Iter 100: VmRSS = 202.90 MB | Zend = 2.00 MB

Over 1,000 iterations, the process climbs by ~1.6 GB. In batch image processing (e.g., CLI workers generating product thumbnails), worker processes balloon until terminated by the Linux Out-Of-Memory (OOM) killer.

But I expected this output instead:
VmRSS should stabilize after initial codec/library initialization (as observed with imagewebp(), which stabilizes at ~44.7 MB without further memory growth).

PHP Version

PHP 8.5.12 (cli) (built: Nov 11 2026 06:28:37) (NTS)
Copyright (c) The PHP Group
Built by Debian
Zend Engine v4.5.12, Copyright (c) Zend Technologies
    with Zend OPcache v8.5.12, Copyright (c), by Zend Technologies

Also reproduced on:
PHP 8.4.27 (cli) (built: Nov 11 2026 06:49:10) (NTS)

Operating System

Debian GNU/Linux (x86_64), Linux 6.12.38
Underlying libraries linked via ext/gd:

  • libgd3: 2.3.3
  • libavif16: 1.4.2-1
  • libaom3: 3.14.1-1

Activity

  1. devnexen commented on Nov 13, 2026

    @devnexen
    Member

    Hi @Bugfunder it had been fixed upstream through this, php had backported it since in the bundled library. The remaining possibility is to reach out your distribution libgd maintainers to backport it because there is no libgd release since 2.3.3. Hope it helps !

  2. Bugfunder commented on Nov 13, 2026

    @Bugfunder
    Author

    Thank you @devnexen for the pointer!

    Followed up with upstream LibGD (libgd/libgd#1052) and submitted to the Debian package maintainers, now officially tracked under Debian Bug #1153907:
    https://bugs.debian.org/1153907

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions