1 // Copyright 2023, The Android Open Source Project 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 //! Library for constructing kernel bootconfig. See the following for more detail: 16 //! 17 //! https://source.android.com/docs/core/architecture/bootloader/implementing-bootconfig#bootloader-changes 18 19 #![cfg_attr(not(test), no_std)] 20 21 /// Library error type. 22 #[derive(Debug)] 23 pub enum BootConfigError { 24 ArithmeticOverflow, 25 BufferTooSmall, 26 InvalidConfigString, 27 GenericReaderError(i64), 28 } 29 30 /// The type of Result used in this library. 31 pub type Result<T> = core::result::Result<T, BootConfigError>; 32 33 /// A class for constructing bootconfig section. 34 pub struct BootConfigBuilder<'a> { 35 current_size: usize, 36 buffer: &'a mut [u8], 37 } 38 39 const BOOTCONFIG_MAGIC: &str = "#BOOTCONFIG\n"; 40 // Trailer structure: 41 // struct { 42 // config_size: u32, 43 // checksum: u32, 44 // bootconfig_magic: [u8] 45 // } 46 const BOOTCONFIG_TRAILER_SIZE: usize = 4 + 4 + BOOTCONFIG_MAGIC.len(); 47 48 impl<'a> BootConfigBuilder<'a> { 49 /// Initialize with a given buffer. new(buffer: &'a mut [u8]) -> Result<Self>50 pub fn new(buffer: &'a mut [u8]) -> Result<Self> { 51 if buffer.len() < BOOTCONFIG_TRAILER_SIZE { 52 return Err(BootConfigError::BufferTooSmall); 53 } 54 let mut ret = Self { current_size: 0, buffer: buffer }; 55 ret.update_trailer()?; 56 Ok(ret) 57 } 58 59 /// Get the remaining capacity for adding new bootconfig. remaining_capacity(&self) -> usize60 pub fn remaining_capacity(&self) -> usize { 61 self.buffer 62 .len() 63 .checked_sub(self.current_size) 64 .unwrap() 65 .checked_sub(BOOTCONFIG_TRAILER_SIZE) 66 .unwrap() 67 } 68 69 /// Get the whole config bytes including trailer. config_bytes(&self) -> &[u8]70 pub fn config_bytes(&self) -> &[u8] { 71 // Arithmetic not expected to fail. 72 &self.buffer[..self.current_size.checked_add(BOOTCONFIG_TRAILER_SIZE).unwrap()] 73 } 74 75 /// Append a new config via a reader callback. 76 /// 77 /// A `&mut [u8]` that covers the remaining space is passed to the callback for reading the 78 /// config bytes. It should return the total size read if operation is successful or a custom 79 /// error code via `BootConfigError::GenericReaderError(<code>)`. Attempting to return a size 80 /// greater than the input will cause it to panic. Empty read is allowed. It's up to the caller 81 /// to make sure the read content will eventually form a valid boot config. The API is for 82 /// situations where configs are read from sources such as disk and separate buffer allocation 83 /// is not possible or desired. add_with<F>(&mut self, reader: F) -> Result<()> where F: FnOnce(&mut [u8]) -> Result<usize>,84 pub fn add_with<F>(&mut self, reader: F) -> Result<()> 85 where 86 F: FnOnce(&mut [u8]) -> Result<usize>, 87 { 88 let remains = self.remaining_capacity(); 89 let size = reader(&mut self.buffer[self.current_size..][..remains])?; 90 assert!(size <= remains); 91 self.current_size += size; 92 // Content may have been modified. Re-compute trailer. 93 self.update_trailer() 94 } 95 96 /// Append a new config from string. add(&mut self, config: &str) -> Result<()>97 pub fn add(&mut self, config: &str) -> Result<()> { 98 if self.remaining_capacity() < config.len() { 99 return Err(BootConfigError::BufferTooSmall); 100 } 101 self.add_with(|out| { 102 out[..config.len()].clone_from_slice(config.as_bytes()); 103 Ok(config.len()) 104 }) 105 } 106 107 /// Update the boot config trailer at the end of parameter list. 108 /// See specification at: 109 /// https://source.android.com/docs/core/architecture/bootloader/implementing-bootconfig#bootloader-changes update_trailer(&mut self) -> Result<()>110 fn update_trailer(&mut self) -> Result<()> { 111 // Config size 112 let size: u32 = 113 self.current_size.try_into().map_err(|_| BootConfigError::ArithmeticOverflow)?; 114 // Check sum. 115 let checksum = self.checksum(); 116 let trailer = &mut self.buffer[self.current_size..]; 117 trailer[..4].clone_from_slice(&size.to_le_bytes()); 118 trailer[4..8].clone_from_slice(&checksum.to_le_bytes()); 119 trailer[8..][..BOOTCONFIG_MAGIC.len()].clone_from_slice(BOOTCONFIG_MAGIC.as_bytes()); 120 Ok(()) 121 } 122 123 /// Compute the checksum value. checksum(&self) -> u32124 fn checksum(&self) -> u32 { 125 self.buffer[..self.current_size] 126 .iter() 127 .map(|v| *v as u32) 128 .reduce(|acc, v| acc.overflowing_add(v).0) 129 .unwrap_or(0) 130 } 131 } 132 133 impl core::fmt::Display for BootConfigBuilder<'_> { fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result134 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 135 let bytes = self.config_bytes(); 136 for val in &bytes[..bytes.len().checked_sub(BOOTCONFIG_TRAILER_SIZE).unwrap()] { 137 write!(f, "{}", core::ascii::escape_default(*val))?; 138 } 139 Ok(()) 140 } 141 } 142 143 impl core::fmt::Write for BootConfigBuilder<'_> { write_str(&mut self, s: &str) -> core::fmt::Result144 fn write_str(&mut self, s: &str) -> core::fmt::Result { 145 self.add_with(|out| { 146 out.get_mut(..s.len()) 147 .ok_or(BootConfigError::BufferTooSmall)? 148 .clone_from_slice(s.as_bytes()); 149 Ok(s.len()) 150 }) 151 .map_err(|_| core::fmt::Error)?; 152 Ok(()) 153 } 154 } 155 156 #[cfg(test)] 157 mod test { 158 use super::*; 159 use core::fmt::Write; 160 161 // Taken from Cuttlefish on QEMU aarch64. 162 const TEST_CONFIG: &str = "androidboot.hardware=cutf_cvm 163 kernel.mac80211_hwsim.radios=0 164 kernel.vmw_vsock_virtio_transport_common.virtio_transport_max_vsock_pkt_buf_size=16384 165 androidboot.vendor.apex.com.google.emulated.camera.provider.hal=com.google.emulated.camera.provider.hal 166 androidboot.slot_suffix=_a 167 androidboot.force_normal_boot=1 168 androidboot.hw_timeout_multiplier=50 169 androidboot.fstab_suffix=cf.f2fs.hctr2 170 androidboot.hypervisor.protected_vm.supported=0 171 androidboot.modem_simulator_ports=9600 172 androidboot.vsock_lights_port=6900 173 androidboot.lcd_density=320 174 androidboot.vendor.audiocontrol.server.port=9410 175 androidboot.vendor.audiocontrol.server.cid=3 176 androidboot.cuttlefish_config_server_port=6800 177 androidboot.hardware.gralloc=minigbm 178 androidboot.vsock_lights_cid=3 179 androidboot.enable_confirmationui=0 180 androidboot.hypervisor.vm.supported=0 181 androidboot.setupwizard_mode=DISABLED 182 androidboot.serialno=CUTTLEFISHCVD011 183 androidboot.enable_bootanimation=1 184 androidboot.hardware.hwcomposer.display_finder_mode=drm 185 androidboot.hardware.angle_feature_overrides_enabled=preferLinearFilterForYUV:mapUnspecifiedColorSpaceToPassThrough 186 androidboot.hardware.egl=mesa 187 androidboot.boot_devices=4010000000.pcie 188 androidboot.opengles.version=196608 189 androidboot.wifi_mac_prefix=5554 190 androidboot.vsock_tombstone_port=6600 191 androidboot.hardware.hwcomposer=ranchu 192 androidboot.hardware.hwcomposer.mode=client 193 androidboot.console=ttyAMA0 194 androidboot.ddr_size=4915MB 195 androidboot.cpuvulkan.version=0 196 androidboot.serialconsole=1 197 androidboot.vbmeta.device=PARTUUID=2b7e273a-42a1-654b-bbad-8cb6ab2b6911 198 androidboot.vbmeta.avb_version=1.1 199 androidboot.vbmeta.device_state=unlocked 200 androidboot.vbmeta.hash_alg=sha256 201 androidboot.vbmeta.size=23040 202 androidboot.vbmeta.digest=6d6cdbad779475dd945ed79e6bd79c0574541d34ff488fa5aeeb024d739dd0d2 203 androidboot.vbmeta.invalidate_on_error=yes 204 androidboot.veritymode=enforcing 205 androidboot.verifiedbootstate=orange 206 "; 207 208 const TEST_CONFIG_TRAILER: &[u8; BOOTCONFIG_TRAILER_SIZE] = 209 b"i\x07\x00\x00\xf9\xc4\x02\x00#BOOTCONFIG\n"; 210 211 #[test] test_add()212 fn test_add() { 213 let mut buffer = [0u8; TEST_CONFIG.len() + TEST_CONFIG_TRAILER.len()]; 214 let mut builder = BootConfigBuilder::new(&mut buffer[..]).unwrap(); 215 builder.add(TEST_CONFIG).unwrap(); 216 assert_eq!( 217 builder.config_bytes().to_vec(), 218 [TEST_CONFIG.as_bytes(), TEST_CONFIG_TRAILER].concat().to_vec() 219 ); 220 } 221 222 #[test] test_add_incremental()223 fn test_add_incremental() { 224 let mut buffer = [0u8; TEST_CONFIG.len() + TEST_CONFIG_TRAILER.len()]; 225 let mut builder = BootConfigBuilder::new(&mut buffer[..]).unwrap(); 226 for ele in TEST_CONFIG.strip_suffix('\n').unwrap().split('\n') { 227 let config = std::string::String::from(ele) + "\n"; 228 builder.add(config.as_str()).unwrap(); 229 } 230 assert_eq!( 231 builder.config_bytes().to_vec(), 232 [TEST_CONFIG.as_bytes(), TEST_CONFIG_TRAILER].concat().to_vec() 233 ); 234 } 235 236 #[test] test_add_incremental_via_fmt_write()237 fn test_add_incremental_via_fmt_write() { 238 let mut buffer = [0u8; TEST_CONFIG.len() + TEST_CONFIG_TRAILER.len()]; 239 let mut builder = BootConfigBuilder::new(&mut buffer[..]).unwrap(); 240 for ele in TEST_CONFIG.strip_suffix('\n').unwrap().split('\n') { 241 write!(builder, "{}\n", ele).unwrap(); 242 } 243 assert_eq!( 244 builder.config_bytes().to_vec(), 245 [TEST_CONFIG.as_bytes(), TEST_CONFIG_TRAILER].concat().to_vec() 246 ); 247 } 248 249 #[test] test_new_buffer_too_small()250 fn test_new_buffer_too_small() { 251 let mut buffer = [0u8; BOOTCONFIG_TRAILER_SIZE - 1]; 252 assert!(BootConfigBuilder::new(&mut buffer[..]).is_err()); 253 } 254 255 #[test] test_add_buffer_too_small()256 fn test_add_buffer_too_small() { 257 let mut buffer = [0u8; BOOTCONFIG_TRAILER_SIZE + 1]; 258 let mut builder = BootConfigBuilder::new(&mut buffer[..]).unwrap(); 259 assert!(builder.add("a\n").is_err()); 260 } 261 262 #[test] test_add_empty_string()263 fn test_add_empty_string() { 264 let mut buffer = [0u8; BOOTCONFIG_TRAILER_SIZE + 1]; 265 let mut builder = BootConfigBuilder::new(&mut buffer[..]).unwrap(); 266 builder.add("").unwrap(); 267 } 268 269 #[test] test_add_with_error()270 fn test_add_with_error() { 271 let mut buffer = [0u8; BOOTCONFIG_TRAILER_SIZE + 1]; 272 let mut builder = BootConfigBuilder::new(&mut buffer[..]).unwrap(); 273 assert!(builder.add_with(|_| Err(BootConfigError::GenericReaderError(123))).is_err()); 274 } 275 } 276