native_metadata.rs (7502B)
1 //! Native filesystem metadata normalization behind portable generic boundaries. 2 3 pub(crate) fn mode<T>(raw: T) -> u32 4 where 5 T: Into<u32>, 6 { 7 raw.into() 8 } 9 10 pub(crate) fn link_count<T>(raw: T) -> u64 11 where 12 T: Into<u64>, 13 { 14 raw.into() 15 } 16 17 pub(crate) fn device<T>(raw: T) -> Result<u64, T::Error> 18 where 19 T: TryInto<u64>, 20 { 21 raw.try_into() 22 } 23 24 pub(crate) fn secure_directory( 25 is_directory: bool, 26 actual_uid: u32, 27 expected_uid: u32, 28 raw_mode: u32, 29 ) -> bool { 30 crate::all_constraints([ 31 is_directory, 32 actual_uid == expected_uid, 33 raw_mode & 0o022 == 0, 34 ]) 35 } 36 37 pub(crate) fn exact_directory( 38 is_directory: bool, 39 actual_uid: u32, 40 expected_uid: u32, 41 raw_mode: u32, 42 ) -> bool { 43 crate::all_constraints([ 44 is_directory, 45 actual_uid == expected_uid, 46 raw_mode & 0o777 == 0o700, 47 ]) 48 } 49 50 pub(crate) fn restrictive_directory( 51 is_directory: bool, 52 actual_uid: u32, 53 expected_uid: u32, 54 raw_mode: u32, 55 ) -> bool { 56 crate::all_constraints([ 57 is_directory, 58 actual_uid == expected_uid, 59 matches!(raw_mode & 0o777, 0o500 | 0o700), 60 ]) 61 } 62 63 pub(crate) fn exact_regular_file( 64 is_regular_file: bool, 65 link_count: u64, 66 actual_uid: u32, 67 expected_uid: u32, 68 raw_mode: u32, 69 ) -> bool { 70 crate::all_constraints([ 71 is_regular_file, 72 link_count == 1, 73 actual_uid == expected_uid, 74 raw_mode & 0o777 == 0o600, 75 ]) 76 } 77 78 pub(crate) fn regular_owner_single_link( 79 is_regular_file: bool, 80 link_count: u64, 81 actual_uid: u32, 82 expected_uid: u32, 83 ) -> bool { 84 crate::all_constraints([is_regular_file, link_count == 1, actual_uid == expected_uid]) 85 } 86 87 pub(crate) fn restrictive_regular_file( 88 is_regular_file: bool, 89 link_count: u64, 90 actual_uid: u32, 91 expected_uid: u32, 92 raw_mode: u32, 93 ) -> bool { 94 crate::all_constraints([ 95 is_regular_file, 96 link_count == 1, 97 actual_uid == expected_uid, 98 matches!(raw_mode & 0o777, 0o400 | 0o600), 99 ]) 100 } 101 102 pub(crate) fn valid_artifact_length(length: u64, expected: Option<u64>) -> bool { 103 let expected_matches = expected.is_none_or(|expected| length == expected); 104 crate::all_constraints([length != 0, length <= i64::MAX as u64, expected_matches]) 105 } 106 107 pub(crate) fn identity_pair_matches( 108 held_device: u64, 109 held_inode: u64, 110 current_device: u64, 111 current_inode: u64, 112 expected_device: u64, 113 expected_inode: u64, 114 ) -> bool { 115 crate::all_constraints([ 116 (held_device, held_inode) == (expected_device, expected_inode), 117 (current_device, current_inode) == (expected_device, expected_inode), 118 ]) 119 } 120 121 pub(crate) fn sqlite_wal_header(header: &[u8; 20]) -> bool { 122 crate::all_constraints([ 123 &header[..16] == b"SQLite format 3\0", 124 header[18] == 2, 125 header[19] == 2, 126 ]) 127 } 128 129 pub(crate) fn sqlite_header(header: &[u8; 20]) -> bool { 130 crate::all_constraints([ 131 &header[..16] == b"SQLite format 3\0", 132 matches!(header[18], 1 | 2), 133 header[19] == header[18], 134 ]) 135 } 136 137 #[cfg(test)] 138 mod tests { 139 use super::*; 140 141 #[test] 142 fn unsigned_mode_and_link_widths_normalize_without_truncation() { 143 assert_eq!(mode(0o600_u16), 0o600); 144 assert_eq!(mode(u32::MAX), u32::MAX); 145 assert_eq!(link_count(u16::MAX), u64::from(u16::MAX)); 146 assert_eq!(link_count(u32::MAX), u64::from(u32::MAX)); 147 assert_eq!(link_count(u64::MAX), u64::MAX); 148 } 149 150 #[test] 151 fn signed_and_unsigned_device_widths_remain_checked() { 152 assert_eq!(device(7_i32), Ok(7)); 153 assert_eq!(device(u64::MAX), Ok(u64::MAX)); 154 assert!(device(-1_i32).is_err()); 155 } 156 157 #[test] 158 fn constraint_inventory_requires_every_entry() { 159 assert!(crate::all_constraints([true; 16])); 160 for index in 0..16 { 161 let mut constraints = [true; 16]; 162 constraints[index] = false; 163 assert!(!crate::all_constraints(constraints)); 164 } 165 assert!(crate::all_constraints([])); 166 } 167 168 #[test] 169 fn directory_predicates_bind_every_independent_fact() { 170 assert!(secure_directory(true, 7, 7, 0o755)); 171 assert!(!secure_directory(false, 7, 7, 0o755)); 172 assert!(!secure_directory(true, 8, 7, 0o755)); 173 assert!(!secure_directory(true, 7, 7, 0o775)); 174 175 assert!(exact_directory(true, 7, 7, 0o700)); 176 assert!(!exact_directory(false, 7, 7, 0o700)); 177 assert!(!exact_directory(true, 8, 7, 0o700)); 178 assert!(!exact_directory(true, 7, 7, 0o500)); 179 180 assert!(restrictive_directory(true, 7, 7, 0o500)); 181 assert!(restrictive_directory(true, 7, 7, 0o700)); 182 assert!(!restrictive_directory(false, 7, 7, 0o700)); 183 assert!(!restrictive_directory(true, 8, 7, 0o700)); 184 assert!(!restrictive_directory(true, 7, 7, 0o755)); 185 } 186 187 #[test] 188 fn regular_file_predicates_bind_every_independent_fact() { 189 assert!(exact_regular_file(true, 1, 7, 7, 0o600)); 190 assert!(!exact_regular_file(false, 1, 7, 7, 0o600)); 191 assert!(!exact_regular_file(true, 2, 7, 7, 0o600)); 192 assert!(!exact_regular_file(true, 1, 8, 7, 0o600)); 193 assert!(!exact_regular_file(true, 1, 7, 7, 0o400)); 194 195 assert!(regular_owner_single_link(true, 1, 7, 7)); 196 assert!(!regular_owner_single_link(false, 1, 7, 7)); 197 assert!(!regular_owner_single_link(true, 2, 7, 7)); 198 assert!(!regular_owner_single_link(true, 1, 8, 7)); 199 200 assert!(restrictive_regular_file(true, 1, 7, 7, 0o400)); 201 assert!(restrictive_regular_file(true, 1, 7, 7, 0o600)); 202 assert!(!restrictive_regular_file(false, 1, 7, 7, 0o600)); 203 assert!(!restrictive_regular_file(true, 2, 7, 7, 0o600)); 204 assert!(!restrictive_regular_file(true, 1, 8, 7, 0o600)); 205 assert!(!restrictive_regular_file(true, 1, 7, 7, 0o700)); 206 } 207 208 #[test] 209 fn artifact_length_and_identity_predicates_bind_boundaries() { 210 assert!(valid_artifact_length(1, None)); 211 assert!(valid_artifact_length( 212 i64::MAX as u64, 213 Some(i64::MAX as u64) 214 )); 215 assert!(!valid_artifact_length(0, None)); 216 assert!(!valid_artifact_length(i64::MAX as u64 + 1, None)); 217 assert!(!valid_artifact_length(1, Some(2))); 218 219 assert!(identity_pair_matches(1, 2, 1, 2, 1, 2)); 220 for values in [(0, 2, 1, 2), (1, 0, 1, 2), (1, 2, 0, 2), (1, 2, 1, 0)] { 221 assert!(!identity_pair_matches( 222 values.0, values.1, values.2, values.3, 1, 2 223 )); 224 } 225 } 226 227 #[test] 228 fn sqlite_header_requires_exact_wal_versions() { 229 let mut header = [0_u8; 20]; 230 header[..16].copy_from_slice(b"SQLite format 3\0"); 231 header[18] = 2; 232 header[19] = 2; 233 assert!(sqlite_wal_header(&header)); 234 235 let mut bad_magic = header; 236 bad_magic[0] = b'X'; 237 assert!(!sqlite_wal_header(&bad_magic)); 238 let mut bad_write = header; 239 bad_write[18] = 1; 240 assert!(!sqlite_wal_header(&bad_write)); 241 let mut bad_read = header; 242 bad_read[19] = 1; 243 assert!(!sqlite_wal_header(&bad_read)); 244 245 let mut rollback = header; 246 rollback[18] = 1; 247 rollback[19] = 1; 248 assert!(sqlite_header(&rollback)); 249 assert!(sqlite_header(&header)); 250 assert!(!sqlite_header(&bad_magic)); 251 assert!(!sqlite_header(&bad_write)); 252 assert!(!sqlite_header(&bad_read)); 253 } 254 }