Skip to content

Fast-CDR deserialization does not validate null terminator at end of CDR string #331

Description

@wuyuqa

Fast-CDR deserialization does not validate null terminator at end of CDR string

Issue type: Implementation defect / security risk
Affected version: Fast-CDR (tested with the version bundled with FastDDS 3.6.2)
Relevant specification: DDS XTypes / CDR serialization format


Description

When deserializing a CDR string (string / string<size>), Fast-CDR does not check whether the string content is terminated by a NUL byte (\0). It simply reads the number of bytes specified by the length field and directly assigns them to a std::string.

According to the OMG CDR specification, the length field of a string must include the terminating NUL byte. Therefore, if the length field is N, the string content should consist of N-1 valid characters followed by one \0.

This test constructs a payload with a string length of 5 and content "crhyv" (5 bytes), which does not include a trailing NUL. According to the specification, this payload should be rejected because a declared length of 5 means 4 characters + 1 NUL should be provided, but only 5 characters are present, missing the terminator.

However, Fast-CDR accepts this payload, successfully decodes the string as "crhyv", and continues parsing subsequent fields without any error.


Impact

  • Fast-DDS relies on Fast-CDR as its underlying serialisation library, so all applications that use Fast-DDS are affected.
  • An attacker could craft malformed packets to bypass input validation in DDS applications, leading to parsing discrepancies or potential logic errors.
  • This behaviour differs from CycloneDDS, OpenDDS, and the local CDR reference model, all of which reject such a payload.

Steps to reproduce

Environment

  • OS: Ubuntu 20.04 / 22.04 (or any Linux)
  • Compiler: g++ with C++11 support
  • Fast-CDR source: obtained from the official repository (tested with the version bundled in FastDDS 3.6.2)

Test code

Create a file test_bug.cpp with the following content:

#include <iostream>
#include <vector>
#include <fastcdr/Cdr.h>
#include <fastcdr/FastBuffer.h>

struct Sample {
    std::string name;
    std::vector<int32_t> values;
};

int main() {
    // Malformed payload: length = 5, actual content "crhyv" without terminating NUL
    unsigned char payload[] = {
        0x05, 0x00, 0x00, 0x00, // length = 5
        0x63, 0x72, 0x68, 0x79, 0x76, // "crhyv"
        0x00, 0x00, 0x00,       // alignment padding
        0x03, 0x00, 0x00, 0x00, // sequence length = 3
        0x31, 0x00, 0x00, 0x00,
        0x32, 0x00, 0x00, 0x00,
        0x33, 0x00, 0x00, 0x00
    };

    eprosima::fastcdr::FastBuffer buffer(
        reinterpret_cast<char*>(payload),
        sizeof(payload)
    );

    eprosima::fastcdr::Cdr cdr(
        buffer,
        eprosima::fastcdr::Cdr::LITTLE_ENDIANNESS
    );
    cdr.set_encoding_flag(eprosima::fastcdr::EncodingAlgorithmFlag::PLAIN_CDR);

    Sample sample;
    try {
        cdr >> sample.name;
        cdr >> sample.values;
    } catch (...) {
        std::cerr << "Rejected (expected)" << std::endl;
        return 1;
    }

    // Actual output: accepted
    std::cout << "ACCEPTED (Bug!)" << std::endl;
    std::cout << "name = \"" << sample.name << "\"" << std::endl;
    std::cout << "values size = " << sample.values.size() << std::endl;
    return 0;
}

Compilation

g++ -std=c++11 -I/path/to/fastcdr/include -L/path/to/fastcdr/lib test_bug.cpp -lfastcdr -Wl,-rpath,/path/to/fastcdr/lib -o test_bug

(Replace with your actual Fast-CDR installation paths.)

Execution

./test_bug

Actual output:

ACCEPTED (Bug!)
name = "crhyv"
values size = 3

Expected output (if specification were enforced):

Rejected (expected)

Comparison with related discussion

In Fast-CDR Pull Request #125, maintainers explicitly stated that “the string content contains a single terminating null character” is required by the CDR specification, and thus rejected a proposal to allow embedded NUL characters inside strings during serialisation.

The issue reported here is the mirror image: deserialisation does not perform the same validation. Both reflect inconsistency with the specification, but the lack of validation during deserialisation is more severe because it allows malformed input to be silently accepted.


Suggested fix

In the Fast-CDR std::string deserialisation implementation (e.g., in read_string or related functions), validation should be added:

  • After reading the length len, check whether the byte sequence of that length ends with \0.
  • If not, throw an exception (e.g., eprosima::fastcdr::exception::NotEnoughMemoryException or a new dedicated exception type) and reject the payload.

Reference implementation from the local model:

if (len > 0 && buffer[len-1] != '\0') {
    // Error: missing terminator
}

A strict‑mode option could be added to Cdr, but the default behaviour should enforce the specification rigorously.


We hope this issue can be addressed promptly to improve the robustness and specification compliance of Fast‑DDS.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions