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
Actual output:
ACCEPTED (Bug!)
name = "crhyv"
values size = 3
Expected output (if specification were enforced):
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.
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 astd::string.According to the OMG CDR specification, the
lengthfield of a string must include the terminating NUL byte. Therefore, if the length field isN, the string content should consist ofN-1valid 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
Steps to reproduce
Environment
Test code
Create a file
test_bug.cppwith the following content:Compilation
(Replace with your actual Fast-CDR installation paths.)
Execution
Actual output:
Expected output (if specification were enforced):
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::stringdeserialisation implementation (e.g., inread_stringor related functions), validation should be added:len, check whether the byte sequence of that length ends with\0.eprosima::fastcdr::exception::NotEnoughMemoryExceptionor a new dedicated exception type) and reject the payload.Reference implementation from the local model:
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.