You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
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
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
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):
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.
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.
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.
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:
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 (colrprof)
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.)
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).
Activity
bradh commentedon Jul 17, 2026
Can you upload a sample demonstrating the problem as a zipped file?
pimpenin commentedon Jul 18, 2026
IMG_1370-sample.zip
bradh commentedon Jul 18, 2026
I tested this with current master and it decoded fine.
gustavcke commentedon Jul 31, 2026
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 seekoffsets cascading down to EOF+1 and topping out at exactly EOF+32, terminating inNo 'ftyp' box (2.102)on a file whoseftypbox is demonstrably present at offset 0. Sample is an iPhone HEIC (iOS 27 beta), 1,429,726 bytes, gain-map HDR capture (tmapitem + hidden gain map + SDR base). Consumer is libvips 8.18.4 here too.Bisected across six releases, one file, one hash:
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.
gustavcke commentedon Jul 31, 2026
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.
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:
ftypat offset 0 (00 00 00 28 66 74 79 70 68 65 69 63): major brandheic, compatiblemif1,tmap,MiHE,miaf,MiHB,heic.MiHEandMiHBdon'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/-disopass rather than my own parser — GPAC read the container without box errors. 86 items, nomoov. The three that matter:hvc1tiles (512 × 512), Main Still Picture (profile_idc=3), 4:2:0 8-bit, embedded Display P3 ICC (colrprof)profile_idc=4), monochrome 4:0:0,colrCICP 2/2/2tmapdimg-derived from both 41 and 84;colrCICP 12/16/6 (Display P3 / PQ / BT.601),clliMaxCLL 203 / MaxPALL 64Plus 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 /
tmaphandlingAcross 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)(thetmap) 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
tmapresult. This is already known and tracked — #1685 covers Apple HEIC gain-map/HDR support, and #1503 implementstmaphandling per ISO 21496-1 behind-DWITH_EXPERIMENTAL_GAIN_MAP=1. My builds were stock, which presumably explains theget_image_handle(85)failures above; I will retest with that flag enabled and report back if the result is interesting. The gain-map symbols inmini.ccbelong 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
colr2/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
clliand named colour-primaries output inheif-infois #1789, so that part is expected behaviour rather than an incidental change.)return ftyp box parse errors instead of masking them as a missing fty…
heif_list_compatible_brands(): treat a brands security limit of 0 as …
farindk commentedon Sep 1, 2026
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=offis set in the environment.Root cause
v1.21.0 added a new security limit
max_number_of_file_brands(default 1000) with a check inBox_ftyp::parse()(commit 720cad2, for #1633):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).