Skip to content

Fix: signed atoi() -> unsigned wrap across IccTagXml ParseXml handlers (#1346)#1349

Merged
xsscx merged 1 commit into
InternationalColorConsortium:masterfrom
colourbill-ctrl:fix-1346-imageparse-atoi-unsigned-sweep
Jun 14, 2026
Merged

Fix: signed atoi() -> unsigned wrap across IccTagXml ParseXml handlers (#1346)#1349
xsscx merged 1 commit into
InternationalColorConsortium:masterfrom
colourbill-ctrl:fix-1346-imageparse-atoi-unsigned-sweep

Conversation

@colourbill-ctrl

Copy link
Copy Markdown
Contributor

Summary

Fixes the tracking issue #1346 — the pervasive atoi() (signed) → unsigned icUIntNN pattern in IccXML/IccLibXML/IccTagXml.cpp that #1342/#1343 were two instances of. A negative XML attribute wrapped to a large unsigned value: caught by UBSan's implicit-integer-sign-change where the conversion was implicit (Class A), and silently where an explicit (icUIntNN) cast already suppressed the check (Class B, invisible to the fuzzer).

Change

Adds one file-local helper and routes all 19 affected sites through it:

// Floors negative (invalid) input to 0 so the stored value is always defined.
static icUInt32Number icXmlAttrToUInt(const char *szValue)
{
  int nValue = atoi(szValue);
  return nValue < 0 ? 0u : (icUInt32Number)nValue;
}
  • Class A (11 implicit sites): cicpFields (ColorPrimaries/TransferCharacteristics/MatrixCoefficients/VideoFullRangeFlag), SparseMatrixArray (outputChannels, rows, cols ×2), MultiProcessElement (input/outputChannels), GamutBoundaryDesc (PCS/Device channels). 8/16-bit targets keep their explicit width cast, which also suppresses implicit-integer-truncation.
  • Class B (6 audit-only sites): spectral/bispectral steps, m_reserved2/m_reserved3, m_nReserved2, nGridGranularity. Same wrap bug, no sanitizer signal — fixed for consistency since no fuzzer will ever resurface them.
  • Out of scope: the two SeamlessIndicator image-tag sites are left to Fix: signed->unsigned SeamlessIndicator conversion in image ParseXml (#1342, #1343) #1344 (same clamp, in flight); Class C (enum/float/bounds-checked int locals) is unchanged.

Only behavioral change: a negative attribute now yields a well-defined 0 instead of a wrapped maximum.

Verification (clang-18, -DENABLE_ASAN=ON -DENABLE_UBSAN=ON -DENABLE_INTEGER_SANITIZER=ON)

  • PoC cicpFields ColorPrimaries="-1"pre-fix: IccTagXml.cpp:1172: runtime error: implicit conversion from type 'int' of value -1 ... changed the value to 255; post-fix: clean, profile parses and saves.
  • Valid ColorPrimaries="9" parses unchanged (value passes through; helper only floors negatives).
  • Full file compiles clean under the integer sanitizer.

The deterministic regression (script + PoC + CMake registration) lands in a companion test PR, since it touches the fork-gated CI/test paths.

🤖 Generated with Claude Code

InternationalColorConsortium#1346)

Sweep of the pattern reported as InternationalColorConsortium#1342/InternationalColorConsortium#1343: XML attributes parsed with
atoi() (signed int) were stored into unsigned icUIntNN members/locals, so a
negative attribute wrapped to a large unsigned value.  Where the conversion
was implicit, UBSan's implicit-integer-sign-change flagged it (that is how the
two reported sites surfaced); where an explicit (icUIntNN) cast was already
present the same wrap happened silently, invisible to the sanitizer.

Adds a single file-local helper, icXmlAttrToUInt(), that parses the value and
floors any negative (invalid) input to 0, and routes all 19 affected sites
through it (11 implicit "Class A" sites + the audit-only "Class B" sites that
the fuzzer can never reach).  8- and 16-bit targets keep their explicit width
cast, which both narrows the value and suppresses implicit-integer-truncation,
so the only behavioral change is that a negative attribute now yields a
well-defined 0 instead of a wrapped maximum.

The two SeamlessIndicator sites (image tags) are intentionally left to InternationalColorConsortium#1344,
which clamps them in the same spirit; the Class C sites (enum / float / int
locals with their own bounds checks) are out of scope and unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@xsscx xsscx left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2026-06-14 19:57:01 UTC

@xsscx xsscx added the Merge Ready Approved, mergeable, and all CI checks passed label Jun 14, 2026
@xsscx xsscx merged commit 968e44c into InternationalColorConsortium:master Jun 14, 2026
31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Merge Ready Approved, mergeable, and all CI checks passed Source C or C++ source code changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants