Skip to content

CWE-135: Incorrect Calculation of Multi-Byte String Length

Overview

Incorrect calculation of multi-byte string length happens when code measures a string in one unit, bytes or characters, and treats the result as the other. The most common form is calling something like strlen() on text encoded as UTF-8, UTF-16, or another multi-byte encoding and assuming the result is a character count. MITRE scopes this weakness to C and C++, where the miscount directly drives buffer allocation and copy sizes; the same byte/character confusion also causes truncation and validation-bypass bugs in managed languages, even though it can't corrupt memory there.

Relationship to Other CWEs

This page's MITRE parent, CWE-682, has no page here, and MITRE records no direct relationship between CWE-135 and anything this corpus covers. The bullets below are the siblings under that uncovered parent plus the pages a miscount leads to.

Risk

High: In C/C++, a length miscalculation used to size a buffer or bound a copy writes past the end of the allocation or reads beyond it. The overwrite corrupts whatever memory follows and is the route to code execution; the over-read discloses it. In any language, truncating a multi-byte string on a byte boundary rather than a character boundary produces malformed text, and a length check that silently uses the wrong unit misfires in whichever direction the mismatch runs. A byte count tested against a character limit rejects legitimate non-ASCII input well short of the stated limit; a character count tested against a limit that is really in bytes lets through several times the data the sink was sized for.

Remediation Steps

Core Principle: Track byte length and character count as two distinct values, and use whichever one the specific operation actually needs - never assume they're interchangeable.

Trace the Data Path

  • Source: Untrusted or external text - user input, file content, network data - that may contain non-ASCII, multi-byte-encoded characters.
  • Sink: A buffer allocation, copy, or length-based validation check that consumes a "length" value computed from that text.
  • Data Flow / Missing Controls: Which counting function produced the length, and whether the sink expects bytes or characters - the miscalculation is almost always a mismatch between the two.

Use the Correct Unit for Each Operation (Primary Defense)

  • Buffer allocation and raw copy sizing (malloc, memcpy) must use the byte length.
  • User-facing limits (message length, display truncation) must use a real character count, obtained by decoding the encoding, not by counting bytes.
  • Never derive one from the other by assumption ("1 byte = 1 character") - that assumption only holds for pure ASCII.

Use Encoding-Aware Functions and Libraries

  • Prefer an API that is explicitly character-aware for the encoding in use - a Unicode library, or a language runtime with native Unicode strings - over hand-computing character counts from raw bytes.
  • Validate that byte sequences are well-formed for the declared encoding before trusting any count derived from them - an invalid sequence should be rejected, not counted optimistically.

Truncate on Character Boundaries, Not Byte Offsets

  • When a string must be cut to a length, use a character-aware truncation function so the cut can never land in the middle of a multi-byte sequence.
  • Track both counts through the operation if downstream code needs to know the resulting byte length as well as the character count.

Test with Multi-Byte Input

  • Test with strings where character count and byte count differ (CJK text, emoji, accented Latin characters) at exactly the length limit, one character over, and one byte over.
  • Test truncation with a multi-byte character positioned exactly at the cut point.
  • Re-scan to confirm the finding is resolved.

Common Vulnerable Patterns

// VULNERABLE - byte-counting function used as if it returned a character count
length = byte_count_of(input)          // e.g. strlen() - 11 for a 7-character UTF-8 string
dest = wide_buffer_of_bytes(length)    // `length` BYTES of storage, so length/2 wide elements
if length <= 10:
    accept(input)                       // a limit meant as "10 characters" now rejects valid
                                        // 7-character input, and its real ceiling is 10 bytes
convert_to_wide(input, dest, length)    // the capacity parameter counts wide ELEMENTS, so this
                                        // claims dest holds twice the elements it really does
// Attack: multi-byte input long enough to fill dest
// Result: the check enforces a limit nobody intended, and the conversion writes past dest

Why this is vulnerable: a byte count and a character count are both integers, and nothing in the type system, the variable name, or the call tells them apart. length is correct - it is genuinely the number of bytes - and every line after it is wrong for a different reason, which is why reviewing the counting function in isolation finds nothing.

The two failures point in opposite directions, and fixing one can create the other. Used as a limit, a byte count is stricter than intended: ASCII text behaves exactly as the author expected and non-ASCII text is rejected early, so the bug reads as an encoding complaint from users rather than as a defect. Used as a capacity, the same number is too small the moment the consumer counts something wider than a byte, and that is the direction that overruns a buffer. A codebase that switches one call site to characters to fix the first failure has usually just introduced the second somewhere downstream that was sized in bytes.

Secure Patterns

// SECURE - byte length and character count kept separate, each used where it belongs
byte_length = byte_count_of(input)          // for allocation/copy sizing
char_count  = decode_and_count_chars(input) // for user-facing limits, via a real decoder

if char_count > MAX_CHARACTERS:
    reject(input)

buffer = allocate(byte_length + terminator_size)
copy(buffer, input, byte_length)

Why this works: Allocation and copy sizes are always computed from the byte length, since that is what occupies memory, while any limit expressed in "characters" is enforced against a count produced by decoding the string rather than by assuming a fixed number of bytes per character. Because the two values are never substituted for each other, a multi-byte sequence can't be undercounted, overcounted, or split.

Common Pitfalls

  • Enlarging the buffer instead of fixing the count: making an undersized buffer bigger reduces how often the miscalculation is visible as a crash, but the length value feeding a validation check or truncation point downstream is still measuring the wrong unit, and the bug is only harder to trigger.
  • Fixing one call site's counting function but not its neighbors: switching a single allocation or check from byte-counting to character-counting (or the reverse) without auditing every other place that consumes the same length value reintroduces the mismatch one call away from the fix.
  • Validating length before decoding: checking a raw string's length against a limit before percent-decoding, transcoding, or otherwise normalizing it can pass input that expands or contracts into a different character count once actually processed.
  • Assuming a "Unicode-aware" library is safe without specifying the encoding: passing the wrong encoding hint to a conversion or counting function still miscounts multi-byte sequences - it just fails silently instead of obviously.

Language-Specific Guidance

  • C - buffer allocation/copy sizing bugs and Win32 MultiByteToWideChar/WideCharToMultiByte element-vs-byte-count mismatches
  • PHP - strlen()/substr() vs mb_strlen()/mb_substr() for length limits and truncation

Additional Resources