CCSDS File Delivery Protocol - CfdpManager#5138
Conversation
There was a problem hiding this comment.
CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
thomas-bc
left a comment
There was a problem hiding this comment.
A few AI finds that seemed relevant, and one big question about how to handle file paths.
|
|
||
| /* store the filenames in transaction - validation already done during deserialization */ | ||
| txn->m_history->fnames.src_filename = md.getSourceFilename(); | ||
| txn->m_history->fnames.dst_filename = md.getDestFilename(); |
There was a problem hiding this comment.
One security issue that we've had reported a lot on the F Prime Svc.FileUplink component is that it allows to write files at any location on the filesystem, potentially overwriting critical system files etc.
This is risky for in case of operator oversight (shrug) but also if you receive unauthenticated (potentially malicious) commands.
My understanding is this CfdpManager has the same capability.
Did you find that CFDP makes recommendation on how to deal with that? If not, we should agree on a rationale so that we stop the spam of "hey you have a critical vulnerability here". Maybe the answer is "encrypt/authenticate your comms". But we should agree on something.
cc @bitWarrior , if you have recommendations.
There was a problem hiding this comment.
CfdpManager was patterned using the same security model as FileUplink/FileDownlink. Authentication is expected to happen at the physical or network layers, not in the application itself. In out use-cases, CFDP traffic is being sent over authenticated channels such as encrypted radios or Bundle Protocol Security, so CfdpManager assumes the source is already trusted. That matches the assumptions generally made by the CCSDS 727.0-B-5 CFDP standard.
I understand the defense-in-depth argument for adding application-layer path validation. The tradeoff is additional configuration complexity and possible conflicts with legitimate use cases like firmware updates or system file management. It also does not solve the underlying problem if the CFDP traffic itself is unauthenticated.
I added a “Security Considerations” section to the SDD to document the design rationale more clearly.
If there is interest in revisiting the design or discussing path validation for F´ components more broadly, I’d be happy to join that discussion.
There was a problem hiding this comment.
I agree with everything you've said. I think this is the correct long-term approach. Leaving this thread open for awareness.
Improve CFDP Unit Tests
# Conflicts: # .github/actions/spelling/expect.txt # .github/actions/spelling/patterns.txt # Os/File.hpp # Svc/Ccsds/CMakeLists.txt
This comment was marked as resolved.
This comment was marked as resolved.
|
@LeStarch @thomas-bc this PR has the updated UT coverage changes and is ready for review. |
|
Thanks for the update. We'll dive into this... |
| } else { | ||
| // Get parameters for fileIn port-initiated transfers | ||
| Fw::ParamValid valid; | ||
| U8 channelId = this->paramGet_FileInDefaultChannel(valid); |
There was a problem hiding this comment.
[Security] must fix FileInDefaultChannel parameter is not range-checked before use, so an out-of-range value asserts in Engine::txFile.
channelId comes from a ground-settable parameter and is passed straight to Engine::txFile, which executes FW_ASSERT(chan_num < Cfdp::NumChannels, ...). Setting the parameter to any value >= Cfdp::NumChannels makes the next fileIn port invocation crash the component (DoS via parameter update). Validate it the way checkCommandChannelIndex() guards the command handlers and return STATUS_INVALID instead of asserting.
| // Write file data | ||
| if (ret == Cfdp::Status::SUCCESS) { | ||
| FwSizeType write_size = dataSize; | ||
| Os::File::Status status = this->m_fd.write(dataPtr, write_size, Os::File::WaitType::WAIT); |
There was a problem hiding this comment.
[Security] must fix File Data PDU offset/length are written to disk with no bound check against the transaction file size, enabling remote disk exhaustion.
offset and dataSize come directly from the received PDU and are never validated against the metadata-declared m_fsize (or any configured maximum) before the seek/write above. A misbehaving peer can send FD PDUs with offsets up to 0xFFFFFFFF and withhold EOF, growing the receive file toward 4 GiB per transaction until the filesystem fills. Consider rejecting FD PDUs where offset + dataSize exceeds the declared/expected file size.
cc @LeStarch @thomas-bc @bitWarrior — low-confidence finding, please confirm.
|
|
||
| ## Telemetry | ||
|
|
||
| **Note:** Telemetry channels are currently **proposals** defined in [Telemetry.fppi](../Telemetry.fppi) but not yet implemented. Proposals are based on the CF implementation. |
There was a problem hiding this comment.
[Documentation] must fix SDD Telemetry section is stale: it states telemetry is "not yet implemented," but ChannelTelemetry is fully implemented and downlinked.
CfdpManager.cpp calls tlmWrite_ChannelTelemetry (lines 72, 473) and per-channel counters are incremented throughout CfdpManager.hpp. This note wrongly tells operators CFDP telemetry is unavailable. The tables also omit 4 emitted fields: recvEofCanceled, sentEofCanceled, faultRxEofError, faultTxEofError.
| **Note:** Telemetry channels are currently **proposals** defined in [Telemetry.fppi](../Telemetry.fppi) but not yet implemented. Proposals are based on the CF implementation. | |
| The following telemetry channels are emitted per CFDP channel as the `ChannelTelemetry` array (see [Telemetry.fppi](../Telemetry.fppi)). |
|
|
||
| Fw::SerializeStatus FileDataPdu::serializeTo(Fw::SerialBufferBase& buffer, Fw::Endianness mode) const { | ||
| // Cast to SerialBuffer and delegate to toSerialBuffer | ||
| Fw::SerialBuffer* serialBuffer = dynamic_cast<Fw::SerialBuffer*>(&buffer); |
There was a problem hiding this comment.
[C++ Design] must fix CPP-25/RTTI — dynamic_cast requires RTTI, which is disabled in F´ flight builds (-fno-rtti); it will not compile.
dynamic_cast is a banned C++ feature (CPP-25). Every sibling PDU (Ack/Eof/Fin/Metadata/Nak) takes Fw::SerialBufferBase& and never downcasts. FileDataPdu needs the concrete SerialBuffer API, so downcast without RTTI. SerialBuffer : LinearBufferBase : SerialBufferBase, so static_cast is valid; the caller contract guarantees a SerialBuffer, which makes the null-check below dead (safe to drop).
| Fw::SerialBuffer* serialBuffer = dynamic_cast<Fw::SerialBuffer*>(&buffer); | |
| Fw::SerialBuffer* serialBuffer = static_cast<Fw::SerialBuffer*>(&buffer); |
|
|
||
| Fw::SerializeStatus FileDataPdu::deserializeFrom(Fw::SerialBufferBase& buffer, Fw::Endianness mode) { | ||
| // Cast to SerialBuffer and delegate to fromSerialBuffer | ||
| Fw::SerialBuffer* serialBuffer = dynamic_cast<Fw::SerialBuffer*>(&buffer); |
There was a problem hiding this comment.
[C++ Design] must fix CPP-25/RTTI — dynamic_cast requires RTTI, which is disabled in F´ flight builds (-fno-rtti); it will not compile.
Same banned-feature issue as in serializeTo above (CPP-25). Sibling PDUs take Fw::SerialBufferBase& and never downcast. Since SerialBuffer : LinearBufferBase : SerialBufferBase, use static_cast; the caller guarantees a SerialBuffer, so the null-check below becomes dead (safe to drop).
| Fw::SerialBuffer* serialBuffer = dynamic_cast<Fw::SerialBuffer*>(&buffer); | |
| Fw::SerialBuffer* serialBuffer = static_cast<Fw::SerialBuffer*>(&buffer); |
lestarch-autobot
left a comment
There was a problem hiding this comment.
Automated review summary (run 2)
Per-agent results
| Agent | must fix | suggestion | could fix | future work | outstanding | Verdict |
|---|---|---|---|---|---|---|
| Security Vulnerabilities | 4 | 2 | 1 | 0 | 3 | No-Go |
| Supply Chain / Runner Safety | 0 | 0 | 1 | 0 | 1 | Go |
| F Prime C/C++ Design | 9 | 6 | 1 | 0 | 4 | No-Go |
| Documentation Currency | 2 | 2 | 3 | 0 | 3 | No-Go |
| Design | 2 | 0 | 0 | 0 | 0 | Go |
| Architecture | 0 | 0 | 0 | 0 | 0 | Go |
| Test Quality | 5 | 1 | 1 | 0 | 1 | Go |
| CI safety | — | — | — | — | — | Go |
| Totals | 22 | 11 | 7 | 0 | 12 | No-Go |
Since last run
| Agent | resolved | still open | newly added | incorrect-fix follow-ups | improperly resolved | disagreements escalated |
|---|---|---|---|---|---|---|
| Security Vulnerabilities | 4 | 1 | 2 | 0 | 0 | 1 |
| Supply Chain / Runner Safety | 0 | 0 | 1 | 0 | 0 | 0 |
| F Prime C/C++ Design | 12 | 2 | 2 | 0 | 0 | 0 |
| Documentation Currency | 4 | 0 | 3 | 0 | 0 | 0 |
| Design | 2 | 0 | 0 | 0 | 0 | 0 |
| Architecture | 0 | 0 | 0 | 0 | 0 | 0 |
| Test Quality | 6 | 1 | 0 | 0 | 0 | 0 |
Supply-chain surfaces
| Surface | Outstanding |
|---|---|
| Dependencies | clean |
| Vendored / submodule | 1 could-fix — ATTRIBUTION lists Types/Types.hpp as ported yet its header carries no NASA notice |
| Build / test infrastructure | clean |
| Workflows / actions / scripts | clean |
| Generator output | clean |
| Prompt-injection | clean |
| Review-system integrity | clean |
Outstanding must-fix items (5)
Security Vulnerabilities
FileInDefaultChannelparameter not range-checked before use — out-of-range value asserts inEngine::txFile(DoS) — #5138 (comment)- File Data PDU offset/length written to disk with no bound check against transaction file size — remote disk exhaustion — #5138 (comment)
F Prime C/C++ Design
- CPP-25/RTTI —
dynamic_castinFileDataPdu::serializeTowon't compile under-fno-rtti; usestatic_cast— #5138 (comment) - CPP-25/RTTI — second
dynamic_castinFileDataPdu.cppwon't compile under-fno-rtti; usestatic_cast— #5138 (comment)
Documentation Currency
- SDD Telemetry section stale: states telemetry "not yet implemented," but
ChannelTelemetryis implemented and downlinked — #5138 (comment)
Merge readiness
Merge readiness: No-Go — Security, C/C++ Design, and Documentation each have outstanding must-fix findings (2, 2, and 1 respectively).
Agents that did not run on this PR
- None — all seven registered reviewers ran this run.
Steady progress this orbit: twelve findings cleared since run 1, five must-fix contacts remain on the scope — clear them and we're go for the next window.
Change Description
This PR adds the CfdpManager component, an F´ implementation of the CCSDS File Delivery Protocol (CFDP) standard ported from NASA's Core Flight System (cFS) CFDP Application v3.0.0. CfdpManager provides both Class 1 (unacknowledged) and Class 2 (acknowledged) file transfer capabilities, designed to replace the standard FileUplink and FileDownlink components with guaranteed file delivery support.
Attribution and License:
Ported components (retain original NASA copyright):
Engine.cpp,Transaction.hpp,TransactionTx.cpp,TransactionRx.cpp)Utils.cpp,Channel.cpp)Chunk.cpp,Clist.cpp)New F´-specific implementations:
SerializableinterfaceSee ATTRIBUTION.md for complete file-by-file attribution breakdown.
Summary of changes:
Rationale
Current F´ file transfer components (FileUplink/FileDownlink) lack reliable delivery guarantees over lossy or intermittent links, automatic retransmission and gap detection, and industry-standard CFDP protocol compliance required for interoperability with ground systems and other spacecraft.
CfdpManager addresses these gaps by implementing the CCSDS CFDP standard, which is specifically designed for space missions with long propagation delays, high error rates, and disruption-tolerant requirements. By porting NASA's proven CF application from cFS, this implementation leverages flight-proven CFDP logic while adapting it to F´'s architecture and component model.
Testing/Review Recommendations
Unit Test Coverage:
Areas to focus on:
dataOut/dataReturnInandbufferAllocate/bufferDeallocate)Future Work
fileInportAI Usage (see policy)
Generative AI (Claude Code) was used for:
Important note: AI was NOT used to generate the core CFDP protocol logic. The core engine, transaction state machines, and protocol logic were ported directly from NASA's CF application v3.0.0 (human-written, flight-proven code). AI assistance was limited to F´-specific integration code, documentation, and port quality improvements.