Fix: divide-by-zero in CIccPcsXform::pushXyzLumToXyz on zero illuminant luminance (#1406)#1408
Merged
Merged
Conversation
…luminant luminance (#1406) When luminance-based PCS matching is requested (+1000 rendering intent), the CMM inserts an inverse-luminance PCS scale step. pushXyzLumToXyz computed the scale as 1.0f / XYZLum[1], where XYZLum[1] is the destination profile's illuminant luminance (Y). A malformed profile can drive that Y to zero — the fuzz PoC carries a spectralViewingConditions (svcn) tag whose IlluminantXYZ is (0,0,0) — so 1.0/0 is a divide by zero that UBSan's float-divide-by-zero check flags, producing an infinite scale that then corrupts every PCS sample: IccCmm.cpp:3048:30: runtime error: division by zero in CIccPcsXform::pushXyzLumToXyz(IIccProfileConnectionConditions*) #1 CIccPcsXform::pushXYZConvert -> Connect -> CheckPCSConnections -> Begin Bisected to 3b6089e (Nov 2017). The fix falls back to an identity (1.0) scale when the luminance is not a positive, finite value (std::isfinite && > 0): there is no meaningful luminance normalization to undo, and a no-op leaves the PCS data intact rather than poisoning it with inf/NaN. Regression test (iccdev.issue-1406-roundtrip-xyzlum-dbz) replays the committed PoC through iccApplyProfiles with a +1000 luminance intent and fails if the divide-by-zero reappears. Verified under a float-divide-by-zero UBSan build: FAIL on master (runtime error at IccCmm.cpp:3048), PASS on fix. The check only exists when the tool is built with -fsanitize=float-divide-by-zero (the ci-regression-container build; plain ENABLE_UBSAN does not enable it), so the test SKIPS on other builds rather than report a misleading pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ChrisCoxArt
approved these changes
Jun 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1406.
The defect (UBSan float-divide-by-zero)
When luminance-based PCS matching is requested (the
+1000rendering-intent flag), the CMM inserts an inverse-luminance PCS scale step.CIccPcsXform::pushXyzLumToXyzbuilt that step as:XYZLum[1]is the destination profile's illuminant luminance (Y), from its connection conditions. A malformed profile can drive it to zero — @xsscx's fuzz PoC carries aspectralViewingConditions(svcn) tag whoseIlluminantXYZis(0,0,0)— so1.0/0is a divide by zero. UBSan flags it, and the resulting infinite scale corrupts every PCS sample pushed throughpushScale3:Bisected to
3b6089e(Nov 2017).The fix
Fall back to an identity (
1.0) scale when the luminance is not a positive, finite value:There is no meaningful luminance normalization to undo for a degenerate illuminant, and a no-op leaves the PCS data intact rather than poisoning it with inf/NaN.
std::isfinitematches the existing guard style in this file (e.g.IccCmm.cpp:1489). The sibling forward steppushXyzToXyzLummultiplies byYand is not a divide, so it has no divide-by-zero exposure.Regression test
iccdev.issue-1406-roundtrip-xyzlum-dbzreplays the committed PoC (.github/ci/test-data/ub-roundtrip-xyzlum-dbz-1406.icc) throughiccApplyProfileswith a+1000luminance intent over the always-presentsmCows380_5_780.tif:IccCmm.cpp:3048:30: runtime error: division by zeroThe float-divide-by-zero check only exists when the tool is built with
-fsanitize=float-divide-by-zero(theci-regression-containerbuild — plainENABLE_UBSANdoes not enable it), so the test gates on the sanitizer inCMakeCache.txtand SKIPS on other builds rather than report a misleading pass — same approach as the #1342/#1343 integer-sanitizer regression.Verified locally under a
-fsanitize=float-divide-by-zerobuild: FAIL on master, PASS on the fix.🤖 Generated with Claude Code