Document: SECURITY.md Crate: irig106-time v0.6.0 Date: 2026-03-25
irig106-time parses binary data from sources that should be treated as
untrusted input:
| Source | Trust Level | Risk |
|---|---|---|
| Ch10 files from own recorders | Medium | Could be corrupted by hardware failure |
| Ch10 files from third parties | Low | Could be intentionally malformed |
| Live UDP streams (Ch10 over IP) | Low | Network-adjacent attacker could inject packets |
| Archived data from data lakes | Low | Integrity may have degraded over storage lifetime |
| Synthetic/test data | Medium | Generator bugs create edge cases |
The crate processes this data in aerospace analysis pipelines where a parsing crash could halt mission-critical post-flight analysis.
Guarantee: Every public function that accepts &[u8] returns Result<T, TimeError>.
No panic on any byte sequence.
Verification:
- 8 fuzz targets covering every parsing path (see
fuzz/) - 10,000-iteration property tests on random inputs (see
tests/properties.rs) - Explicit
BufferTooShortchecks before all slice indexing
The crate uses zero unsafe blocks. All byte-level parsing uses safe Rust
methods (u16::from_le_bytes, bitwise ops, slice indexing with bounds checks).
| Operation | Risk | Mitigation |
|---|---|---|
Rtc::to_nanos() |
48-bit × 100 could overflow u64 | Max value = 2^48 × 100 = 2.81 × 10^16 < u64::MAX (1.84 × 10^19) ✓ |
Ertc::to_nanos() |
64-bit × 100 overflows u64 | Returns u128 |
elapsed_ticks() |
Subtraction could underflow | Uses wrapping_sub + 48-bit mask |
add_nanos() |
Large delta + midnight could overflow | Uses u64 intermediate, carries to days |
| BCD decoding | Multi-digit multiply | Max value per field < 1000; no overflow in u16 |
| Vector | Risk | Mitigation |
|---|---|---|
| Huge correlation table | Memory exhaustion | TimeCorrelator accepts at most what the caller inserts; no amplification |
| Infinite loop on malformed BCD | CPU stall | All parsing is single-pass, bounded by buffer length |
| Checksum computation | Timing side-channel | Not relevant (checksum is not a secret) |
Every decoded value is checked against the IRIG 106 specification ranges before
being returned. A TimeError::OutOfRange is returned for:
- Hours > 23, minutes > 59, seconds > 59
- Day-of-year 0 or > 366, month 0 or > 12, day 0 or > 31
- IEEE-1588 nanoseconds >= 1,000,000,000
- BCD nibbles > 9
# Requires nightly Rust
rustup install nightly
cargo +nightly install cargo-fuzz# Run a specific target (recommended: start with BCD parsers)
cargo +nightly fuzz run fuzz_bcd_day
cargo +nightly fuzz run fuzz_bcd_dmy
# Run with a time limit
cargo +nightly fuzz run fuzz_secondary_header -- -max_total_time=300
# All available targets:
# fuzz_bcd_day — DayFormatTime::from_le_bytes
# fuzz_bcd_dmy — DmyFormatTime::from_le_bytes
# fuzz_rtc — Rtc construction and arithmetic
# fuzz_secondary_header — Secondary header checksum + parsing
# fuzz_intra_packet — IntraPacketTime dispatch
# fuzz_ieee1588 — Ieee1588Time parsing
# fuzz_csdw — CSDW field extraction
# fuzz_correlation — TimeCorrelator with random ref pointsFor better coverage, seed the fuzz corpus with real Ch10 time packet payloads:
mkdir -p fuzz/corpus/fuzz_bcd_day
# Extract time packet payloads from real Ch10 files and place in corpus dir- Panics — Any panic is a bug. File as a security issue.
- Assertion failures in fuzz targets — The fuzz targets assert post-conditions (e.g., parsed hours ≤ 23). A failure means the validation logic has a gap.
- OOM / infinite allocation — The correlation fuzz target caps at 100 refs.
# Run property tests (10,000 iterations each, no external deps)
cargo test --test properties
# The tests use a built-in PRNG (Xorshift) so they're deterministic
# and reproducible without proptest. When proptest is added as a
# dev-dependency, the tests can be upgraded to use its strategies
# for better coverage and shrinking.| Property | Why It Matters |
|---|---|
from_raw always produces ≤ 48-bit value |
Prevents arithmetic overflow in elapsed_ticks |
| LE bytes round-trip | Data integrity through encode/decode |
elapsed_ticks result ≤ 48 bits |
No corruption in nanosecond conversion |
add_nanos then sub_nanos = identity |
Correlation engine correctness |
add_nanos is monotonic |
Time never goes backward unexpectedly |
| CSDW field extraction is idempotent | No hidden state corrupting repeated reads |
-
Always check
Result— Don't.unwrap()in production code. A malformed time packet should not crash the analysis pipeline. -
Validate before correlating — Check that the
TimeCorrelatorhas reference points before calling.correlate(). TheNoReferencePointerror is expected for files that lack time packets. -
Monitor for time jumps — Use
detect_time_jump()to identify GPS lock events or clock resets. Don't silently interpolate across a 5-second jump. -
Don't trust secondary header checksums alone — A valid checksum does not guarantee the time value is correct — only that the bytes are internally consistent.
-
Prefer nearest reference — The correlation engine already uses nearest-point lookup, but if you're building your own pipeline, don't use a single reference point for an entire file. RTC drift is real.