Safeguard
Application Security•

CWE-228 Improper Handling of Syntactically Invalid Structure

Malformed input that breaks a parser can crash a service or burn CPU. CWE-228 explained, with defensive parsing examples and how to test.

Safeguard Security Team
3 min read

CWE-228, Improper Handling of Syntactically Invalid Structure, is a class-level weakness. The product does not handle, or handles incorrectly, input that is not well formed with respect to the specification it is supposed to follow.

Every parser has it as a risk: protocol handlers, file readers, configuration loaders, JSON and XML processors, anything that reads data it did not generate itself.

How it happens

Developers test with valid input. A parser written for valid input often assumes fields exist, numbers are numbers and lengths match. When one of those assumptions fails, the code reaches a state nobody planned for.

MITRE's observed example is CVE-2004-0270: an anti-virus product hits an assert error when a line length is non-numeric.

# Vulnerable: assumes the header is a well-formed number
def read_record(stream):
    header = stream.readline()
    length = int(header.split(b":")[1])    # IndexError or ValueError on bad input
    return stream.read(length)

A malformed header raises an exception that the caller may not catch, and the process dies.

What an attacker gains

MITRE lists integrity and availability. Invalid input can place the system in an unexpected state, crash it, make it exit or restart, or consume CPU and other resources.

For a service that other systems depend on, a single malformed message that kills the process is an easy denial of service.

How to find it

MITRE names static analysis. SAST can find some instances by building a model of data flow and control flow and searching for risky patterns that connect sources of input to sinks. MITRE rates its effectiveness as high for this entry.

Add testing by feeding broken input:

  1. Truncate valid messages at every byte offset.
  2. Replace numbers with text, remove delimiters, repeat fields, and add stray bytes.
  3. Run a fuzzer against each parser and watch for crashes and long run times.
  4. Check what happens to one bad record in a batch: skipped, rejected or fatal.

How to fix it

MITRE does not list mitigations, so these steps are practical ones:

  • Validate structure first. Check that required parts exist and have the right type before using them.
  • Fail closed and fail small. Reject the bad item with a clear error. Do not let it take down the process.
  • Use mature parsers for standard formats rather than hand-rolled splitting.
  • Bound the work. Set limits on size, depth and time.
  • Do not use assertions as input validation. An assert is a programmer check, and it can be disabled or can abort the process.
def read_record(stream):
    header = stream.readline()
    parts = header.split(b":")
    if len(parts) != 2 or not parts[1].strip().isdigit():
        raise BadRecord("invalid header")
    length = int(parts[1])
    if length > MAX_RECORD:
        raise BadRecord("record too large")
    return stream.read(length)

The caller catches BadRecord, logs it without echoing secrets, and moves on to the next record.

Parsers are also often third-party libraries. Keeping them patched matters, since parser crashes are a frequent source of CVEs. A free in-browser dependency scan shows known CVEs for up to 100 dependencies, with no login and the file staying on your machine.

Quick check

Is it enough to test only with valid input?

No. This weakness only appears when input breaks the specification, so tests need broken input.

Why not let the process crash and restart?

A crash drops in-flight work and gives an attacker a cheap way to repeat it. Rejecting the bad input keeps the service up.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.