1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//! Deterministic byte-smoke harness for the hand-rolled binary/text parsers.
//!
//! cargo-fuzz's libfuzzer runner is not always usable in CI, so this test feeds
//! many random, truncated, and structurally-corrupted byte buffers through the
//! SPK/DAF binary parser and the NAVCEN HTML parser and asserts each call
//! returns a `Result` without panicking (the test process surviving is the
//! assertion). The byte stream is a fixed splitmix64 sequence with no external
//! entropy, so a failure reproduces exactly.
use sidereon_core::astro::spk::{parse_daf_spk, Spk};
use sidereon_core::bias::BiasSet;
/// splitmix64: a tiny, fully deterministic PRNG so corpora are reproducible.
struct SplitMix64(u64);
impl SplitMix64 {
fn next_u64(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
fn fill(&mut self, buf: &mut [u8]) {
for byte in buf.iter_mut() {
*byte = self.next_u64() as u8;
}
}
}
/// A valid little-endian DAF/SPK file-record prefix so a share of the corpus
/// clears the magic and format gates and reaches the summary + segment readers
/// instead of bouncing off the header check.
fn write_daf_header(buf: &mut [u8], rng: &mut SplitMix64) {
if buf.len() < 96 {
return;
}
buf[0..8].copy_from_slice(b"DAF/SPK ");
buf[8..12].copy_from_slice(&2i32.to_le_bytes());
buf[12..16].copy_from_slice(&6i32.to_le_bytes());
// Randomize FWARD/BWARD/FREE so the summary-record chain walks hostile values.
let fward = (rng.next_u64() % 8) as i32;
let bward = (rng.next_u64() % 8) as i32;
buf[76..80].copy_from_slice(&fward.to_le_bytes());
buf[80..84].copy_from_slice(&bward.to_le_bytes());
buf[84..88].copy_from_slice(&(rng.next_u64() as i32).to_le_bytes());
buf[88..96].copy_from_slice(b"LTL-IEEE");
}
fn exercise_spk(bytes: &[u8]) {
// Directory parse must not panic on any byte slice.
let _ = parse_daf_spk(bytes);
// If a full kernel parses, drive the type-2/3/21 evaluators with queries
// derived from the (untrusted) segment descriptors so the binary-offset
// record readers run on hostile coverage windows.
if let Ok(spk) = Spk::from_bytes(bytes) {
for segment in spk.segments().iter().take(8) {
let mid = 0.5 * (segment.start_et + segment.stop_et);
for et in [
segment.start_et,
segment.stop_et,
mid,
0.0,
f64::NAN,
f64::INFINITY,
] {
let _ = spk.spk_state(segment.target, segment.center, et);
}
}
}
}
fn exercise_bias(bytes: &[u8]) {
let _ = BiasSet::parse_bias_sinex(bytes);
let _ = BiasSet::parse_code_dcb(bytes, None);
}
#[test]
fn spk_parser_survives_hostile_bytes() {
let mut rng = SplitMix64(0x5151_DE60_0F0F_2026);
// Lengths spanning sub-header, exact records, and multi-record kernels.
let lengths = [
0usize, 1, 7, 8, 16, 95, 96, 1023, 1024, 1025, 2047, 2048, 3072, 4096, 5121,
];
for &len in &lengths {
for case in 0..400u32 {
let mut buf = vec![0u8; len];
rng.fill(&mut buf);
// Half random, half magic-prefixed to reach deep parser paths.
if case % 2 == 0 {
write_daf_header(&mut buf, &mut rng);
}
exercise_spk(&buf);
exercise_bias(&buf);
}
}
// All-zero, all-ones, and magic-only buffers as explicit boundary cases.
exercise_spk(&[0u8; 4096]);
exercise_bias(&[0u8; 4096]);
exercise_spk(&[0xFFu8; 4096]);
exercise_bias(&[0xFFu8; 4096]);
let mut magic_only = vec![0u8; 1024];
write_daf_header(&mut magic_only, &mut rng);
exercise_spk(&magic_only);
exercise_bias(&magic_only);
}
#[test]
fn navcen_parser_survives_hostile_bytes() {
use sidereon_core::constellation::parse_navcen;
// Markup fragments the scanner keys on, so corruption lands mid-token and
// mid-tag rather than only on random noise.
const TOKENS: &[&[u8]] = &[
b"<tr",
b"</tr>",
b"<td",
b"</td>",
b">",
b"views-field-field-gps-prn",
b"views-field-field-gps-svn",
b"views-field-field-nanu-type",
b"nanu-active-check",
b"&#",
b"&#x",
b";",
b"&",
b"\xE2\x82\xAC", // multi-byte UTF-8 (euro sign) to probe slice boundaries
b"123",
b" ",
];
let mut rng = SplitMix64(0xC0FF_EE12_3456_789A);
for _ in 0..4000u32 {
let mut buf: Vec<u8> = Vec::new();
let pieces = (rng.next_u64() % 24) as usize;
for _ in 0..pieces {
if rng.next_u64() & 1 == 0 {
let token = TOKENS[(rng.next_u64() as usize) % TOKENS.len()];
buf.extend_from_slice(token);
} else {
buf.push(rng.next_u64() as u8);
}
}
// parse_navcen rejects non-UTF-8 up front; both UTF-8 and non-UTF-8
// buffers must merely return, never panic.
let _ = parse_navcen(&buf);
}
// Pure random byte buffers (mostly invalid UTF-8) and boundary cases.
for len in [0usize, 1, 2, 3, 4, 64, 256, 1024] {
for _ in 0..200u32 {
let mut buf = vec![0u8; len];
rng.fill(&mut buf);
let _ = parse_navcen(&buf);
}
}
let _ = parse_navcen(b"<tr><td class=\"views-field-field-gps-prn\">\xC3\x28</td></tr>");
}