Now let's take a silly one
0

Configure Feed

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

at main 5.3 kB View raw
1use std::collections::HashMap; 2use std::io::{self, Write}; 3 4use flate2::{Decompress, FlushDecompress, Status}; 5use gix_pack::data::{Entry, entry::Header, header}; 6 7use crate::error::{PackError, PackLimit}; 8 9const MAX_OBJECTS: u32 = 2_000_000; 10const MAX_OBJECT_BYTES: u64 = 512 * 1024 * 1024; 11const MAX_TOTAL_BYTES: u64 = 4 * 1024 * 1024 * 1024; 12const MAX_DELTA_DEPTH: usize = 50; 13 14#[derive(Debug, Clone, Copy, PartialEq, Eq)] 15pub struct PackLimits { 16 pub max_objects: u32, 17 pub max_object_bytes: u64, 18 pub max_total_bytes: u64, 19 pub max_delta_depth: usize, 20} 21 22impl Default for PackLimits { 23 fn default() -> Self { 24 Self { 25 max_objects: MAX_OBJECTS, 26 max_object_bytes: MAX_OBJECT_BYTES, 27 max_total_bytes: MAX_TOTAL_BYTES, 28 max_delta_depth: MAX_DELTA_DEPTH, 29 } 30 } 31} 32 33pub(crate) fn malformed(message: &str) -> PackError { 34 PackError::Pack(message.to_string()) 35} 36 37pub fn meter(pack: &[u8], limits: &PackLimits, kind: gix::hash::Kind) -> Result<(), PackError> { 38 let hash_len = kind.len_in_bytes(); 39 if pack.len() < 12 + hash_len { 40 return Err(malformed("packfile is truncated")); 41 } 42 let trailer = pack.len() - hash_len; 43 44 let head: [u8; 12] = pack[..12] 45 .try_into() 46 .map_err(|_| malformed("packfile header is truncated"))?; 47 let (_version, num_objects) = 48 header::decode(&head).map_err(|error| PackError::Pack(error.to_string()))?; 49 if num_objects > limits.max_objects { 50 return Err(PackError::LimitExceeded(PackLimit::Objects)); 51 } 52 53 let mut offset = 12u64; 54 let mut total = 0u64; 55 let mut base_of: HashMap<u64, u64> = HashMap::new(); 56 57 (0..num_objects).try_for_each(|_| -> Result<(), PackError> { 58 let start = offset; 59 let mut reader: &[u8] = pack 60 .get(start as usize..trailer) 61 .ok_or_else(|| malformed("entry offset past pack end"))?; 62 let entry = Entry::from_read(&mut reader, offset, hash_len) 63 .map_err(|error| PackError::Pack(error.to_string()))?; 64 65 if entry.decompressed_size > limits.max_object_bytes { 66 return Err(PackError::LimitExceeded(PackLimit::ObjectBytes)); 67 } 68 total = total 69 .checked_add(entry.decompressed_size) 70 .ok_or_else(|| malformed("decompressed size overflow"))?; 71 if total > limits.max_total_bytes { 72 return Err(PackError::LimitExceeded(PackLimit::TotalBytes)); 73 } 74 75 if let Header::OfsDelta { base_distance } = entry.header { 76 let base = entry 77 .checked_base_pack_offset(base_distance) 78 .ok_or_else(|| malformed("ofs-delta base out of range"))?; 79 base_of.insert(start, base); 80 } 81 82 let data_start = entry.data_offset as usize; 83 let consumed = inflate_into( 84 pack.get(data_start..) 85 .ok_or_else(|| malformed("entry body past pack end"))?, 86 entry.decompressed_size, 87 &mut io::sink(), 88 )?; 89 offset = entry 90 .data_offset 91 .checked_add(consumed) 92 .ok_or_else(|| malformed("pack offset overflow"))?; 93 Ok(()) 94 })?; 95 96 if offset as usize != trailer { 97 return Err(malformed("pack entries do not fill packfile")); 98 } 99 100 check_depth(&base_of, limits.max_delta_depth) 101} 102 103pub(crate) fn inflate_into( 104 input: &[u8], 105 expected: u64, 106 out: &mut dyn Write, 107) -> Result<u64, PackError> { 108 let mut decompress = Decompress::new(true); 109 let mut scratch = [0u8; 8192]; 110 let mut produced = 0u64; 111 loop { 112 let consumed = decompress.total_in() as usize; 113 let out_before = decompress.total_out(); 114 let status = decompress 115 .decompress( 116 input.get(consumed..).unwrap_or_default(), 117 &mut scratch, 118 FlushDecompress::None, 119 ) 120 .map_err(|error| PackError::Pack(format!("inflate: {error}")))?; 121 let written = (decompress.total_out() - out_before) as usize; 122 produced += written as u64; 123 if produced > expected { 124 return Err(malformed("object inflates beyond its declared size")); 125 } 126 out.write_all(&scratch[..written]) 127 .map_err(|error| PackError::Pack(format!("inflate sink: {error}")))?; 128 match status { 129 Status::StreamEnd => break, 130 Status::Ok | Status::BufError => { 131 if decompress.total_in() as usize == consumed 132 && decompress.total_out() == out_before 133 { 134 return Err(malformed("inflate stalled or pack truncated")); 135 } 136 } 137 } 138 } 139 if produced != expected { 140 return Err(malformed("object decompressed size mismatch")); 141 } 142 Ok(decompress.total_in()) 143} 144 145pub(crate) fn check_depth(base_of: &HashMap<u64, u64>, max: usize) -> Result<(), PackError> { 146 base_of.keys().try_for_each(|start| { 147 let mut depth = 0usize; 148 let mut cursor = *start; 149 while let Some(&base) = base_of.get(&cursor) { 150 depth += 1; 151 if depth > max { 152 return Err(PackError::LimitExceeded(PackLimit::DeltaDepth)); 153 } 154 cursor = base; 155 } 156 Ok(()) 157 }) 158}