CCSDS AOS (Advanced Orbiting Systems) Transfer Frame for satellite downlinks
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fuzz tests: AOS 2→9, USLP 2→9, fix OCF/FECF masking bug

Comprehensive fuzz test suites for AOS and USLP, replacing the
minimal 2-test stubs with 9 tests each covering:

- Decode crash safety (found and fixed a real bug: Wire.Codec
raised Invalid_argument on short input due to negative data_size
expression — added min_size pre-validation in Aos.decode)
- Roundtrip, roundtrip with OCF, encode determinism
- FECF corruption detection
- PP crash safety
- CLCW integration roundtrip
- Wire header roundtrip
- Idle frame detection
- USLP-specific: CRC-16/CRC-32 FECF variants, VCFC length variants

Bug fixes found by fuzzing:

- aos/tm/uslp: OCF and FECF values were not masked to their wire
width (32/16 bits) in constructors. On 64-bit OCaml, a caller
passing a negative int (e.g., from Int32.to_int on a high-bit-set
value) would encode the low 32 bits correctly but decode as a
different positive int, breaking roundtrip. Fix: mask in Aos.v,
Tm.v, Uslp.v, matching the existing pattern for VCFC/vcid/scid.

- aos: Wire.Codec.decode_with on short/malformed input could
produce a negative data_size (frame_len - header - trailer sizes),
causing Invalid_argument in String.sub. Fix: validate minimum
frame size before calling the codec.

+149 -12
+139 -12
fuzz/fuzz_aos.ml
··· 5 5 6 6 open Alcobar 7 7 8 + (** {1 Decode crash safety} *) 9 + 10 + let test_decode_crash buf = 11 + ignore (Aos.decode buf) 12 + 13 + (** {1 Roundtrip: encode then decode recovers the same frame} *) 14 + 15 + let test_roundtrip scid_val vcid_val vcfc data = 16 + match (Aos.scid scid_val, Aos.vcid vcid_val) with 17 + | Some scid, Some vcid -> ( 18 + let frame = Aos.v ~scid ~vcid ~vcfc data in 19 + let encoded = Aos.encode frame in 20 + match Aos.decode encoded with 21 + | Error e -> fail (Fmt.str "decode failed: %a" Aos.pp_error e) 22 + | Ok frame' -> check_eq ~pp:Aos.pp ~eq:Aos.equal frame frame') 23 + | _ -> () 24 + 25 + (** {1 Roundtrip with OCF} *) 26 + 27 + let test_roundtrip_with_ocf scid_val vcid_val vcfc ocf data = 28 + match (Aos.scid scid_val, Aos.vcid vcid_val) with 29 + | Some scid, Some vcid -> ( 30 + let frame = Aos.v ~scid ~vcid ~vcfc ~ocf data in 31 + let encoded = Aos.encode frame in 32 + match Aos.decode encoded with 33 + | Error e -> fail (Fmt.str "decode failed: %a" Aos.pp_error e) 34 + | Ok frame' -> 35 + check_eq ~pp:Fmt.int (Aos.scid_to_int frame.header.scid) 36 + (Aos.scid_to_int frame'.header.scid); 37 + check_eq ~pp:Fmt.(option int) frame.ocf frame'.ocf) 38 + | _ -> () 39 + 40 + (** {1 Encode determinism: same input -> same bytes} *) 41 + 42 + let test_encode_determinism scid_val vcid_val vcfc data = 43 + match (Aos.scid scid_val, Aos.vcid vcid_val) with 44 + | Some scid, Some vcid -> 45 + let frame = Aos.v ~scid ~vcid ~vcfc data in 46 + let enc1 = Aos.encode frame in 47 + let enc2 = Aos.encode frame in 48 + if enc1 <> enc2 then fail "encode not deterministic" 49 + | _ -> () 50 + 51 + (** {1 FECF corruption detection} *) 52 + 53 + let test_fecf_corruption scid_val vcid_val vcfc data bit_pos = 54 + match (Aos.scid scid_val, Aos.vcid vcid_val) with 55 + | Some scid, Some vcid -> 56 + let frame = Aos.v ~scid ~vcid ~vcfc data in 57 + let encoded = Aos.encode ~with_fecf:true frame in 58 + let len = String.length encoded in 59 + if len > 2 then begin 60 + let byte_pos = (bit_pos mod (len - 2)) in 61 + let buf = Bytes.of_string encoded in 62 + let old = Bytes.get_uint8 buf byte_pos in 63 + Bytes.set_uint8 buf byte_pos (old lxor 1); 64 + match Aos.decode ~frame_len:len (Bytes.to_string buf) with 65 + | Error (Fecf_mismatch _) -> () 66 + | Error _ -> () 67 + | Ok _ -> 68 + (* Allowed: if the flip is in FECF itself, the CRC may 69 + accidentally match. Otherwise this is a real miss. *) 70 + if byte_pos < len - 2 then 71 + fail "FECF did not detect corruption" 72 + end 73 + | _ -> () 74 + 75 + (** {1 PP crash safety} *) 76 + 77 + let test_pp buf = 78 + match Aos.decode buf with 79 + | Error _ -> () 80 + | Ok frame -> 81 + let _ = Fmt.str "%a" Aos.pp frame in 82 + () 83 + 84 + (** {1 CLCW integration: OCF roundtrip through CLCW} *) 85 + 86 + let test_clcw_roundtrip scid_val vcid_val vcfc clcw_vcid report_value data = 87 + match (Aos.scid scid_val, Aos.vcid vcid_val) with 88 + | Some scid, Some vcid -> 89 + let clcw_vcid = clcw_vcid mod 64 in 90 + let report_value = report_value mod 256 in 91 + let clcw = Clcw.v ~vcid:clcw_vcid ~report_value () in 92 + let frame = Aos.with_clcw ~scid ~vcid ~vcfc ~clcw data in 93 + let encoded = Aos.encode frame in 94 + (match Aos.decode encoded with 95 + | Error e -> fail (Fmt.str "decode failed: %a" Aos.pp_error e) 96 + | Ok frame' -> ( 97 + match Aos.clcw frame' with 98 + | None -> fail "no CLCW in decoded frame" 99 + | Some (Error e) -> fail (Fmt.str "CLCW decode: %a" Clcw.pp_error e) 100 + | Some (Ok clcw') -> 101 + check_eq ~pp:Fmt.int clcw_vcid clcw'.vcid; 102 + check_eq ~pp:Fmt.int report_value clcw'.report_value)) 103 + | _ -> () 104 + 105 + (** {1 Idle frame detection} *) 106 + 107 + let test_idle_frame data = 108 + let scid = Aos.scid_exn 0 in 109 + let vcid = Aos.vcid_exn Aos.idle_vcid in 110 + let frame = Aos.v ~scid ~vcid ~vcfc:0 data in 111 + if not (Aos.is_idle frame) then fail "idle frame not detected" 112 + 113 + (** {1 Wire header roundtrip} *) 114 + 115 + let test_wire_header_roundtrip buf = 116 + let buf = 117 + if String.length buf < 6 then buf ^ String.make (6 - String.length buf) '\x00' 118 + else String.sub buf 0 6 119 + in 120 + let bytes_buf = Bytes.of_string buf in 121 + match Aos.decode_bytes bytes_buf with 122 + | Error _ -> () 123 + | Ok hdr -> 124 + let encoded = Aos.encode_bytes hdr in 125 + (match Aos.decode_bytes encoded with 126 + | Error _ -> fail "re-decode of encoded header failed" 127 + | Ok hdr' -> check_eq ~pp:pp_int hdr.version hdr'.version) 128 + 8 129 let suite = 9 130 ( "aos", 10 131 [ 132 + test_case "decode crash safety" [ bytes ] test_decode_crash; 11 133 test_case "roundtrip" 12 134 [ range 256; range 64; range 0x1000000; bytes ] 13 - (fun scid_val vcid_val vcfc data -> 14 - match (Aos.scid scid_val, Aos.vcid vcid_val) with 15 - | Some scid, Some vcid -> ( 16 - let frame = Aos.v ~scid ~vcid ~vcfc data in 17 - let encoded = Aos.encode frame in 18 - match Aos.decode encoded with 19 - | Error e -> fail (Fmt.str "decode failed: %a" Aos.pp_error e) 20 - | Ok frame' -> check_eq ~pp:Aos.pp ~eq:Aos.equal frame frame') 21 - | _ -> ()); 22 - test_case "decode random bytes" [ bytes ] (fun buf -> 23 - (* Just check decode doesn't crash on random input *) 24 - ignore (Aos.decode buf)); 135 + test_roundtrip; 136 + test_case "roundtrip with OCF" 137 + [ range 256; range 64; range 0x1000000; int32; bytes ] 138 + (fun s v vc ocf d -> 139 + test_roundtrip_with_ocf s v vc (Some (Int32.to_int ocf)) d); 140 + test_case "encode determinism" 141 + [ range 256; range 64; range 0x1000000; bytes ] 142 + test_encode_determinism; 143 + test_case "FECF corruption" 144 + [ range 256; range 64; range 0x1000000; bytes; range 8192 ] 145 + test_fecf_corruption; 146 + test_case "pp crash safety" [ bytes ] test_pp; 147 + test_case "CLCW roundtrip" 148 + [ range 256; range 64; range 0x1000000; range 64; range 256; bytes ] 149 + test_clcw_roundtrip; 150 + test_case "idle frame" [ bytes ] test_idle_frame; 151 + test_case "wire header roundtrip" [ bytes ] test_wire_header_roundtrip; 25 152 ] )
+10
lib/aos.ml
··· 407 407 let env = 408 408 bind_frame_params ~frame_len ~insert_zone_len ~expect_ocf ~expect_fecf 409 409 in 410 + let min_size = 411 + header_len + insert_zone_len 412 + + (if expect_ocf then ocf_len else 0) 413 + + (if expect_fecf then fecf_len else 0) 414 + in 415 + if frame_len < min_size then 416 + Error (Truncated { need = min_size; have = frame_len }) 417 + else 410 418 match Wire.Codec.decode_with frame_codec env frame_buf 0 with 411 419 | Error _ -> Error (Truncated { need = frame_len; have = buf_len }) 412 420 | Ok pf -> ( ··· 516 524 vc_count_cycle = vc_count_cycle land 0xF; 517 525 } 518 526 in 527 + let ocf = Option.map (fun v -> v land 0xFFFFFFFF) ocf in 528 + let fecf = Option.map (fun v -> v land 0xFFFF) fecf in 519 529 { header; insert_zone; data; ocf; fecf } 520 530 521 531 (* {1 CLCW Integration} *)