github.com/transparency-dev/armored-witness-os@v0.1.3-0.20240514084412-27eef7325168/trusted_os/flash.go (about) 1 // Copyright 2022 The Armored Witness OS authors. All Rights Reserved. 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 package main 16 17 import ( 18 "bytes" 19 "fmt" 20 "log" 21 "runtime" 22 "time" 23 24 usbarmory "github.com/usbarmory/tamago/board/usbarmory/mk2" 25 "github.com/usbarmory/tamago/soc/nxp/usdhc" 26 27 "github.com/transparency-dev/armored-witness-boot/config" 28 "github.com/transparency-dev/armored-witness-common/release/firmware" 29 ) 30 31 // imx6_usdhc: 15 GB/14 GiB card detected {MMC:true SD:false HC:true HS:true DDR:false Rate:150 BlockSize:512 Blocks:30576640 32 const ( 33 expectedBlockSize = 512 // Expected size of MMC block in bytes 34 otaLimit = 31457280 35 taConfBlock = 0x200000 36 taBlockA = 0x200050 37 taBlockB = 0x2FD050 38 osConfBlock = 0x5000 39 osBlockA = 0x5050 40 osBlockB = 0x102828 41 crashLogBlock = 0x1D20000 // For storing contents of log ringbuffer on applet crash for later investigation. 42 crashLogNumBlocks = 0x800 // 1MB 43 batchSize = 2048 44 ) 45 46 const ( 47 Firmware_Applet FirmwareType = iota 48 Firmware_OS 49 ) 50 51 var ( 52 // appletLoadedFromBlock is set to the first block of MMC where the applet firmware was loaded from. 53 // This will be set by the read func below. 54 appletLoadedFromBlock int64 55 56 // osLoadedFromBlock is set to the first block of MMC the running OS firmware was loaded from. 57 // This will be set by the determineLoadedOS func below. 58 osLoadedFromBlock int64 59 ) 60 61 // FirmwareType represents the types of updatable firmware. 62 type FirmwareType int 63 64 func (ft FirmwareType) String() string { 65 switch ft { 66 case Firmware_Applet: 67 return "applet" 68 case Firmware_OS: 69 return "OS" 70 } 71 panic(fmt.Errorf("Unknown FirmwareType %v", ft)) 72 } 73 74 type otaBuffer struct { 75 total uint32 76 seq uint32 77 sig []byte 78 buf []byte 79 bundle *config.ProofBundle 80 } 81 82 // Card mostly mirrors the public API of the usdhc.Card struct, allowing 83 // substitutions for testing. 84 type Card interface { 85 // Read reads size bytes at offset from the underlying storage. 86 Read(offset int64, size int64) ([]byte, error) 87 //WriteBlocks writes data at sector lba onwards on the underlying storage. 88 WriteBlocks(lba int, data []byte) error 89 // Info returns information about the underlying storage. 90 Info() usdhc.CardInfo 91 // Detect causes the underlying storage to probe itself. 92 Detect() error 93 } 94 95 // readConfig reads and parses a firmware config structure stored in the given block. 96 func readConfig(card Card, configBlock int64) (*config.Config, error) { 97 buf, err := card.Read(configBlock*expectedBlockSize, config.MaxLength) 98 if err != nil { 99 return nil, err 100 } 101 102 conf := &config.Config{} 103 if err := conf.Decode(buf); err != nil { 104 return nil, err 105 } 106 107 return conf, nil 108 } 109 110 // determineLoadedOSBlock reads the current OS config, and updates osLoadedFromBlock with the 111 // MMC block index where the corresponding firmware image can be found. 112 func determineLoadedOSBlock(card Card) error { 113 blockSize := card.Info().BlockSize 114 if blockSize != expectedBlockSize { 115 return fmt.Errorf("h/w invariant error - expected MMC blocksize %d, found %d", expectedBlockSize, blockSize) 116 } 117 118 conf, err := readConfig(card, osConfBlock) 119 if err != nil { 120 return fmt.Errorf("failed to read OS config: %v", err) 121 } 122 123 osLoadedFromBlock = conf.Offset / expectedBlockSize 124 switch osLoadedFromBlock { 125 case osBlockA: 126 log.Print("Loaded OS from slot A") 127 case osBlockB: 128 log.Print("Loaded OS from slot B") 129 default: 130 log.Printf("Loaded OS from unexpected block %d", osLoadedFromBlock) 131 } 132 return nil 133 } 134 135 // read reads the trusted applet bundle from internal storage, the 136 // applet and FT proofs are *not* verified by this function. 137 // 138 // This function will update appletLoadedFromBlock with the MMC block index 139 // the applet firmware image was loaded from. 140 func read(card Card) (fw *firmware.Bundle, err error) { 141 blockSize := card.Info().BlockSize 142 if blockSize != expectedBlockSize { 143 return nil, fmt.Errorf("h/w invariant error - expected MMC blocksize %d, found %d", expectedBlockSize, blockSize) 144 } 145 146 conf, err := readConfig(card, taConfBlock) 147 if err != nil { 148 return nil, fmt.Errorf("failed to read applet config: %v", err) 149 } 150 151 fw = &firmware.Bundle{ 152 Checkpoint: conf.Bundle.Checkpoint, 153 Index: conf.Bundle.LogIndex, 154 InclusionProof: conf.Bundle.InclusionProof, 155 Manifest: conf.Bundle.Manifest, 156 } 157 158 fw.Firmware, err = card.Read(conf.Offset, conf.Size) 159 if err != nil { 160 return nil, fmt.Errorf("failed to read firmware: %v", err) 161 } 162 163 appletLoadedFromBlock = conf.Offset / expectedBlockSize 164 switch appletLoadedFromBlock { 165 case taBlockA: 166 log.Print("Loaded applet from slot A") 167 case taBlockB: 168 log.Print("Loaded applet from slot B") 169 default: 170 log.Printf("Loaded applet from unexpected block %d", appletLoadedFromBlock) 171 } 172 173 return 174 } 175 176 // flash writes a buffer to internal storage. 177 // 178 // Since this function is writing blocks to MMC, it will pad the passed in 179 // buf with zeros to ensure full MMC blocks are written. 180 func flash(card Card, buf []byte, lba int) (err error) { 181 blockSize := card.Info().BlockSize 182 if blockSize != expectedBlockSize { 183 return fmt.Errorf("h/w invariant error - expected MMC blocksize %d, found %d", expectedBlockSize, blockSize) 184 } 185 186 // write in chunks to limit DMA requirements 187 bytesPerChunk := blockSize * batchSize 188 for blocks := 0; len(buf) > 0; { 189 var chunk []byte 190 if len(buf) >= bytesPerChunk { 191 chunk = buf[:bytesPerChunk] 192 buf = buf[bytesPerChunk:] 193 } else { 194 // The final chunk could end with a partial MMC block, so it may need padding with zeroes to make up 195 // a whole MMC block size. We'll do this with a separate buffer rather than trying to extend the 196 // passed-in buf as doing so will potentially cause a re-alloc & copy which would temporarily use double 197 // the amount of RAM. 198 roundedUpSize := ((len(buf) / blockSize) + 1) * blockSize 199 chunk = make([]byte, roundedUpSize) 200 copy(chunk, buf) 201 buf = []byte{} 202 } 203 if err = card.WriteBlocks(lba+blocks, chunk); err != nil { 204 return 205 } 206 blocks += len(chunk) / blockSize 207 208 log.Printf("flashed %d blocks", blocks) 209 } 210 211 return 212 } 213 214 func blinkenLights() (func(), func()) { 215 var exit = make(chan bool) 216 cancel := func() { close(exit) } 217 218 blink := func() { 219 var on bool 220 221 for { 222 select { 223 case <-exit: 224 usbarmory.LED("white", false) 225 return 226 default: 227 } 228 229 on = !on 230 usbarmory.LED("white", on) 231 232 runtime.Gosched() 233 time.Sleep(100 * time.Millisecond) 234 } 235 } 236 237 return blink, cancel 238 } 239 240 // updateApplet verifies an applet update and flashes it to internal storage 241 func updateApplet(storage Card, taELF []byte, pb config.ProofBundle) (err error) { 242 // First, verify everything is correct and that, as far as we can tell, 243 // we would succeed in loadering and launching this applet upon next boot. 244 bundle := firmware.Bundle{ 245 Checkpoint: pb.Checkpoint, 246 Index: pb.LogIndex, 247 InclusionProof: pb.InclusionProof, 248 Manifest: pb.Manifest, 249 Firmware: taELF, 250 } 251 if _, err := AppletBundleVerifier.Verify(bundle); err != nil { 252 return err 253 } 254 log.Printf("SM verified applet bundle for update") 255 256 return flashFirmware(storage, Firmware_Applet, taELF, pb) 257 } 258 259 // updateOS verifies an OS update and flashes it to internal storage 260 func updateOS(storage Card, osELF []byte, pb config.ProofBundle) (err error) { 261 // First, verify everything is correct and that, as far as we can tell, 262 // we would succeed in loadering and launching this applet upon next boot. 263 bundle := firmware.Bundle{ 264 Checkpoint: pb.Checkpoint, 265 Index: pb.LogIndex, 266 InclusionProof: pb.InclusionProof, 267 Manifest: pb.Manifest, 268 Firmware: osELF, 269 } 270 if _, err := OSBundleVerifier.Verify(bundle); err != nil { 271 return err 272 } 273 log.Printf("SM verified applet bundle for update") 274 275 return flashFirmware(storage, Firmware_OS, osELF, pb) 276 } 277 278 // flashFirmware writes config & elf bytes to the MMC in the correct region for the specificed type of firmware. 279 func flashFirmware(storage Card, t FirmwareType, elf []byte, pb config.ProofBundle) error { 280 if storage == nil { 281 return fmt.Errorf("Flashing %s error: missing Storage", t) 282 } 283 284 blink, cancel := blinkenLights() 285 defer cancel() 286 go blink() 287 288 confBlock := 0 289 elfBlock := 0 290 switch t { 291 case Firmware_Applet: 292 confBlock = taConfBlock 293 if appletLoadedFromBlock == taBlockA { 294 elfBlock = taBlockB 295 log.Print("SM will flash applet to slot B") 296 } else { 297 // If the running applet was loaded from applet slot B, or there was no valid config, store in slot A 298 elfBlock = taBlockA 299 log.Print("SM will flash applet to slot A") 300 } 301 case Firmware_OS: 302 confBlock = osConfBlock 303 if osLoadedFromBlock == osBlockA { 304 elfBlock = osBlockB 305 log.Print("SM will flash OS to slot B") 306 } else { 307 // If the running OS was loaded from OS slot B, or there was no valid config, store in slot A 308 elfBlock = osBlockA 309 log.Print("SM will flash OS to slot A") 310 } 311 default: 312 return fmt.Errorf("unknown firmware type %v", t) 313 } 314 315 // Convert the signature to an armory-witness-boot format to serialize 316 // all required information for applet loading. 317 conf := &config.Config{ 318 Size: int64(len(elf)), 319 Bundle: pb, 320 Offset: int64(elfBlock) * expectedBlockSize, 321 } 322 323 confEnc, err := conf.Encode() 324 if err != nil { 325 return err 326 } 327 328 // Flash firmware bytes first before updating config so that in case of any error the unit 329 // will still boot the previous working firmware. 330 log.Printf("SM flashing %s (%d bytes) @ 0x%x", t, len(elf), elfBlock) 331 if err = flash(storage, elf, elfBlock); err != nil { 332 return fmt.Errorf("%s flashing error: %v", t, err) 333 } 334 335 log.Printf("SM flashing %s config (%d bytes) @ 0x%x", t, len(confEnc), confBlock) 336 if err = flash(storage, confEnc, confBlock); err != nil { 337 return fmt.Errorf("%s signature flashing error: %v", t, err) 338 } 339 340 log.Printf("SM %s update complete", t) 341 return nil 342 } 343 344 func storeAppletCrashLog(storage Card, l []byte) error { 345 log.Printf("SM storing applet exit log") 346 defer log.Printf("SM applet exit log stored") 347 348 maxLogSize := crashLogNumBlocks * expectedBlockSize 349 if ll := len(l); ll > maxLogSize { 350 l = l[ll-maxLogSize:] 351 } else if ll < maxLogSize { 352 // Pad up so we overwrite all of any prior logging to avoid confusion. 353 l = append(l, make([]byte, maxLogSize-ll)...) 354 } 355 return storage.WriteBlocks(crashLogBlock, l) 356 } 357 358 func retrieveLastCrashLog(storage Card) ([]byte, error) { 359 maxLogSize := crashLogNumBlocks * expectedBlockSize 360 r, err := storage.Read(crashLogBlock*expectedBlockSize, int64(maxLogSize)) 361 if err != nil { 362 return nil, err 363 } 364 if p := bytes.IndexByte(r, 0); p > 0 { 365 r = r[:p] 366 } 367 return r, nil 368 }