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  }