gitee.com/mysnapcore/mysnapd@v0.1.0/boot/model.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2021 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package boot
    21  
    22  import (
    23  	"fmt"
    24  	"path/filepath"
    25  
    26  	"gitee.com/mysnapcore/mysnapd/asserts"
    27  	"gitee.com/mysnapcore/mysnapd/dirs"
    28  	"gitee.com/mysnapcore/mysnapd/osutil"
    29  	"gitee.com/mysnapcore/mysnapd/snap"
    30  )
    31  
    32  // DeviceChange handles a change of the underlying device. Specifically it can
    33  // be used during remodel when a new device is associated with a new model. The
    34  // encryption keys will be resealed for both models. The device model file which
    35  // is measured during boot will be updated. The recovery systems that belong to
    36  // the old model will no longer be usable.
    37  func DeviceChange(from snap.Device, to snap.Device) error {
    38  	if !to.HasModeenv() {
    39  		// nothing useful happens on a non-UC20 system here
    40  		return nil
    41  	}
    42  
    43  	m, err := loadModeenv()
    44  	if err != nil {
    45  		return err
    46  	}
    47  
    48  	newModel := to.Model()
    49  	oldModel := from.Model()
    50  	modified := false
    51  	if modelUniqueID(m.TryModelForSealing()) != modelUniqueID(newModel) {
    52  		// we either haven't been here yet, or a reboot occurred after
    53  		// try model was cleared and modeenv was rewritten
    54  		m.setTryModel(newModel)
    55  		modified = true
    56  	}
    57  	if modelUniqueID(m.ModelForSealing()) != modelUniqueID(oldModel) {
    58  		// a modeenv with new model was already written, restore
    59  		// the 'expected' original state, the model file on disk
    60  		// will match one of the models
    61  		m.setModel(oldModel)
    62  		modified = true
    63  	}
    64  	if modified {
    65  		if err := m.Write(); err != nil {
    66  			return err
    67  		}
    68  	}
    69  
    70  	// reseal with both models now, such that we'd still be able to boot
    71  	// even if there is a reboot before the device/model file is updated, or
    72  	// before the final reseal with one model
    73  	const expectReseal = true
    74  	if err := resealKeyToModeenv(dirs.GlobalRootDir, m, expectReseal); err != nil {
    75  		// best effort clear the modeenv's try model
    76  		m.clearTryModel()
    77  		if mErr := m.Write(); mErr != nil {
    78  			return fmt.Errorf("%v (restoring modeenv failed: %v)", err, mErr)
    79  		}
    80  		return err
    81  	}
    82  
    83  	// update the device model file in boot (we may be overwriting the same
    84  	// model file if we reached this place before a reboot has occurred)
    85  	if err := writeModelToUbuntuBoot(to.Model()); err != nil {
    86  		err = fmt.Errorf("cannot write new model file: %v", err)
    87  		// the file has not been modified, so just clear the try model
    88  		m.clearTryModel()
    89  		if mErr := m.Write(); mErr != nil {
    90  			return fmt.Errorf("%v (restoring modeenv failed: %v)", err, mErr)
    91  		}
    92  		return err
    93  	}
    94  
    95  	// now we can update the model to the new one
    96  	m.setModel(newModel)
    97  	// and clear the try model
    98  	m.clearTryModel()
    99  
   100  	if err := m.Write(); err != nil {
   101  		// modeenv has not been written and still contains both the old
   102  		// and a new model, but the model file has been modified,
   103  		// restore the original model file
   104  		if restoreErr := writeModelToUbuntuBoot(from.Model()); restoreErr != nil {
   105  			return fmt.Errorf("%v (restoring model failed: %v)", err, restoreErr)
   106  		}
   107  		// however writing modeenv failed, so trying to clear the model
   108  		// and write it again could be pointless, let the failure
   109  		// percolate up the stack
   110  		return err
   111  	}
   112  
   113  	// past a successful reseal, the old recovery systems become unusable and will
   114  	// not be able to access the data anymore
   115  	if err := resealKeyToModeenv(dirs.GlobalRootDir, m, expectReseal); err != nil {
   116  		// resealing failed, but modeenv and the file have been modified
   117  
   118  		// first restore the modeenv in case we reboot, such that if the
   119  		// post reboot code reseals, it will allow both models (in case
   120  		// even more reboots occur)
   121  		m.setModel(from.Model())
   122  		m.setTryModel(newModel)
   123  		if mErr := m.Write(); mErr != nil {
   124  			return fmt.Errorf("%v (writing modeenv failed: %v)", err, mErr)
   125  		}
   126  
   127  		// restore the original model file (we have resealed for both
   128  		// models previously)
   129  		if restoreErr := writeModelToUbuntuBoot(from.Model()); restoreErr != nil {
   130  			return fmt.Errorf("%v (restoring model failed: %v)", err, restoreErr)
   131  		}
   132  
   133  		// drop the tried model
   134  		m.clearTryModel()
   135  		if mErr := m.Write(); mErr != nil {
   136  			return fmt.Errorf("%v (restoring modeenv failed: %v)", err, mErr)
   137  		}
   138  
   139  		// resealing failed, so no point in trying it again
   140  		return err
   141  	}
   142  	return nil
   143  }
   144  
   145  var writeModelToUbuntuBoot = writeModelToUbuntuBootImpl
   146  
   147  func writeModelToUbuntuBootImpl(model *asserts.Model) error {
   148  	modelPath := filepath.Join(InitramfsUbuntuBootDir, "device/model")
   149  	f, err := osutil.NewAtomicFile(modelPath, 0644, 0, osutil.NoChown, osutil.NoChown)
   150  	if err != nil {
   151  		return err
   152  	}
   153  	defer f.Cancel()
   154  	if err := asserts.NewEncoder(f).Encode(model); err != nil {
   155  		return err
   156  	}
   157  	return f.Commit()
   158  }