1use std::mem::size_of;
5
6use crate::{contract::*, replication::opening::FRAME_LIMIT, transport};
7
8const OPAQUE_NODE_SIZES: &[usize] = &[
15 size_of::<api::heddle::api::common::CallFailure>(),
16 size_of::<api::heddle::api::common::ErrorDetail>(),
17 size_of::<api::heddle::api::common::RetryAdvice>(),
18 size_of::<api::heddle::api::common::ConflictDetail>(),
19 size_of::<api::heddle::api::common::CursorFailure>(),
20 size_of::<api::heddle::api::common::CapabilityRequirement>(),
21 size_of::<api::heddle::api::common::PolicyDenial>(),
22 size_of::<api::heddle::api::common::UnknownDetail>(),
23 size_of::<api::heddle::api::common::AmbiguousChangeIdDetail>(),
24 size_of::<api::heddle::api::common::SignupFailure>(),
25 size_of::<api::heddle::api::common::StreamFailure>(),
26 size_of::<api::heddle::api::common::HumanVerificationChallenge>(),
27 size_of::<api::heddle::api::common::OAuthLinkChallenge>(),
28 size_of::<TransferObject>(),
29 size_of::<ObjectAddress>(),
30 size_of::<String>(),
31 size_of::<Vec<u8>>(),
32];
33fn opaque_node_factor() -> usize {
34 OPAQUE_NODE_SIZES.iter().copied().max().unwrap_or(0) * 4
35}
36
37type Result<T> = std::result::Result<T, transport::Error>;
38fn invalid() -> transport::Error {
39 transport::Error::Protocol("live replication input shape exceeds bounds")
40}
41struct Field<'a> {
42 tag: u32,
43 data: Option<&'a [u8]>,
44}
45fn varint(bytes: &mut &[u8]) -> Result<u64> {
46 let mut value = 0u64;
47 for shift in (0..70).step_by(7) {
48 let (&byte, rest) = bytes.split_first().ok_or_else(invalid)?;
49 *bytes = rest;
50 if shift == 63 && byte > 1 {
51 return Err(invalid());
52 }
53 value |= u64::from(byte & 127) << shift;
54 if byte < 128 {
55 return Ok(value);
56 }
57 }
58 Err(invalid())
59}
60fn field<'a>(bytes: &mut &'a [u8]) -> Result<Option<Field<'a>>> {
61 if bytes.is_empty() {
62 return Ok(None);
63 }
64 let key = varint(bytes)?;
65 let tag = u32::try_from(key >> 3).map_err(|_| invalid())?;
66 if tag == 0 {
67 return Err(invalid());
68 }
69 let data = match key & 7 {
70 0 => {
71 varint(bytes)?;
72 None
73 }
74 1 | 5 => {
75 let n = if key & 7 == 1 { 8 } else { 4 };
76 *bytes = bytes.get(n..).ok_or_else(invalid)?;
77 None
78 }
79 2 => {
80 let n = usize::try_from(varint(bytes)?).map_err(|_| invalid())?;
81 let value = bytes.get(..n).ok_or_else(invalid)?;
82 *bytes = &bytes[n..];
83 Some(value)
84 }
85 _ => return Err(invalid()),
86 };
87 Ok(Some(Field { tag, data }))
88}
89fn data<'a>(field: &Field<'a>) -> Result<&'a [u8]> {
90 field.data.ok_or_else(invalid)
91}
92fn count(value: &mut usize, max: usize) -> Result<()> {
93 *value += 1;
94 if *value > max { Err(invalid()) } else { Ok(()) }
95}
96fn id(field: &Field<'_>) -> Result<()> {
97 if data(field)?.len() == 32 {
98 Ok(())
99 } else {
100 Err(invalid())
101 }
102}
103fn signature(mut bytes: &[u8]) -> Result<()> {
104 let (mut keys, mut signatures) = (0, 0);
105 while let Some(f) = field(&mut bytes)? {
106 match f.tag {
107 1 => {
108 count(&mut keys, 1)?;
109 id(&f)?;
110 }
111 2 => {
112 count(&mut signatures, 1)?;
113 if data(&f)?.len() != 64 {
114 return Err(invalid());
115 }
116 }
117 _ => {}
118 }
119 }
120 if keys == 1 && signatures == 1 {
121 Ok(())
122 } else {
123 Err(invalid())
124 }
125}
126fn record(mut bytes: &[u8]) -> Result<()> {
127 let (mut formats, mut canonicals, mut signatures) = (0, 0, 0);
128 while let Some(f) = field(&mut bytes)? {
129 match f.tag {
130 1 => {
131 count(&mut formats, 1)?;
132 if data(&f)?.len() > 128 {
133 return Err(invalid());
134 }
135 }
136 2 => {
137 count(&mut canonicals, 1)?;
138 data(&f)?;
139 }
140 3 => {
141 count(&mut signatures, 1)?;
142 signature(data(&f)?)?;
143 }
144 _ => {}
145 }
146 }
147 if formats == 1 && canonicals == 1 && signatures == 1 {
148 Ok(())
149 } else {
150 Err(invalid())
151 }
152}
153fn frontier(mut bytes: &[u8], heads: &mut usize, max: usize) -> Result<()> {
154 while let Some(f) = field(&mut bytes)? {
155 if f.tag == 2 {
156 count(heads, max)?;
157 id(&f)?;
158 }
159 }
160 Ok(())
161}
162fn failure(mut bytes: &[u8], opaque: &mut usize) -> Result<()> {
163 while let Some(f) = field(&mut bytes)? {
164 match f.tag {
165 2 => {
166 data(&f)?;
167 }
168 4 => {
172 *opaque = opaque.checked_add(data(&f)?.len()).ok_or_else(invalid)?;
173 }
174 _ => {}
175 }
176 }
177 Ok(())
178}
179fn rejection(mut bytes: &[u8], opaque: &mut usize) -> Result<()> {
180 while let Some(f) = field(&mut bytes)? {
181 match f.tag {
182 1 => id(&f)?,
183 2 => failure(data(&f)?, opaque)?,
184 _ => {}
185 }
186 }
187 Ok(())
188}
189pub fn reservation(bytes: &[u8], max: usize) -> Result<usize> {
193 if max == 0 || max > 64 || bytes.len() > FRAME_LIMIT {
194 return Err(invalid());
195 }
196 let original_len = bytes.len();
197 let mut bytes = bytes;
198 let mut body_count = 0;
199 let mut objects = 0usize;
200 let mut opaque = 0usize;
201 while let Some(body) = field(&mut bytes)? {
202 if !(2..=5).contains(&body.tag) {
203 return Err(invalid());
204 }
205 count(&mut body_count, 1)?;
206 let mut inner = data(&body)?;
207 let (mut first, mut second, mut third, mut fourth, mut fifth) = (0, 0, 0, 0, 0);
208 let mut heads = 0;
209 while let Some(f) = field(&mut inner)? {
210 match (body.tag, f.tag) {
211 (2, 1) => {
212 count(&mut first, max)?;
213 frontier(data(&f)?, &mut heads, max)?;
214 objects += 1;
215 }
216 (3, 1) => {
217 count(&mut first, max)?;
218 id(&f)?;
219 objects += 1;
220 }
221 (4, 1) => {
222 count(&mut first, max)?;
223 record(data(&f)?)?;
224 objects += 3;
225 }
226 (4, 2) => {
227 count(&mut second, max)?;
228 record(data(&f)?)?;
229 objects += 3;
230 }
231 (4, 3) => {
232 count(&mut third, max)?;
233 record(data(&f)?)?;
234 objects += 3;
235 }
236 (5, 1) | (5, 2) => {
237 count(&mut first, max)?;
238 id(&f)?;
239 objects += 1;
240 }
241 (5, 3) => {
242 count(&mut first, max)?;
243 count(&mut third, max)?;
244 rejection(data(&f)?, &mut opaque)?;
245 objects += 3;
246 }
247 (5, 4) => {
248 count(&mut fourth, max)?;
249 frontier(data(&f)?, &mut heads, max)?;
250 objects += 1;
251 }
252 (5, 5) => {
253 count(&mut fifth, max)?;
254 opaque = opaque.checked_add(data(&f)?.len()).ok_or_else(invalid)?;
255 objects += 1;
256 }
257 (5, 6) => {
258 data(&f)?;
259 }
260 _ => {}
261 }
262 }
263 }
264 if body_count != 1 {
265 return Err(invalid());
266 }
267 let container = size_of::<crate::replication::Frame>()
268 + size_of::<crate::replication::InputUnit>()
269 + size_of::<SignedRecord>()
270 + size_of::<RecordSignature>()
271 + size_of::<ReplicationRejection>();
272 original_len
273 .checked_mul(6)
274 .and_then(|value| value.checked_add((objects + 4) * 4 * container))
275 .and_then(|value| value.checked_add(opaque.saturating_mul(opaque_node_factor())))
276 .ok_or_else(invalid)
277}
278
279#[cfg(test)]
280mod tests {
281 use prost::Message;
282
283 use super::*;
284 fn record() -> SignedRecord {
285 SignedRecord {
286 format: "fixture".into(),
287 canonical_record: vec![1; 32],
288 signatures: vec![RecordSignature {
289 public_key: vec![2; 32],
290 signature: vec![3; 64],
291 }],
292 }
293 }
294 fn batch(operations: Vec<SignedRecord>, authority_admissions: Vec<SignedRecord>) -> Vec<u8> {
295 ReplicateThreadRequest {
296 body: Some(replicate_thread_request::Body::Operations(
297 ReplicationOperations {
298 native_authority: None,
299 operations,
300 authority_admissions,
301 boundary_acceptances: vec![],
302 import_authority: None,
303 },
304 )),
305 }
306 .encode_to_vec()
307 }
308 #[test]
309 fn protobuf_shape_preflight_bounds_record_expansion_before_decode() {
310 let valid = record();
311 let bytes = batch(vec![valid.clone(); 64], vec![valid.clone(); 64]);
312 assert!(
313 reservation(&bytes, 64).expect("maximum separate record and receipt vectors")
314 > bytes.len()
315 );
316 assert!(
317 reservation(&bytes, 63).is_err(),
318 "negotiated count applies before protobuf decode"
319 );
320 let mut duplicate = valid.clone();
321 duplicate.signatures.push(valid.signatures[0].clone());
322 assert!(
323 reservation(&batch(vec![duplicate], vec![]), 64).is_err(),
324 "multiple nested signatures must fail shape preflight"
325 );
326 let mut short = valid;
327 short.signatures[0].signature.truncate(63);
328 assert!(
329 reservation(&batch(vec![short], vec![]), 64).is_err(),
330 "signature shape is checked without allocating decoded vectors"
331 );
332 assert!(reservation(&[0x22, 0xff], 64).is_err(), "truncated length");
333 }
334 #[test]
335 fn protobuf_shape_bounds_boundary_acceptance_carriers() {
336 let valid = record();
337 let encode = |records| {
338 ReplicateThreadRequest {
339 body: Some(replicate_thread_request::Body::Operations(
340 ReplicationOperations {
341 native_authority: None,
342 operations: vec![valid.clone()],
343 authority_admissions: vec![],
344 boundary_acceptances: records,
345 import_authority: None,
346 },
347 )),
348 }
349 .encode_to_vec()
350 };
351 let maximum = encode(vec![valid.clone(); 64]);
352 assert!(reservation(&maximum, 64).expect("bounded acceptance records") > maximum.len());
353 assert!(
354 reservation(&maximum, 63).is_err(),
355 "acceptance count obeys negotiated bound"
356 );
357 let mut duplicate = valid.clone();
358 duplicate.signatures.push(valid.signatures[0].clone());
359 assert!(
360 reservation(&encode(vec![duplicate]), 64).is_err(),
361 "acceptance signature expansion is rejected before decode"
362 );
363 }
364 #[test]
365 fn protobuf_shape_preserves_legal_receipt_details_and_accounts_nested_layouts() {
366 use api::heddle::api::common::{
367 CallFailure, CapabilityRequirement, ErrorDetail, error_detail::Context,
368 };
369 let failure = CallFailure {
370 code: 9,
371 message: "needs a capability".into(),
372 error: Some(ErrorDetail {
373 context: Some(Context::Capability(CapabilityRequirement {
374 capabilities: vec![String::new(); 128],
375 })),
376 ..Default::default()
377 }),
378 };
379 let receipt = ReplicationReceipt {
380 rejected: vec![ReplicationRejection {
381 operation_id: vec![7; 32],
382 failure: Some(failure),
383 }],
384 accepted_frontiers: vec![CausalFrontier {
385 facet: 1,
386 heads: vec![vec![8; 32]],
387 }],
388 missing_objects: vec![TransferObject {
389 address: Some(ObjectAddress {
390 algorithm: "blake3".into(),
391 digest: vec![9; 32],
392 }),
393 kind: "source".into(),
394 ..Default::default()
395 }],
396 sharing_policy_version: vec![5; 32],
397 ..Default::default()
398 };
399 let response = ReplicateThreadResponse {
400 body: Some(replicate_thread_response::Body::Receipt(receipt)),
401 };
402 let bytes = response.encode_to_vec();
403 let reserved = reservation(&bytes, 64)
404 .expect("preserve all existing receipt capability and coverage fields");
405 assert_eq!(
406 ReplicateThreadResponse::decode(bytes.as_slice()).expect("prost response"),
407 response
408 );
409 assert_eq!(
410 OPAQUE_NODE_SIZES.len(),
411 17,
412 "all currently recursive error/transfer node layouts enumerated"
413 );
414 assert!(
415 OPAQUE_NODE_SIZES
416 .iter()
417 .all(|size| opaque_node_factor() >= 4 * size)
418 );
419 assert!(
420 reserved > 128 * size_of::<String>() + bytes.len(),
421 "empty repeated strings still consume vector slots"
422 );
423 }
424}