Dear GNU Libidn maintainers,

I found a use of uninitialized memory in Libidn's ToUnicode round-trip 
verification while fuzzing the project with DMSAN, my sanitizer tool for 
detecting uses of uninitialized memory. The issue is in the `lib/idna.c` 
library code; I use `idna_to_unicode_8z8z()` below because it is a small public 
API path that reaches the affected code.

The root cause is in `idna_to_unicode_internal()`. It removes the `xn--` 
prefix, decodes the remaining Punycode suffix, calls `idna_to_ascii_4i()` into 
a local stack buffer, and then compares against `tmpout + 
strlen(IDNA_ACE_PREFIX)`:

```c
char tmpout[64];

rc = idna_to_ascii_4i(out, *outlen, tmpout, flags);
if (c_strcasecmp(utf8in, tmpout + strlen(IDNA_ACE_PREFIX)) != 0)
    return IDNA_ROUNDTRIP_VERIFY_ERROR;
```

The code assumes that `idna_to_ascii_4i()` wrote an ACE string beginning with 
`xn--` into `tmpout`. That assumption is false when the decoded label is all 
ASCII, because the ToASCII path leaves ASCII labels unchanged:

```c
for (i = 0; src[i]; i++) {
    if (src[i] > 0x7F)
        inasciirange = 0;
    if (i < 64)
        out[i] = src[i];
}
if (i < 64)
    out[i] = '\0';
else
    return IDNA_INVALID_LENGTH;
if (inasciirange)
    goto step8;
```

If the decoded ASCII label is shorter than 4 characters, `tmpout + 
strlen("xn--")` points past the initialized string into the uninitialized tail 
of `tmpout`. `c_strcasecmp()` then reads and branches on stale stack bytes.

In the included PoC, an earlier label in the same domain leaves matching bytes 
in the same stack slot. A later invalid ACE label that should remain unchanged 
is then decoded as a short ASCII label.

Native output from the primary PoC shows:

```text
input:    xn--com-
expected: xn--com-
actual:   xn--com-
rc:       0
OK

input:    xn--XXXXcom--.xn--com-
expected: xn--XXXXcom--.xn--com-
actual:   xn--XXXXcom--.com
rc:       0
BUG: output changed

input:    xn--XXXXabcd--.xn--abcd-
expected: xn--XXXXabcd--.xn--abcd-
actual:   xn--XXXXabcd--.xn--abcd-
rc:       0
OK
```

The middle case is the bug: the second label is `xn--com-`, which does not pass 
ToUnicode round-trip verification and should stay unchanged. Instead, the 
output label becomes `com`. The `abcd` case stays unchanged because the decoded 
ASCII label is long enough that `tmpout + 4` no longer points into the 
uninitialized tail.

The attack scenario is a hostname or email domain supplied to an application 
that normalizes user input with Libidn ToUnicode before making a security 
decision. The victim application may use the normalized domain for an allowlist 
or blocklist check, routing, cache identity, logging, or a warning shown to 
users. With default flags, the concrete effect is that an invalid ACE label 
such as `xn--com-`, `xn--net-`, `xn--org-`, `xn--www-`, `xn--api-`, `xn--ssh-`, 
or `xn--123-` can be normalized to `com`, `net`, `org`, `www`, `api`, `ssh`, or 
`123` when matching stale stack bytes are present.

The primary PoC uses one domain string where an earlier label supplies those 
bytes. I also observed that the same stale bytes can carry across separate API 
calls in native builds using gcc. For example, after one call processes 
`xn--XXXXwww--`, a later separate call processing `xn--www-` can return `www`; 
after `xn--XXXX123--`, a later `xn--123-` can return `123`. This reproduced 
with the 1.38 and 1.43 release tarballs and with current git `ef65bfd`. This 
behavior is stack layout dependent, but it is relevant for long-running 
processes that normalize multiple names supplied by an attacker. The attached 
`poc_cross_call.c` demonstrates it. The reproduction uses native Libidn builds 
and public APIs.

The same source pattern is visible at least as far back as the public tag 
`libidn-0-1-15` from 2003-06-07 and remains present through `v1.43` from 
2025-03-21 and current git `ef65bfd` from 2026-03-22. Early releases used 
`idna.c` at the repository root and named the helper `idna_to_ascii()`, but the 
same stack buffer and `tmpout + strlen(IDNA_ACE_PREFIX)` comparison were 
already present. I directly reproduced the behavior on Ubuntu `libidn12 
1.38-4ubuntu1` (`libidn.so.12.6.3`) and on a build from current source.

I checked current Libidn2 source as well; its A-label round-trip check is 
implemented in different code and I did not find the same `tmpout + 
strlen(IDNA_ACE_PREFIX)` pattern there.

I am not claiming code execution, invalid writes, native crashes, or heap 
disclosure. Concretely, this is a use of uninitialized memory (CWE-457), 
detected by DMSAN. It can still affect callers that trust Libidn's normalized 
domain string for security decisions. I defer to your judgment on the final 
classification.

My suggested fix is to compare the full original ACE label against the full 
ToASCII result, instead of assuming the ToASCII result begins with `xn--`. RFC 
3490 ToUnicode step 7 describes comparing the ToASCII result with the saved 
copy from step 3, which is the full original label before removing the ACE 
prefix. A smaller defensive check would be to reject the round-trip 
verification if `tmpout` does not start with `IDNA_ACE_PREFIX` before using 
`tmpout + strlen(IDNA_ACE_PREFIX)`:

```c
if (c_strncasecmp(tmpout, IDNA_ACE_PREFIX, strlen(IDNA_ACE_PREFIX)) != 0 ||
    c_strcasecmp(utf8in, tmpout + strlen(IDNA_ACE_PREFIX)) != 0)
    return IDNA_ROUNDTRIP_VERIFY_ERROR;
```

I would not recommend simply initializing `tmpout` to zero; that avoids the 
uninitialized read but does not address the underlying assumption that the 
ToASCII result always contains the ACE prefix.

The attached package contains the PoCs and a README with native and optional 
Docker reproduction commands. The reproduction uses public Libidn APIs.

Could you please review whether you consider this a Libidn bug and whether this 
classification seems reasonable? If you consider it security relevant, would 
you also be willing to help request a CVE? If fixed, I would appreciate 
attribution as "Reported by DMSAN (Differential Memory Sanitizer)". In my 
paper, this would appear as one entry in the evaluation, described as a use of 
uninitialized memory in Libidn's ToUnicode round-trip verification, without 
overstating the impact.

Best,

Jiami (DMSAN)

Attachment: libidn_tounicode_uninitialized_memory_pocs_20260519.tar.gz
Description: GNU Zip compressed data

  • Sec... jiami3us
    • ... Discussion list for GNU Internationalized Domain Name library (Libidn)

Reply via email to