February 13, 2025·6 min read·engineeringcmuslsecurityunicode

How musl iconv turned an invalid character into an out-of-bounds write

CVE-2025-26519 crosses the boundary between a character decoder and UTF-8 output. Its two patches repair input validation and protect output pointer arithmetic independently.

A character decoder and an output encoder can each look straightforward while their shared assumption makes memory corruption possible. The musl iconv vulnerability disclosed on February 13 follows that pattern. Incorrect EUC-KR input validation allowed a decoder to produce a value that the UTF-8 encoder could not represent. The encoder returned an error value, and the conversion loop used that value to adjust its output pointer and remaining buffer length.

Rich Felker describes the exposure in the CVE-2025-26519 advisory. Nick Wellnhofer discovered and reported the issue. The advisory includes two patches with different purposes. One repairs the EUC-KR decoder. The other prevents a decoder mistake of this kind from corrupting the output buffer accounting. Reading both patches explains why a bounds error in character conversion deserves attention beyond applications that explicitly present themselves as text-processing software.

C was my first language, and I still value how much of its behavior can be understood by reading a small implementation. This is also the kind of failure that makes that reading demanding. An internal value has a semantic constraint, an error has an unsigned representation, and a later pointer adjustment relies on the constraint having held. The code that fails to validate the value is separated from the operation that turns the failure into memory corruption.

The encoding choice is part of the attack input

The advisory gives specific conditions for exposure. An application must open a conversion with UTF-8 as the output encoding and EUC-KR as the input encoding. It must then process untrusted input through the resulting conversion descriptor. Merely linking an affected musl does not establish that this path is reachable in a particular application.

The common scenario in the advisory is an application that accepts the declared charset from untrusted material. XML, HTML, and MIME-encoded email can carry that declaration. A program that converts arbitrary declared encodings into UTF-8 can therefore let input select the decoder as well as supply the bytes it decodes. The resulting vulnerability is in the library, but the conversion policy of the application determines whether an attacker can reach it.

That distinction matters when evaluating an application that normally processes UTF-8. Its internal representation may be UTF-8 precisely because an earlier conversion step accepts other encodings. Looking only at the representation after conversion misses the operation that consumes the hostile bytes. The advisory identifies both the selected input encoding and the bytes passed through it as relevant to exposure.

The affected history is unusually long. Felker states that the vulnerable code dates to the addition of EUC-KR support in musl 0.9.13. Versions from 0.9.13 through 1.2.5 are affected. As of this disclosure, the advisory says future releases beginning with 1.2.6 will contain the fix. That is a statement about the planned fixed release line, not a claim that 1.2.6 has already shipped today.

An invalid lead byte reaches a conversion table

The decoder patch identifies an incorrect bound on the lead byte. Its explanation gives "\xc8\x41" as an example of invalid input that should produce an encoding error. Instead, the old path could load outside the ksc conversion table. That out-of-bounds load is the first memory error in the sequence described by the maintainer.

The patch changes the condition used after the decoder adjusts its intermediate values. The corrected condition limits the lead-byte value against 0xc6-0x81 and handles the boundary case with a separate condition on the second byte. This is a repair to the accepted input domain. Bytes outside that domain go to the illegal-sequence path instead of contributing an invalid table access.

The value obtained from the table access is then important to a separate component. UTF-8 encoding expects a valid Unicode scalar value. An out-of-bounds load does not have the semantic guarantees of a valid entry in the conversion table, so the decoder bug can violate that expectation. The advisory does not require every invalid load to produce the same bad character code. It explains that the worst case produces a value outside the valid domain of the encoder.

This separation makes the patch easier to assess. The EUC-KR correction addresses the input validation error that permits the bad lookup. It does not depend on arranging a particular later output-buffer layout. Rejecting the invalid input removes the original path into the invalid internal state of the decoder.

The error value breaks output accounting

The UTF-8 hardening patch explains the next assumption. The conversion code expects successful encoding to produce a byte count between one and four. If the character code is invalid, the conversion routine returns (size_t)-1. The subsequent code uses the returned count to advance the output pointer and subtract from the remaining capacity.

Under the invalid-input condition, that returned value is not a byte count that may safely update the conversion state. The patch explanation says the arithmetic can move the output position backwards, potentially before the beginning of the buffer. That movement occurs without the failed conversion having stored any bytes. Later processing can then write through output state that no longer describes the supplied buffer.

The hardening change checks k > 4 before updating the pointer and byte count. It sends the operation to the illegal-sequence path if that condition holds. The check covers the error representation because (size_t)-1 is outside the expected successful range. It makes the assumption explicit at the location where the value is about to affect memory access.

This is why the two patches are useful together. Correct input validation prevents the reported decoder failure. The output-side check prevents that class of decoder error from reaching the pointer arithmetic unchecked. The comment in the second patch describes the condition as something that should be unreachable, while still handling it because of the consequences if an upstream invariant fails.

I find the output-side change persuasive because its scope is narrow and its reason is visible. It checks the result immediately before the conversion loop changes buffer state. That is a concrete defense at the dependency between components, with no claim that the rest of the conversion implementation has become memory-safe.

Repairing the library does not repair every executable

The advisory tells users of musl-based distributions to obtain a patched package through their distribution update channels. It also asks users building musl themselves to apply both source patches. Those instructions follow the ownership of the affected code, but a deployed program can contain its own copy of that code through static linking.

Felker therefore discusses statically linked binaries separately. If an executable includes the vulnerable implementation, replacing another library file on the machine does not replace those embedded instructions. Relinking the executable against fixed code addresses that copy. A fleet inventory that only records the installed distribution package can consequently miss an affected standalone binary.

For a statically linked binary that cannot easily be relinked, the advisory provides a specific emergency mitigation. It replaces the contiguous names for EUC-KR and its aliases in the binary with names that cannot match. The aliases listed include euckr, ksc5601, ksx1001, and cp949. Disabling those names makes the vulnerable decoder unreachable through encoding selection.

That mitigation has a functional cost. The executable loses support for decoding EUC-KR text through those aliases. Substituting punctuation works because the encoding-name handling strips non-alphanumeric ASCII characters, as the advisory explains. The replacement entries therefore cannot match a requested charset. This is a targeted binary intervention described by the maintainer, with a specific effect on supported conversions.

The repair decision depends on the deployed artifact. A patched shared library can supply fixed conversion code to programs that use it. A statically linked executable needs its embedded implementation replaced or the described decoding path disabled. Both cases concern the same source defect, but the distribution of executable code changes how the fix reaches a running application.

The disclosure supplies enough detail to distinguish those cases without assuming an exploit outcome that it does not establish. Its confirmed issue is an input-controlled out-of-bounds write in the specified conversion path. The useful engineering detail is the complete dependency chain. Input validation admits a bad table access, a resulting invalid scalar reaches UTF-8 conversion, and an unchecked error value changes output-buffer state. The published fixes address the start of that chain and the point where its consequences become a write outside the buffer.