Improper Validation of Consistency within Input

Description

Improper Validation of Consistency within Input occurs when a product receives inputs with multiple elements or fields that should align with each other but fails to validate or incorrectly validates this consistency. Complex structured inputs often contain interdependent fields—such as a count field followed by the specified number of items, or a size field that must match actual data length. When these inputs are inconsistent, attackers can trigger unexpected errors, cause incorrect actions, or exploit dormant vulnerabilities.

Risk

Inconsistent input validation has severe security implications. Buffer overflows from size mismatches. Data corruption from count errors. Denial of service possible. Logic errors may occur. Memory corruption attacks enabled. Security bypasses possible. Parsing vulnerabilities exposed. Application crashes likely.

Solution

Implement allowlist-based validation requiring strict conformance to specifications. Validate all relevant properties: length, type, value ranges, syntax, and inter-field consistency. Reject inputs failing validation rather than attempting repairs. Verify that count fields match actual item counts. Ensure size fields match actual data sizes. Cross-validate related fields.

Common Consequences

ImpactDetails
OtherScope: Other

Impact varies depending on how inconsistent input is processed.
IntegrityScope: Integrity

Inconsistent data can corrupt application state.
AvailabilityScope: Availability

Mismatched counts/sizes can cause crashes.

Example Code

Vulnerable Code

// Vulnerable: No consistency validation between fields

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    uint32_t count;
    uint32_t items[100];  // Fixed max, but 'count' could be larger
} vulnerable_packet_t;

// VULNERABLE: Count not validated against actual items
void vulnerable_process_packet(const uint8_t* data, size_t data_len) {
    // VULNERABLE: Read count without validation
    uint32_t count;
    memcpy(&count, data, sizeof(count));

    // VULNERABLE: No check that count matches available data
    // If count > (data_len - 4) / 4, we'll read past buffer
    for (uint32_t i = 0; i < count; i++) {
        uint32_t item;
        memcpy(&item, data + 4 + i * 4, sizeof(item));
        process_item(item);  // Buffer over-read if count is too large
    }
}

// VULNERABLE: Size field not validated against packet size
typedef struct {
    uint32_t total_size;
    uint32_t header_size;
    uint32_t data_size;
    uint8_t header[256];
    uint8_t data[1024];
} message_t;

void vulnerable_process_message(const message_t* msg, size_t actual_size) {
    // VULNERABLE: No validation of size fields

    // total_size could be inconsistent with actual_size
    // header_size + data_size could != total_size

    // VULNERABLE: Use provided sizes without validation
    process_header(msg->header, msg->header_size);
    process_data(msg->data, msg->data_size);
}

// VULNERABLE: Start/end block inconsistency
typedef struct {
    uint32_t start_block;
    uint32_t end_block;
    uint8_t block_data[4096];
} block_range_t;

void vulnerable_process_blocks(const block_range_t* range) {
    // VULNERABLE: No check that start < end
    // If start > end, the loop might underflow or skip incorrectly

    for (uint32_t i = range->start_block; i <= range->end_block; i++) {
        // If start=100, end=50, this never executes (or wraps around)
        process_block(i, range->block_data);
    }
}
# Vulnerable: Python with inconsistent field handling

import struct

# VULNERABLE: Length field not validated
def vulnerable_parse_tlv(data):
    """Parse Type-Length-Value structure."""
    offset = 0
    results = []

    while offset < len(data):
        # Read type and length
        tlv_type = data[offset]
        length = struct.unpack('>H', data[offset+1:offset+3])[0]

        # VULNERABLE: No check that length is consistent with remaining data
        value = data[offset+3:offset+3+length]

        # If length > remaining data, value is truncated silently
        results.append({'type': tlv_type, 'length': length, 'value': value})

        offset += 3 + length

    return results

# VULNERABLE: Array count not validated
def vulnerable_process_items(data):
    """Process items with count prefix."""
    count = struct.unpack('>I', data[:4])[0]

    # VULNERABLE: No validation that data contains 'count' items
    items = []
    offset = 4

    for i in range(count):
        # Will raise IndexError if not enough data
        # Or may read partial/wrong data
        item_size = struct.unpack('>I', data[offset:offset+4])[0]
        item_data = data[offset+4:offset+4+item_size]
        items.append(item_data)
        offset += 4 + item_size

    return items

# VULNERABLE: Checksum not validated
def vulnerable_process_packet(packet):
    """Process packet with checksum."""
    data = packet['data']
    checksum = packet['checksum']

    # VULNERABLE: Checksum not verified
    # Attacker can modify data and checksum independently

    process_data(data)  # Process potentially corrupted data
// Vulnerable: Java with inconsistent input handling

public class VulnerableParser {

    // VULNERABLE: Declared length not validated against actual
    public void processChunk(byte[] data) {
        int offset = 0;

        while (offset < data.length) {
            // Read declared chunk size
            int chunkSize = ByteBuffer.wrap(data, offset, 4).getInt();
            offset += 4;

            // VULNERABLE: No validation that chunkSize bytes are available
            byte[] chunk = new byte[chunkSize];

            // ArrayIndexOutOfBoundsException or reads wrong data
            System.arraycopy(data, offset, chunk, 0, chunkSize);
            offset += chunkSize;

            processChunkData(chunk);
        }
    }

    // VULNERABLE: Count field inconsistency
    public List<String> parseStringList(DataInputStream input) throws IOException {
        int count = input.readInt();  // Declared count
        List<String> result = new ArrayList<>();

        // VULNERABLE: Trust the count field
        for (int i = 0; i < count; i++) {
            int length = input.readInt();
            byte[] bytes = new byte[length];

            // VULNERABLE: No validation of length against available data
            input.readFully(bytes);
            result.add(new String(bytes, StandardCharsets.UTF_8));
        }

        return result;
    }
}

Fixed Code

// Fixed: Proper consistency validation

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

// FIXED: Validate count against available data
bool secure_process_packet(const uint8_t* data, size_t data_len) {
    // FIXED: Minimum size check
    if (data_len < sizeof(uint32_t)) {
        log_error("Packet too small for count field");
        return false;
    }

    // Read count
    uint32_t count;
    memcpy(&count, data, sizeof(count));

    // FIXED: Validate count against actual data
    size_t expected_size = sizeof(uint32_t) + count * sizeof(uint32_t);
    if (expected_size > data_len) {
        log_error("Count %u exceeds available data (have %zu, need %zu)",
                 count, data_len, expected_size);
        return false;
    }

    // FIXED: Also check for count overflow
    if (count > (SIZE_MAX - sizeof(uint32_t)) / sizeof(uint32_t)) {
        log_error("Count overflow");
        return false;
    }

    // Now safe to process
    for (uint32_t i = 0; i < count; i++) {
        uint32_t item;
        memcpy(&item, data + 4 + i * 4, sizeof(item));
        process_item(item);
    }

    return true;
}

// FIXED: Validate all size fields for consistency
typedef struct {
    uint32_t total_size;
    uint32_t header_size;
    uint32_t data_size;
    uint8_t header[256];
    uint8_t data[1024];
} message_t;

bool secure_process_message(const message_t* msg, size_t actual_size) {
    // FIXED: Validate actual_size is at least minimum
    if (actual_size < offsetof(message_t, header)) {
        log_error("Message too small");
        return false;
    }

    // FIXED: Validate total_size matches actual_size
    if (msg->total_size != actual_size) {
        log_error("Total size mismatch: declared %u, actual %zu",
                 msg->total_size, actual_size);
        return false;
    }

    // FIXED: Validate header_size
    if (msg->header_size > sizeof(msg->header)) {
        log_error("Header size exceeds buffer");
        return false;
    }

    // FIXED: Validate data_size
    if (msg->data_size > sizeof(msg->data)) {
        log_error("Data size exceeds buffer");
        return false;
    }

    // FIXED: Validate sizes are consistent
    size_t computed_size = offsetof(message_t, header) +
                           msg->header_size + msg->data_size;
    if (computed_size != msg->total_size) {
        log_error("Size fields inconsistent: header=%u, data=%u, total=%u",
                 msg->header_size, msg->data_size, msg->total_size);
        return false;
    }

    // Now safe to process
    process_header(msg->header, msg->header_size);
    process_data(msg->data, msg->data_size);
    return true;
}

// FIXED: Validate start/end consistency
typedef struct {
    uint32_t start_block;
    uint32_t end_block;
    uint8_t block_data[4096];
} block_range_t;

bool secure_process_blocks(const block_range_t* range) {
    // FIXED: Validate start <= end
    if (range->start_block > range->end_block) {
        log_error("Invalid block range: start %u > end %u",
                 range->start_block, range->end_block);
        return false;
    }

    // FIXED: Validate range is reasonable
    uint32_t count = range->end_block - range->start_block + 1;
    if (count > MAX_BLOCKS) {
        log_error("Block range too large: %u blocks", count);
        return false;
    }

    for (uint32_t i = range->start_block; i <= range->end_block; i++) {
        process_block(i, range->block_data);
    }

    return true;
}
# Fixed: Python with consistency validation

import struct
from typing import List, Dict, Any, Optional

# FIXED: TLV parsing with length validation
def secure_parse_tlv(data: bytes) -> List[Dict[str, Any]]:
    """Parse Type-Length-Value structure with validation."""
    offset = 0
    results = []

    while offset < len(data):
        # FIXED: Check minimum header size
        if offset + 3 > len(data):
            raise ValueError(f"Incomplete TLV header at offset {offset}")

        # Read type and length
        tlv_type = data[offset]
        length = struct.unpack('>H', data[offset+1:offset+3])[0]

        # FIXED: Validate length against remaining data
        if offset + 3 + length > len(data):
            raise ValueError(
                f"TLV length {length} exceeds remaining data "
                f"(offset={offset}, data_len={len(data)})"
            )

        value = data[offset+3:offset+3+length]

        # FIXED: Verify we got the expected amount
        if len(value) != length:
            raise ValueError(f"TLV value truncated: expected {length}, got {len(value)}")

        results.append({'type': tlv_type, 'length': length, 'value': value})
        offset += 3 + length

    return results

# FIXED: Array count validation
def secure_process_items(data: bytes, max_count: int = 1000) -> List[bytes]:
    """Process items with validated count prefix."""
    if len(data) < 4:
        raise ValueError("Data too short for count field")

    count = struct.unpack('>I', data[:4])[0]

    # FIXED: Validate count is reasonable
    if count > max_count:
        raise ValueError(f"Count {count} exceeds maximum {max_count}")

    items = []
    offset = 4

    for i in range(count):
        # FIXED: Check for item header
        if offset + 4 > len(data):
            raise ValueError(f"Incomplete item header at index {i}")

        item_size = struct.unpack('>I', data[offset:offset+4])[0]

        # FIXED: Validate item_size against remaining data
        if offset + 4 + item_size > len(data):
            raise ValueError(
                f"Item {i} size {item_size} exceeds remaining data"
            )

        item_data = data[offset+4:offset+4+item_size]
        items.append(item_data)
        offset += 4 + item_size

    # FIXED: Verify we consumed all data (no trailing garbage)
    if offset != len(data):
        raise ValueError(f"Unexpected trailing data: {len(data) - offset} bytes")

    return items

# FIXED: Checksum validation
import hashlib

def secure_process_packet(packet: Dict[str, Any]) -> bool:
    """Process packet with checksum validation."""
    if 'data' not in packet or 'checksum' not in packet:
        raise ValueError("Missing required fields")

    data = packet['data']
    expected_checksum = packet['checksum']

    # FIXED: Verify checksum before processing
    actual_checksum = hashlib.sha256(data).hexdigest()

    if actual_checksum != expected_checksum:
        raise ValueError(
            f"Checksum mismatch: expected {expected_checksum}, "
            f"computed {actual_checksum}"
        )

    # Now safe to process verified data
    process_data(data)
    return True
// Fixed: Java with consistency validation

public class SecureParser {

    private static final int MAX_CHUNK_SIZE = 1024 * 1024;  // 1 MB

    // FIXED: Validate declared length against actual
    public void processChunk(byte[] data) throws ValidationException {
        int offset = 0;

        while (offset < data.length) {
            // FIXED: Check header fits
            if (offset + 4 > data.length) {
                throw new ValidationException(
                    "Incomplete chunk header at offset " + offset);
            }

            // Read declared chunk size
            int chunkSize = ByteBuffer.wrap(data, offset, 4).getInt();
            offset += 4;

            // FIXED: Validate chunk size
            if (chunkSize < 0) {
                throw new ValidationException("Negative chunk size: " + chunkSize);
            }

            if (chunkSize > MAX_CHUNK_SIZE) {
                throw new ValidationException(
                    "Chunk size " + chunkSize + " exceeds maximum " + MAX_CHUNK_SIZE);
            }

            // FIXED: Validate chunk data available
            if (offset + chunkSize > data.length) {
                throw new ValidationException(
                    "Chunk size " + chunkSize + " exceeds remaining data " +
                    (data.length - offset));
            }

            byte[] chunk = new byte[chunkSize];
            System.arraycopy(data, offset, chunk, 0, chunkSize);
            offset += chunkSize;

            processChunkData(chunk);
        }
    }

    // FIXED: Count field validation
    public List<String> parseStringList(DataInputStream input, int maxCount)
            throws IOException, ValidationException {

        int count = input.readInt();

        // FIXED: Validate count
        if (count < 0) {
            throw new ValidationException("Negative count: " + count);
        }

        if (count > maxCount) {
            throw new ValidationException(
                "Count " + count + " exceeds maximum " + maxCount);
        }

        List<String> result = new ArrayList<>(count);

        for (int i = 0; i < count; i++) {
            // FIXED: Validate string length
            int length = input.readInt();

            if (length < 0) {
                throw new ValidationException(
                    "Negative string length at index " + i);
            }

            if (length > MAX_STRING_LENGTH) {
                throw new ValidationException(
                    "String length " + length + " exceeds maximum at index " + i);
            }

            byte[] bytes = new byte[length];

            // FIXED: Verify we read expected amount
            int read = input.read(bytes);
            if (read != length) {
                throw new ValidationException(
                    "Expected " + length + " bytes, got " + read);
            }

            result.add(new String(bytes, StandardCharsets.UTF_8));
        }

        return result;
    }
}

CVE Examples

  • CVE-2018-16733: Product does not validate that the start block appears before the end block, causing processing errors.
  • CVE-2006-3790: Size field that is inconsistent with packet size leads to buffer over-read.
  • CVE-2008-4114: System crash with offset value that is inconsistent with packet size.

  • CWE-20: Improper Input Validation (parent)
  • CWE-1215: Data Validation Issues (category)
  • CWE-130: Improper Handling of Length Parameter Inconsistency (related)
  • CWE-805: Buffer Access with Incorrect Length Value (related)

References

  1. MITRE Corporation. "CWE-1288: Improper Validation of Consistency within Input." https://cwe.mitre.org/data/definitions/1288.html
  2. OWASP. "Input Validation Cheat Sheet"
  3. CERT. "Secure Coding Guidelines"