[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