github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/secboot/secboot_tpm.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  // +build !nosecboot
     3  
     4  /*
     5   * Copyright (C) 2020 Canonical Ltd
     6   *
     7   * This program is free software: you can redistribute it and/or modify
     8   * it under the terms of the GNU General Public License version 3 as
     9   * published by the Free Software Foundation.
    10   *
    11   * This program is distributed in the hope that it will be useful,
    12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14   * GNU General Public License for more details.
    15   *
    16   * You should have received a copy of the GNU General Public License
    17   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18   *
    19   */
    20  
    21  package secboot
    22  
    23  import (
    24  	"crypto/rand"
    25  	"errors"
    26  	"fmt"
    27  	"io/ioutil"
    28  	"os"
    29  
    30  	"github.com/canonical/go-tpm2"
    31  	sb "github.com/snapcore/secboot"
    32  	"golang.org/x/xerrors"
    33  
    34  	"github.com/snapcore/snapd/asserts"
    35  	"github.com/snapcore/snapd/bootloader"
    36  	"github.com/snapcore/snapd/bootloader/efi"
    37  	"github.com/snapcore/snapd/logger"
    38  	"github.com/snapcore/snapd/osutil"
    39  	"github.com/snapcore/snapd/randutil"
    40  	"github.com/snapcore/snapd/snap/snapfile"
    41  )
    42  
    43  const (
    44  	keyringPrefix = "ubuntu-fde"
    45  )
    46  
    47  var (
    48  	sbConnectToDefaultTPM                  = sb.ConnectToDefaultTPM
    49  	sbMeasureSnapSystemEpochToTPM          = sb.MeasureSnapSystemEpochToTPM
    50  	sbMeasureSnapModelToTPM                = sb.MeasureSnapModelToTPM
    51  	sbBlockPCRProtectionPolicies           = sb.BlockPCRProtectionPolicies
    52  	sbActivateVolumeWithTPMSealedKey       = sb.ActivateVolumeWithTPMSealedKey
    53  	sbAddEFISecureBootPolicyProfile        = sb.AddEFISecureBootPolicyProfile
    54  	sbAddEFIBootManagerProfile             = sb.AddEFIBootManagerProfile
    55  	sbAddSystemdEFIStubProfile             = sb.AddSystemdEFIStubProfile
    56  	sbAddSnapModelProfile                  = sb.AddSnapModelProfile
    57  	sbSealKeyToTPMMultiple                 = sb.SealKeyToTPMMultiple
    58  	sbUpdateKeyPCRProtectionPolicyMultiple = sb.UpdateKeyPCRProtectionPolicyMultiple
    59  
    60  	randutilRandomKernelUUID = randutil.RandomKernelUUID
    61  
    62  	isTPMEnabled = isTPMEnabledImpl
    63  	provisionTPM = provisionTPMImpl
    64  
    65  	// dummy to check whether the interfaces match
    66  	_ (sb.SnapModel) = ModelForSealing(nil)
    67  )
    68  
    69  func isTPMEnabledImpl(tpm *sb.TPMConnection) bool {
    70  	return tpm.IsEnabled()
    71  }
    72  
    73  func CheckTPMKeySealingSupported() error {
    74  	logger.Noticef("checking if secure boot is enabled...")
    75  	if err := checkSecureBootEnabled(); err != nil {
    76  		logger.Noticef("secure boot not enabled: %v", err)
    77  		return err
    78  	}
    79  	logger.Noticef("secure boot is enabled")
    80  
    81  	logger.Noticef("checking if TPM device is available...")
    82  	tpm, err := sbConnectToDefaultTPM()
    83  	if err != nil {
    84  		err = fmt.Errorf("cannot connect to TPM device: %v", err)
    85  		logger.Noticef("%v", err)
    86  		return err
    87  	}
    88  	defer tpm.Close()
    89  
    90  	if !isTPMEnabled(tpm) {
    91  		logger.Noticef("TPM device detected but not enabled")
    92  		return fmt.Errorf("TPM device is not enabled")
    93  	}
    94  
    95  	logger.Noticef("TPM device detected and enabled")
    96  
    97  	return nil
    98  }
    99  
   100  func checkSecureBootEnabled() error {
   101  	// 8be4df61-93ca-11d2-aa0d-00e098032b8c is the EFI Global Variable vendor GUID
   102  	b, _, err := efi.ReadVarBytes("SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c")
   103  	if err != nil {
   104  		if err == efi.ErrNoEFISystem {
   105  			return err
   106  		}
   107  		return fmt.Errorf("cannot read secure boot variable: %v", err)
   108  	}
   109  	if len(b) < 1 {
   110  		return errors.New("secure boot variable does not exist")
   111  	}
   112  	if b[0] != 1 {
   113  		return errors.New("secure boot is disabled")
   114  	}
   115  
   116  	return nil
   117  }
   118  
   119  // initramfsPCR is the TPM PCR that we reserve for the EFI image and use
   120  // for measurement from the initramfs.
   121  const initramfsPCR = 12
   122  
   123  func secureConnectToTPM(ekcfile string) (*sb.TPMConnection, error) {
   124  	ekCertReader, err := os.Open(ekcfile)
   125  	if err != nil {
   126  		return nil, fmt.Errorf("cannot open endorsement key certificate file: %v", err)
   127  	}
   128  	defer ekCertReader.Close()
   129  
   130  	return sb.SecureConnectToDefaultTPM(ekCertReader, nil)
   131  }
   132  
   133  func insecureConnectToTPM() (*sb.TPMConnection, error) {
   134  	return sbConnectToDefaultTPM()
   135  }
   136  
   137  func measureWhenPossible(whatHow func(tpm *sb.TPMConnection) error) error {
   138  	// the model is ready, we're good to try measuring it now
   139  	tpm, err := insecureConnectToTPM()
   140  	if err != nil {
   141  		if xerrors.Is(err, sb.ErrNoTPM2Device) {
   142  			return nil
   143  		}
   144  		return fmt.Errorf("cannot open TPM connection: %v", err)
   145  	}
   146  	defer tpm.Close()
   147  
   148  	if !isTPMEnabled(tpm) {
   149  		return nil
   150  	}
   151  
   152  	return whatHow(tpm)
   153  }
   154  
   155  // MeasureSnapSystemEpochWhenPossible measures the snap system epoch only if the
   156  // TPM device is available. If there's no TPM device success is returned.
   157  func MeasureSnapSystemEpochWhenPossible() error {
   158  	measure := func(tpm *sb.TPMConnection) error {
   159  		return sbMeasureSnapSystemEpochToTPM(tpm, initramfsPCR)
   160  	}
   161  
   162  	if err := measureWhenPossible(measure); err != nil {
   163  		return fmt.Errorf("cannot measure snap system epoch: %v", err)
   164  	}
   165  
   166  	return nil
   167  }
   168  
   169  // MeasureSnapModelWhenPossible measures the snap model only if the TPM device is
   170  // available. If there's no TPM device success is returned.
   171  func MeasureSnapModelWhenPossible(findModel func() (*asserts.Model, error)) error {
   172  	measure := func(tpm *sb.TPMConnection) error {
   173  		model, err := findModel()
   174  		if err != nil {
   175  			return err
   176  		}
   177  		return sbMeasureSnapModelToTPM(tpm, initramfsPCR, model)
   178  	}
   179  
   180  	if err := measureWhenPossible(measure); err != nil {
   181  		return fmt.Errorf("cannot measure snap model: %v", err)
   182  	}
   183  
   184  	return nil
   185  }
   186  
   187  func lockTPMSealedKeys() error {
   188  	tpm, tpmErr := sbConnectToDefaultTPM()
   189  	if tpmErr != nil {
   190  		if xerrors.Is(tpmErr, sb.ErrNoTPM2Device) {
   191  			logger.Noticef("cannot open TPM connection: %v", tpmErr)
   192  			return nil
   193  		}
   194  		return fmt.Errorf("cannot lock TPM: %v", tpmErr)
   195  	}
   196  	defer tpm.Close()
   197  
   198  	// Lock access to the sealed keys. This should be called whenever there
   199  	// is a TPM device detected, regardless of whether secure boot is enabled
   200  	// or there is an encrypted volume to unlock. Note that snap-bootstrap can
   201  	// be called several times during initialization, and if there are multiple
   202  	// volumes to unlock we should lock access to the sealed keys only after
   203  	// the last encrypted volume is unlocked, in which case lockKeysOnFinish
   204  	// should be set to true.
   205  	//
   206  	// We should only touch the PCR that we've currently reserved for the kernel
   207  	// EFI image. Touching others will break the ability to perform any kind of
   208  	// attestation using the TPM because it will make the log inconsistent.
   209  	return sbBlockPCRProtectionPolicies(tpm, []int{initramfsPCR})
   210  }
   211  
   212  func unlockVolumeUsingSealedKeyTPM(name, sealedEncryptionKeyFile, sourceDevice, targetDevice, mapperName string, opts *UnlockVolumeUsingSealedKeyOptions) (UnlockResult, error) {
   213  	// TODO:UC20: use sb.SecureConnectToDefaultTPM() if we decide there's benefit in doing that or
   214  	//            we have a hard requirement for a valid EK cert chain for every boot (ie, panic
   215  	//            if there isn't one). But we can't do that as long as we need to download
   216  	//            intermediate certs from the manufacturer.
   217  
   218  	res := UnlockResult{IsEncrypted: true, PartDevice: sourceDevice}
   219  	// Obtain a TPM connection.
   220  	tpm, tpmErr := sbConnectToDefaultTPM()
   221  	if tpmErr != nil {
   222  		if !xerrors.Is(tpmErr, sb.ErrNoTPM2Device) {
   223  			return res, fmt.Errorf("cannot unlock encrypted device %q: %v", name, tpmErr)
   224  		}
   225  		logger.Noticef("cannot open TPM connection: %v", tpmErr)
   226  	} else {
   227  		defer tpm.Close()
   228  	}
   229  
   230  	// Also check if the TPM device is enabled. The platform firmware may disable the storage
   231  	// and endorsement hierarchies, but the device will remain visible to the operating system.
   232  	tpmDeviceAvailable := tpmErr == nil && isTPMEnabled(tpm)
   233  
   234  	// if we don't have a tpm, and we allow using a recovery key, do that
   235  	// directly
   236  	if !tpmDeviceAvailable && opts.AllowRecoveryKey {
   237  		if err := UnlockEncryptedVolumeWithRecoveryKey(mapperName, sourceDevice); err != nil {
   238  			return res, err
   239  		}
   240  		res.FsDevice = targetDevice
   241  		res.UnlockMethod = UnlockedWithRecoveryKey
   242  		return res, nil
   243  	}
   244  
   245  	// otherwise we have a tpm and we should use the sealed key first, but
   246  	// this method will fallback to using the recovery key if enabled
   247  	method, err := unlockEncryptedPartitionWithSealedKey(tpm, mapperName, sourceDevice, sealedEncryptionKeyFile, "", opts.AllowRecoveryKey)
   248  	res.UnlockMethod = method
   249  	if err == nil {
   250  		res.FsDevice = targetDevice
   251  	}
   252  	return res, err
   253  }
   254  
   255  func isActivatedWithRecoveryKey(err error) bool {
   256  	if err == nil {
   257  		return false
   258  	}
   259  	// with non-nil err, we should check for err being ActivateWithTPMSealedKeyError
   260  	// and RecoveryKeyUsageErr inside that being nil - this indicates that the
   261  	// recovery key was used to unlock it
   262  	activateErr, ok := err.(*sb.ActivateWithTPMSealedKeyError)
   263  	if !ok {
   264  		return false
   265  	}
   266  	return activateErr.RecoveryKeyUsageErr == nil
   267  }
   268  
   269  func activateVolOpts(allowRecoveryKey bool) *sb.ActivateVolumeOptions {
   270  	options := sb.ActivateVolumeOptions{
   271  		PassphraseTries: 1,
   272  		// disable recovery key by default
   273  		RecoveryKeyTries: 0,
   274  		KeyringPrefix:    keyringPrefix,
   275  	}
   276  	if allowRecoveryKey {
   277  		// enable recovery key only when explicitly allowed
   278  		options.RecoveryKeyTries = 3
   279  	}
   280  	return &options
   281  }
   282  
   283  // unlockEncryptedPartitionWithSealedKey unseals the keyfile and opens an encrypted
   284  // device. If activation with the sealed key fails, this function will attempt to
   285  // activate it with the fallback recovery key instead.
   286  func unlockEncryptedPartitionWithSealedKey(tpm *sb.TPMConnection, name, device, keyfile, pinfile string, allowRecovery bool) (UnlockMethod, error) {
   287  	options := activateVolOpts(allowRecovery)
   288  	// XXX: pinfile is currently not used
   289  	activated, err := sbActivateVolumeWithTPMSealedKey(tpm, name, device, keyfile, nil, options)
   290  
   291  	if activated {
   292  		// non nil error may indicate the volume was unlocked using the
   293  		// recovery key
   294  		if err == nil {
   295  			logger.Noticef("successfully activated encrypted device %q with TPM", device)
   296  			return UnlockedWithSealedKey, nil
   297  		} else if isActivatedWithRecoveryKey(err) {
   298  			logger.Noticef("successfully activated encrypted device %q using a fallback activation method", device)
   299  			return UnlockedWithRecoveryKey, nil
   300  		}
   301  		// no other error is possible when activation succeeded
   302  		return UnlockStatusUnknown, fmt.Errorf("internal error: volume activated with unexpected error: %v", err)
   303  	}
   304  	// ActivateVolumeWithTPMSealedKey should always return an error if activated == false
   305  	return NotUnlocked, fmt.Errorf("cannot activate encrypted device %q: %v", device, err)
   306  }
   307  
   308  // SealKeys provisions the TPM and seals the encryption keys according to the
   309  // specified parameters. If the TPM is already provisioned, or a sealed key already
   310  // exists, SealKeys will fail and return an error.
   311  func SealKeys(keys []SealKeyRequest, params *SealKeysParams) error {
   312  	numModels := len(params.ModelParams)
   313  	if numModels < 1 {
   314  		return fmt.Errorf("at least one set of model-specific parameters is required")
   315  	}
   316  
   317  	tpm, err := sbConnectToDefaultTPM()
   318  	if err != nil {
   319  		return fmt.Errorf("cannot connect to TPM: %v", err)
   320  	}
   321  	defer tpm.Close()
   322  	if !isTPMEnabled(tpm) {
   323  		return fmt.Errorf("TPM device is not enabled")
   324  	}
   325  
   326  	pcrProfile, err := buildPCRProtectionProfile(params.ModelParams)
   327  	if err != nil {
   328  		return err
   329  	}
   330  
   331  	if params.TPMProvision {
   332  		// Provision the TPM as late as possible
   333  		if err := tpmProvision(tpm, params.TPMLockoutAuthFile); err != nil {
   334  			return err
   335  		}
   336  	}
   337  
   338  	// Seal the provided keys to the TPM
   339  	creationParams := sb.KeyCreationParams{
   340  		PCRProfile:             pcrProfile,
   341  		PCRPolicyCounterHandle: tpm2.Handle(params.PCRPolicyCounterHandle),
   342  		AuthKey:                params.TPMPolicyAuthKey,
   343  	}
   344  
   345  	sbKeys := make([]*sb.SealKeyRequest, 0, len(keys))
   346  	for i := range keys {
   347  		sbKeys = append(sbKeys, &sb.SealKeyRequest{
   348  			Key:  keys[i].Key,
   349  			Path: keys[i].KeyFile,
   350  		})
   351  	}
   352  
   353  	authKey, err := sbSealKeyToTPMMultiple(tpm, sbKeys, &creationParams)
   354  	if err != nil {
   355  		return err
   356  	}
   357  	if params.TPMPolicyAuthKeyFile != "" {
   358  		if err := osutil.AtomicWriteFile(params.TPMPolicyAuthKeyFile, authKey, 0600, 0); err != nil {
   359  			return fmt.Errorf("cannot write the policy auth key file: %v", err)
   360  		}
   361  	}
   362  
   363  	return nil
   364  }
   365  
   366  // ResealKeys updates the PCR protection policy for the sealed encryption keys
   367  // according to the specified parameters.
   368  func ResealKeys(params *ResealKeysParams) error {
   369  	numModels := len(params.ModelParams)
   370  	if numModels < 1 {
   371  		return fmt.Errorf("at least one set of model-specific parameters is required")
   372  	}
   373  
   374  	tpm, err := sbConnectToDefaultTPM()
   375  	if err != nil {
   376  		return fmt.Errorf("cannot connect to TPM: %v", err)
   377  	}
   378  	defer tpm.Close()
   379  	if !isTPMEnabled(tpm) {
   380  		return fmt.Errorf("TPM device is not enabled")
   381  	}
   382  
   383  	pcrProfile, err := buildPCRProtectionProfile(params.ModelParams)
   384  	if err != nil {
   385  		return err
   386  	}
   387  
   388  	authKey, err := ioutil.ReadFile(params.TPMPolicyAuthKeyFile)
   389  	if err != nil {
   390  		return fmt.Errorf("cannot read the policy auth key file: %v", err)
   391  	}
   392  
   393  	return sbUpdateKeyPCRProtectionPolicyMultiple(tpm, params.KeyFiles, authKey, pcrProfile)
   394  }
   395  
   396  func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb.PCRProtectionProfile, error) {
   397  	numModels := len(modelParams)
   398  	modelPCRProfiles := make([]*sb.PCRProtectionProfile, 0, numModels)
   399  
   400  	for _, mp := range modelParams {
   401  		modelProfile := sb.NewPCRProtectionProfile()
   402  
   403  		loadSequences, err := buildLoadSequences(mp.EFILoadChains)
   404  		if err != nil {
   405  			return nil, fmt.Errorf("cannot build EFI image load sequences: %v", err)
   406  		}
   407  
   408  		// Add EFI secure boot policy profile
   409  		policyParams := sb.EFISecureBootPolicyProfileParams{
   410  			PCRAlgorithm:  tpm2.HashAlgorithmSHA256,
   411  			LoadSequences: loadSequences,
   412  			// TODO:UC20: set SignatureDbUpdateKeystore to support applying forbidden
   413  			//            signature updates to blacklist signing keys (after rotating them).
   414  			//            This also requires integration of sbkeysync, and some work to
   415  			//            ensure that the PCR profile is updated before/after sbkeysync executes.
   416  		}
   417  
   418  		if err := sbAddEFISecureBootPolicyProfile(modelProfile, &policyParams); err != nil {
   419  			return nil, fmt.Errorf("cannot add EFI secure boot policy profile: %v", err)
   420  		}
   421  
   422  		// Add EFI boot manager profile
   423  		bootManagerParams := sb.EFIBootManagerProfileParams{
   424  			PCRAlgorithm:  tpm2.HashAlgorithmSHA256,
   425  			LoadSequences: loadSequences,
   426  		}
   427  		if err := sbAddEFIBootManagerProfile(modelProfile, &bootManagerParams); err != nil {
   428  			return nil, fmt.Errorf("cannot add EFI boot manager profile: %v", err)
   429  		}
   430  
   431  		// Add systemd EFI stub profile
   432  		if len(mp.KernelCmdlines) != 0 {
   433  			systemdStubParams := sb.SystemdEFIStubProfileParams{
   434  				PCRAlgorithm:   tpm2.HashAlgorithmSHA256,
   435  				PCRIndex:       initramfsPCR,
   436  				KernelCmdlines: mp.KernelCmdlines,
   437  			}
   438  			if err := sbAddSystemdEFIStubProfile(modelProfile, &systemdStubParams); err != nil {
   439  				return nil, fmt.Errorf("cannot add systemd EFI stub profile: %v", err)
   440  			}
   441  		}
   442  
   443  		// Add snap model profile
   444  		if mp.Model != nil {
   445  			snapModelParams := sb.SnapModelProfileParams{
   446  				PCRAlgorithm: tpm2.HashAlgorithmSHA256,
   447  				PCRIndex:     initramfsPCR,
   448  				Models:       []sb.SnapModel{mp.Model},
   449  			}
   450  			if err := sbAddSnapModelProfile(modelProfile, &snapModelParams); err != nil {
   451  				return nil, fmt.Errorf("cannot add snap model profile: %v", err)
   452  			}
   453  		}
   454  
   455  		modelPCRProfiles = append(modelPCRProfiles, modelProfile)
   456  	}
   457  
   458  	var pcrProfile *sb.PCRProtectionProfile
   459  	if numModels > 1 {
   460  		pcrProfile = sb.NewPCRProtectionProfile().AddProfileOR(modelPCRProfiles...)
   461  	} else {
   462  		pcrProfile = modelPCRProfiles[0]
   463  	}
   464  
   465  	logger.Debugf("PCR protection profile:\n%s", pcrProfile.String())
   466  
   467  	return pcrProfile, nil
   468  }
   469  
   470  func tpmProvision(tpm *sb.TPMConnection, lockoutAuthFile string) error {
   471  	// Create and save the lockout authorization file
   472  	lockoutAuth := make([]byte, 16)
   473  	// crypto rand is protected against short reads
   474  	_, err := rand.Read(lockoutAuth)
   475  	if err != nil {
   476  		return fmt.Errorf("cannot create lockout authorization: %v", err)
   477  	}
   478  	if err := osutil.AtomicWriteFile(lockoutAuthFile, lockoutAuth, 0600, 0); err != nil {
   479  		return fmt.Errorf("cannot write the lockout authorization file: %v", err)
   480  	}
   481  
   482  	// TODO:UC20: ideally we should ask the firmware to clear the TPM and then reboot
   483  	//            if the device has previously been provisioned, see
   484  	//            https://godoc.org/github.com/snapcore/secboot#RequestTPMClearUsingPPI
   485  	if err := provisionTPM(tpm, sb.ProvisionModeFull, lockoutAuth); err != nil {
   486  		logger.Noticef("TPM provisioning error: %v", err)
   487  		return fmt.Errorf("cannot provision TPM: %v", err)
   488  	}
   489  	return nil
   490  }
   491  
   492  func provisionTPMImpl(tpm *sb.TPMConnection, mode sb.ProvisionMode, lockoutAuth []byte) error {
   493  	return tpm.EnsureProvisioned(mode, lockoutAuth)
   494  }
   495  
   496  // buildLoadSequences builds EFI load image event trees from this package LoadChains
   497  func buildLoadSequences(chains []*LoadChain) (loadseqs []*sb.EFIImageLoadEvent, err error) {
   498  	// this will build load event trees for the current
   499  	// device configuration, e.g. something like:
   500  	//
   501  	// shim -> recovery grub -> recovery kernel 1
   502  	//                      |-> recovery kernel 2
   503  	//                      |-> recovery kernel ...
   504  	//                      |-> normal grub -> run kernel good
   505  	//                                     |-> run kernel try
   506  
   507  	for _, chain := range chains {
   508  		// root of load events has source Firmware
   509  		loadseq, err := chain.loadEvent(sb.Firmware)
   510  		if err != nil {
   511  			return nil, err
   512  		}
   513  		loadseqs = append(loadseqs, loadseq)
   514  	}
   515  	return loadseqs, nil
   516  }
   517  
   518  // loadEvent builds the corresponding load event and its tree
   519  func (lc *LoadChain) loadEvent(source sb.EFIImageLoadEventSource) (*sb.EFIImageLoadEvent, error) {
   520  	var next []*sb.EFIImageLoadEvent
   521  	for _, nextChain := range lc.Next {
   522  		// everything that is not the root has source shim
   523  		ev, err := nextChain.loadEvent(sb.Shim)
   524  		if err != nil {
   525  			return nil, err
   526  		}
   527  		next = append(next, ev)
   528  	}
   529  	image, err := efiImageFromBootFile(lc.BootFile)
   530  	if err != nil {
   531  		return nil, err
   532  	}
   533  	return &sb.EFIImageLoadEvent{
   534  		Source: source,
   535  		Image:  image,
   536  		Next:   next,
   537  	}, nil
   538  }
   539  
   540  func efiImageFromBootFile(b *bootloader.BootFile) (sb.EFIImage, error) {
   541  	if b.Snap == "" {
   542  		if !osutil.FileExists(b.Path) {
   543  			return nil, fmt.Errorf("file %s does not exist", b.Path)
   544  		}
   545  		return sb.FileEFIImage(b.Path), nil
   546  	}
   547  
   548  	snapf, err := snapfile.Open(b.Snap)
   549  	if err != nil {
   550  		return nil, err
   551  	}
   552  	return sb.SnapFileEFIImage{
   553  		Container: snapf,
   554  		FileName:  b.Path,
   555  	}, nil
   556  }