Skip to content

Regression between 1.20.2 and 1.21.2 : valid HEIC file fails to decode with "bad seek" errors (not yet tested with 1.22.0) #1872

Closed

Description

@pimpenin

Description

A HEIC file that decodes correctly with libheif 1.20.2 fails to decode
with libheif 1.21.2, producing repeated internal seek errors before
failing entirely. I have not yet been able to test against 1.22.0
(released after I diagnosed this) — flagging in case it's relevant to
recent changes, or already fixed there.

Environment

  • libheif 1.20.2 (working) vs 1.21.2 (broken), same file, same host
  • Consumer: libvips 8.18.4 (vips-heif.so module), via vipsthumbnail
  • OS: Debian/Ubuntu-based LXC container

Steps to reproduce

vipsthumbnail --size=256x256 sample.HEIC -o out.jpg

With libheif 1.21.2 loaded (default):

sample.HEIC: bad seek to 2593797
sample.HEIC: bad seek to 2593777
sample.HEIC: bad seek to 2593767
sample.HEIC: bad seek to 2593766
heif: Invalid input: No 'ftyp' box (2.102)

With libheif 1.20.2 forced via LD_PRELOAD (same file, same vips):

LD_PRELOAD=/usr/local/lib/libheif.so.1.20.2 vipsthumbnail --size=256x256 sample.HEIC -o out.jpg
# succeeds, produces a valid 8927-byte JPEG thumbnail

Expected behavior

The file should decode successfully in 1.21.2, as it did in 1.20.2 and
as it does when the earlier library is force-loaded via LD_PRELOAD on
the exact same file, same libvips build, same machine.

Additional notes

  • The reported seek offsets (2593766–2593797) are all within ~30 bytes
    of the file's actual size (2593765 bytes), suggesting the code path
    attempts to read a small fixed-size trailing structure (box header/
    padding?) that this file does not have at that position.
  • The security-limit environment variable
    (LIBHEIF_SECURITY_LIMITS=off) has no effect on this particular
    failure — confirmed by testing both on/off with identical results —
    so it's unrelated to the iref reference-count limit.
  • Not an isolated case: after upgrading a self-hosted photo library
    (Immich) from an older libheif to 1.21.2, several thousand
    previously-working HEIC files (originating from iPhone HDR/Portrait/
    Live Photo captures) began failing the same way. Workaround
    currently in production: LD_PRELOAD forcing 1.20.2.

Activity

  1. bradh commented on Jul 17, 2026

    @bradh
    Contributor

    Can you upload a sample demonstrating the problem as a zipped file?

  2. pimpenin commented on Jul 18, 2026

    @pimpenin
    Author
  3. bradh commented on Jul 18, 2026

    @bradh
    Contributor

    I tested this with current master and it decoded fine.

  4. gustavcke commented on Jul 31, 2026

    @gustavcke

    Confirming this with a second, independent sample — different device, different file — and closing the 1.22.x question you flagged as untested.

    Same signature: bad seek offsets cascading down to EOF+1 and topping out at exactly EOF+32, terminating in No 'ftyp' box (2.102) on a file whose ftyp box is demonstrably present at offset 0. Sample is an iPhone HEIC (iOS 27 beta), 1,429,726 bytes, gain-map HDR capture (tmap item + hidden gain map + SDR base). Consumer is libvips 8.18.4 here too.

    Bisected across six releases, one file, one hash:

    libheif Result
    1.19.7 PASS
    1.20.2 PASS
    1.21.2 FAIL
    1.22.2 (latest 1.22.x) PASS
    1.23.0 PASS
    1.23.1 PASS

    So the regression appears confined to 1.21.x and is already fixed by 1.22.2 — no downgrade needed, which matters given the security fixes that landed after 1.21.2. Renders from 1.20.2, 1.22.2 and 1.23.1 are pixel-identical (RMSE 0 on every output).

    Immich v3.1.0 ships libheif 1.21.2, which is how this surfaced at scale — several hundred assets in one library, reported to the user as file corruption. Filed there as immich-app/immich#30459, which has the sample file and side-by-side renders. Happy to attach them here as well if useful for a regression fixture.

  5. gustavcke commented on Jul 31, 2026

    @gustavcke

    Attaching the sample and renders, plus what I found dissecting the file — this seemed the more useful place for the format internals than the downstream tracker.

    Image

    ios27heiftest.zip — the regression sample. iPhone, iOS 27 public beta 2. SHA-256 01008309d921fa812b3f6620583303b384564ae4271a0f9c638fe7640422ddf6, 1,429,726 bytes. — the same file rendered natively by 1.20.2, 1.22.2 and 1.23.1. All three outputs pixel-identical (RMSE 0). Only 1.21.2 fails.

    Container structure

    Top-level boxes, parsed directly from the bytes — they account for the file exactly, which seemed worth establishing given that 1.21.2 seeks to EOF+32:

    offset=0        size=40        type=ftyp    ends=40
    offset=40       size=5515      type=meta    ends=5555
    offset=5555     size=1424171   type=mdat    ends=1429726   == EOF
    

    ftyp at offset 0 (00 00 00 28 66 74 79 70 68 65 69 63): major brand heic, compatible mif1, tmap, MiHE, miaf, MiHB, heic. MiHE and MiHB don't appear in the MP4RA registry; unrecognised compatible brands should be ignored by conformant readers, and the versions that work do appear to ignore them.

    The rest of this section is from a GPAC mp4box -info / -diso pass rather than my own parser — GPAC read the container without box errors. 86 items, no moov. The three that matter:

    Item Role As reported
    41 primary, grid 4032 × 2268 from 40 × hvc1 tiles (512 × 512), Main Still Picture (profile_idc=3), 4:2:0 8-bit, embedded Display P3 ICC (colr prof)
    84 hidden, grid 4032 × 2268 gain map, 40 further tiles, HEVC Range Extensions (profile_idc=4), monochrome 4:0:0, colr CICP 2/2/2
    85 tmap 10-bit, dimg-derived from both 41 and 84; colr CICP 12/16/6 (Display P3 / PQ / BT.601), clli MaxCLL 203 / MaxPALL 64

    Plus Exif (42) and an XMP mime item (43), both cdsc-linked to 41 and 85.

    On the gain-map metadata: walking ipco, its declared 1292 bytes are accounted for by 13 named boxes (2 × colr, clli, 2 × ispe, irot, 3 × pixi, 2 × hvcC) with none left over, and a raw byte scan for the usual markers (gmap, hdrgm, mdta, com.apple) found nothing. So I could not locate ISO 21496-1 tone-mapping parameters in the standardised property location. I can't rule out that they sit somewhere I didn't inspect — the Exif item is opaque to this analysis — only that a reader looking for them in the box structure won't find them here. Consistent with that: reconstructing the HDR result required implementing the combine by hand rather than reading parameters out of the file.

    Gain map / tmap handling

    Across 1.20.2, 1.22.2 and 1.23.1 — same suite, same file, same build flags — every measurement was identical: SDR decode works, one top-level image reported, get_image_handle(84) (gain map) works, get_image_handle(85) (the tmap) fails, and manual ISO 21496-1 reconstruction succeeds with byte-identical output across all three (peak 2.7422×, mean boost 1.7621, 25.61 % of subpixels above SDR white). Nothing in that behaviour varies across the range, unlike the decode failure, which is confined to 1.21.x.

    Correction to my own draft: I should have checked the tracker before speculating about that tmap result. This is already known and tracked — #1685 covers Apple HEIC gain-map/HDR support, and #1503 implements tmap handling per ISO 21496-1 behind -DWITH_EXPERIMENTAL_GAIN_MAP=1. My builds were stock, which presumably explains the get_image_handle(85) failures above; I will retest with that flag enabled and report back if the result is interesting. The gain-map symbols in mini.cc belong to the separate AVIF MinimizedImageBox track, not to this path.

    Minor side-observation

    For gain-map item 84, 1.20.2 returns nclx CICP primaries=2 / transfer=2 / matrix=2 / full_range=1, matching the colr 2/2/2 in the file. 1.22.2 and 1.23.1 both return (none) — all-unspecified nclx profiles appear to have stopped being surfaced somewhere in 1.21.x–1.22.2. Harmless in itself, since 2/2/2 carries no information. I could not find a PR or issue covering this specific API-level change, so I am flagging rather than asserting it is unintended — if it is deliberate, disregard.

    (The clli and named colour-primaries output in heif-info is #1789, so that part is expected behaviour rather than an incidental change.)

  6. farindk commented on Sep 1, 2026

    @farindk
    Member

    Thank you for the very detailed reports and the bisection, they made this straightforward to
    track down. I can confirm the regression and confirm that it is fixed. Summary
    up front: the bug exists only in v1.21.0 to v1.21.2, it is fixed since v1.22.0, and it only
    triggers when LIBHEIF_SECURITY_LIMITS=off is set in the environment.

    Root cause

    v1.21.0 added a new security limit max_number_of_file_brands (default 1000) with a check in
    Box_ftyp::parse() (commit 720cad2, for #1633):

    if (n_minor_brands > limits->max_number_of_file_brands) { ... }

    This check did not follow the "0 means no limit" convention that all security limit fields use.
    With LIBHEIF_SECURITY_LIMITS=off, all limit fields are zero, so this check rejected every
    file whose ftyp box lists at least one compatible brand, which is essentially every HEIC/AVIF
    file. That is why thousands of files failed at once after the upgrade.

    Fixed in commit ef85bc8 ("fix reading ftyp box with disabled security limits"), first released
    in v1.22.0. This matches your bisection exactly (1.19.7/1.20.2 pass, 1.21.2 fails,
    1.22.2/1.23.x pass).

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