github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/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  	"github.com/snapcore/snapd/asserts"
    27  	"github.com/snapcore/snapd/dirs"
    28  	"github.com/snapcore/snapd/osutil"
    29  )
    30  
    31  // DeviceChange handles a change of the underlying device. Specifically it can
    32  // be used during remodel when a new device is associated with a new model. The
    33  // encryption keys will be resealed for both models. The device model file which
    34  // is measured during boot will be updated. The recovery systems that belong to
    35  // the old model will no longer be usable.
    36  func DeviceChange(from Device, to Device) error {
    37  	if !to.HasModeenv() {
    38  		// nothing useful happens on a non-UC20 system here
    39  		return nil
    40  	}
    41  
    42  	m, err := loadModeenv()
    43  	if err != nil {
    44  		return err
    45  	}
    46  
    47  	newModel := to.Model()
    48  	oldModel := from.Model()
    49  	modified := false
    50  	if modelUniqueID(m.TryModelForSealing()) != modelUniqueID(newModel) {
    51  		// we either haven't been here yet, or a reboot occurred after
    52  		// try model was cleared and modeenv was rewritten
    53  		m.setTryModel(newModel)
    54  		modified = true
    55  	}
    56  	if modelUniqueID(m.ModelForSealing()) != modelUniqueID(oldModel) {
    57  		// a modeenv with new model was already written, restore
    58  		// the 'expected' original state, the model file on disk
    59  		// will match one of the models
    60  		m.setModel(oldModel)
    61  		modified = true
    62  	}
    63  	if modified {
    64  		if err := m.Write(); err != nil {
    65  			return err
    66  		}
    67  	}
    68  
    69  	// reseal with both models now, such that we'd still be able to boot
    70  	// even if there is a reboot before the device/model file is updated, or
    71  	// before the final reseal with one model
    72  	const expectReseal = true
    73  	if err := resealKeyToModeenv(dirs.GlobalRootDir, m, expectReseal); err != nil {
    74  		// best effort clear the modeenv's try model
    75  		m.clearTryModel()
    76  		if mErr := m.Write(); mErr != nil {
    77  			return fmt.Errorf("%v (restoring modeenv failed: %v)", err, mErr)
    78  		}
    79  		return err
    80  	}
    81  
    82  	// update the device model file in boot (we may be overwriting the same
    83  	// model file if we reached this place before a reboot has occurred)
    84  	if err := writeModelToUbuntuBoot(to.Model()); err != nil {
    85  		err = fmt.Errorf("cannot write new model file: %v", err)
    86  		// the file has not been modified, so just clear the try model
    87  		m.clearTryModel()
    88  		if mErr := m.Write(); mErr != nil {
    89  			return fmt.Errorf("%v (restoring modeenv failed: %v)", err, mErr)
    90  		}
    91  		return err
    92  	}
    93  
    94  	// now we can update the model to the new one
    95  	m.setModel(newModel)
    96  	// and clear the try model
    97  	m.clearTryModel()
    98  
    99  	if err := m.Write(); err != nil {
   100  		// modeenv has not been written and still contains both the old
   101  		// and a new model, but the model file has been modified,
   102  		// restore the original model file
   103  		if restoreErr := writeModelToUbuntuBoot(from.Model()); restoreErr != nil {
   104  			return fmt.Errorf("%v (restoring model failed: %v)", err, restoreErr)
   105  		}
   106  		// however writing modeenv failed, so trying to clear the model
   107  		// and write it again could be pointless, let the failure
   108  		// percolate up the stack
   109  		return err
   110  	}
   111  
   112  	// past a successful reseal, the old recovery systems become unusable and will
   113  	// not be able to access the data anymore
   114  	if err := resealKeyToModeenv(dirs.GlobalRootDir, m, expectReseal); err != nil {
   115  		// resealing failed, but modeenv and the file have been modified
   116  
   117  		// first restore the modeenv in case we reboot, such that if the
   118  		// post reboot code reseals, it will allow both models (in case
   119  		// even more reboots occur)
   120  		m.setModel(from.Model())
   121  		m.setTryModel(newModel)
   122  		if mErr := m.Write(); mErr != nil {
   123  			return fmt.Errorf("%v (writing modeenv failed: %v)", err, mErr)
   124  		}
   125  
   126  		// restore the original model file (we have resealed for both
   127  		// models previously)
   128  		if restoreErr := writeModelToUbuntuBoot(from.Model()); restoreErr != nil {
   129  			return fmt.Errorf("%v (restoring model failed: %v)", err, restoreErr)
   130  		}
   131  
   132  		// drop the tried model
   133  		m.clearTryModel()
   134  		if mErr := m.Write(); mErr != nil {
   135  			return fmt.Errorf("%v (restoring modeenv failed: %v)", err, mErr)
   136  		}
   137  
   138  		// resealing failed, so no point in trying it again
   139  		return err
   140  	}
   141  	return nil
   142  }
   143  
   144  var writeModelToUbuntuBoot = writeModelToUbuntuBootImpl
   145  
   146  func writeModelToUbuntuBootImpl(model *asserts.Model) error {
   147  	modelPath := filepath.Join(InitramfsUbuntuBootDir, "device/model")
   148  	f, err := osutil.NewAtomicFile(modelPath, 0644, 0, osutil.NoChown, osutil.NoChown)
   149  	if err != nil {
   150  		return err
   151  	}
   152  	defer f.Cancel()
   153  	if err := asserts.NewEncoder(f).Encode(model); err != nil {
   154  		return err
   155  	}
   156  	return f.Commit()
   157  }