[PATCH v3] Use pending character state in IBM1390, IBM1399 character sets (CVE-2026-4046)

Florian Weimer fweimer@redhat.com
Fri Apr 10 19:56:26 GMT 2026


* Carlos O'Donell:

> On 4/9/26 8:32 AM, Florian Weimer wrote:
>> Follow the example in iso-2022-jp-3.c and use the __count state
>> variable to store the pending character.  This avoids restarting
>> the conversion if the output buffer ends between two 4-byte UCS-4
>> code points, so that the assert reported in the bug can no longer
>> happen.
>
> Looking forward to a v4.
>
>> Even though the fix is applied to ibm1364.c, the change is only
>> effective for the two HAS_COMBINED codecs for IBM1390, IBM1399.
>> The test case was mostly auto-generated using
>> claude-4.6-opus-high-thinking, and composer-2-fast shows up in the
>> log as well.  During review, gpt-5.4-xhigh flagged that the original
>> version of the test case was not exercising the new character
>> flush logic.
>
> Please add the following tag to your commit message?
> ~~~
> Assisted-by: Claude:claude-opus-4-6
> ~~~
> This follows Linux kernel convention and acts as due diligence in our
> record keeping that this contribution is a mix of human and machine
> generated content which is still copyrightable.

I feel more comfortable documenting this in unstructured text, telling
what I see in the Anysphere dashboard as the models used.  This avoids
the need to define common names for model names.  The names are likely
to be imprecise anyway.

> What happens when we don't make forward progress?
>
> Do we expect the test to loop forever and timeout or should we just
> check produced == 0?
>
> Noted by claude-opus-4.6.

The test timeout would catch it.

The combining character tables look like this (IBM1390 followed by IBM 1399):

  [0xecb5 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x304b, .res2 = 0x309a },
  [0xecb6 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x304d, .res2 = 0x309a },
  [0xecb7 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x304f, .res2 = 0x309a },
  [0xecb8 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x3051, .res2 = 0x309a },
  [0xecb9 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x3053, .res2 = 0x309a },
  [0xecba - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x30ab, .res2 = 0x309a },
  [0xecbb - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x30ad, .res2 = 0x309a },
  [0xecbc - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x30af, .res2 = 0x309a },
  [0xecbd - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x30b1, .res2 = 0x309a },
  [0xecbe - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x30b3, .res2 = 0x309a },
  [0xecbf - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x30bb, .res2 = 0x309a },
  [0xecc0 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x30c4, .res2 = 0x309a },
  [0xecc1 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x30c8, .res2 = 0x309a },
  [0xecc2 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x31f7, .res2 = 0x309a },
  [0xecc3 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x00e6, .res2 = 0x0300 },
  [0xecc4 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x0254, .res2 = 0x0300 },
  [0xecc5 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x0254, .res2 = 0x0301 },
  [0xecc6 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x028c, .res2 = 0x0300 },
  [0xecc7 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x028c, .res2 = 0x0301 },
  [0xecc8 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x0259, .res2 = 0x0300 },
  [0xecc9 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x0259, .res2 = 0x0301 },
  [0xecca - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x025a, .res2 = 0x0300 },
  [0xeccb - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x025a, .res2 = 0x0301 },
  [0xeccc - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x02e9, .res2 = 0x02e5 },
  [0xeccd - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x02e5, .res2 = 0x02e9 }

  [0xecb5 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x304b, .res2 = 0x309a },
  [0xecb6 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x304d, .res2 = 0x309a },
  [0xecb7 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x304f, .res2 = 0x309a },
  [0xecb8 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x3051, .res2 = 0x309a },
  [0xecb9 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x3053, .res2 = 0x309a },
  [0xecba - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x30ab, .res2 = 0x309a },
  [0xecbb - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x30ad, .res2 = 0x309a },
  [0xecbc - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x30af, .res2 = 0x309a },
  [0xecbd - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x30b1, .res2 = 0x309a },
  [0xecbe - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x30b3, .res2 = 0x309a },
  [0xecbf - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x30bb, .res2 = 0x309a },
  [0xecc0 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x30c4, .res2 = 0x309a },
  [0xecc1 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x30c8, .res2 = 0x309a },
  [0xecc2 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x31f7, .res2 = 0x309a },
  [0xecc3 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x00e6, .res2 = 0x0300 },
  [0xecc4 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x0254, .res2 = 0x0300 },
  [0xecc5 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x0254, .res2 = 0x0301 },
  [0xecc6 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x028c, .res2 = 0x0300 },
  [0xecc7 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x028c, .res2 = 0x0301 },
  [0xecc8 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x0259, .res2 = 0x0300 },
  [0xecc9 - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x0259, .res2 = 0x0301 },
  [0xecca - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x025a, .res2 = 0x0300 },
  [0xeccb - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x025a, .res2 = 0x0301 },
  [0xeccc - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x02e9, .res2 = 0x02e5 },
  [0xeccd - __TO_UCS4_COMBINED_MIN] = { .res1 = 0x02e5, .res2 = 0x02e9 }

Only U+309A needs three bytes in UTF-8, the other combining characters
have a two-byte representation.  However, U+309A only follows a
character that has a three-byte representation.  A two-byte buffer
cannot store that first character, so U+309A never becomes pending.

I'm going to remove the loop from the test case and test this behavior
more directly.

Thanks,
Florian



More information about the Libc-alpha mailing list