@@ -2,21 +2,27 @@ package tdx
22
33import (
44 "context"
5+ "errors"
56 "fmt"
67 "net"
8+ "os"
9+ "os/exec"
10+ "path/filepath"
711 "strings"
812 "sync"
913 "time"
1014
1115 "github.com/mdlayher/vsock"
1216
17+ "github.com/oasisprotocol/oasis-core/go/common"
1318 "github.com/oasisprotocol/oasis-core/go/common/identity"
1419 "github.com/oasisprotocol/oasis-core/go/common/logging"
1520 "github.com/oasisprotocol/oasis-core/go/common/node"
1621 "github.com/oasisprotocol/oasis-core/go/common/persistent"
1722 "github.com/oasisprotocol/oasis-core/go/common/sgx/pcs"
1823 sgxQuote "github.com/oasisprotocol/oasis-core/go/common/sgx/quote"
1924 consensus "github.com/oasisprotocol/oasis-core/go/consensus/api"
25+ "github.com/oasisprotocol/oasis-core/go/runtime/bundle"
2026 "github.com/oasisprotocol/oasis-core/go/runtime/bundle/component"
2127 "github.com/oasisprotocol/oasis-core/go/runtime/host"
2228 "github.com/oasisprotocol/oasis-core/go/runtime/host/protocol"
@@ -28,10 +34,15 @@ import (
2834const (
2935 // defaultQemuSystemPath is the default QEMU system binary path.
3036 defaultQemuSystemPath = "/usr/bin/qemu-system-x86_64"
37+ // defaultQemuImgPath is the default qemu-bin binary path.
38+ defaultQemuImgPath = "/usr/bin/qemu-img"
3139 // defaultStartCid is the default start CID.
3240 defaultStartCid = 0xA5150000
3341 // defaultRuntimeAttestInterval is the default runtime (re-)attestation interval.
3442 defaultRuntimeAttestInterval = 2 * time .Hour
43+ // persistentImageDir is the name of the directory within the runtime data directory
44+ // where persistent overlay images can be stored.
45+ persistentImageDir = "images"
3546
3647 // vsockPortRHP is the VSOCK port used for the Runtime-Host Protocol.
3748 vsockPortRHP = 1
@@ -41,6 +52,8 @@ const (
4152
4253// QemuConfig is the configuration of the QEMU-based TDX runtime provisioner.
4354type QemuConfig struct {
55+ // DataDir is the runtime data directory.
56+ DataDir string
4457 // HostInfo provides information about the host environment.
4558 HostInfo * protocol.HostInfo
4659
@@ -166,9 +179,20 @@ func (q *qemuProvisioner) getSandboxConfig(rtCfg host.Config, _ sandbox.Connecto
166179 return process.Config {}, fmt .Errorf ("format '%s' is not supported" , stage2Format )
167180 }
168181
182+ // Set up a persistent overlay image when configured to do so.
183+ snapshotMode := "on" // Default to ephemeral images.
184+ if tdxCfg .Stage2Persist {
185+ stage2Image , err = q .createPersistentOverlayImage (rtCfg , comp , stage2Image , stage2Format )
186+ if err != nil {
187+ return process.Config {}, err
188+ }
189+ stage2Format = "qcow2"
190+ snapshotMode = "off"
191+ }
192+
169193 cfg .Args = append (cfg .Args ,
170194 // Stage 2 drive.
171- "-drive" , fmt .Sprintf ("format=%s,file=%s,if=none,id=drive0,snapshot=on " , stage2Format , stage2Image ),
195+ "-drive" , fmt .Sprintf ("format=%s,file=%s,if=none,id=drive0,snapshot=%s " , stage2Format , stage2Image , snapshotMode ),
172196 "-device" , "virtio-blk-pci,drive=drive0" ,
173197 )
174198 }
@@ -211,6 +235,65 @@ func (q *qemuProvisioner) getSandboxConfig(rtCfg host.Config, _ sandbox.Connecto
211235 return cfg , nil
212236}
213237
238+ // createPersistentOverlayImage creates a persistent overlay image for the given backing image and
239+ // returns the full path to the overlay image. In case the image already exists, it is reused.
240+ //
241+ // The format of the resulting image is always qcow2.
242+ func (q * qemuProvisioner ) createPersistentOverlayImage (
243+ rtCfg host.Config ,
244+ comp * bundle.ExplodedComponent ,
245+ image string ,
246+ format string ,
247+ ) (string , error ) {
248+ compID , _ := comp .ID ().MarshalText ()
249+ imageDir := filepath .Join (q .cfg .DataDir , persistentImageDir , rtCfg .ID .String (), string (compID ))
250+ imageFn := filepath .Join (imageDir , fmt .Sprintf ("%s.overlay" , filepath .Base (image )))
251+ switch _ , err := os .Stat (imageFn ); {
252+ case err == nil :
253+ // Image already exists, perform a rebase operation to account for the backing file location
254+ // changing (e.g. due to an upgrade).
255+ cmd := exec .Command (
256+ defaultQemuImgPath ,
257+ "rebase" ,
258+ "-u" ,
259+ "-f" , "qcow2" ,
260+ "-b" , image ,
261+ "-F" , format ,
262+ imageFn ,
263+ )
264+ var out strings.Builder
265+ cmd .Stderr = & out
266+ cmd .Stdout = & out
267+ if err := cmd .Run (); err != nil {
268+ return "" , fmt .Errorf ("failed to rebase persistent overlay image: %s\n %w" , out .String (), err )
269+ }
270+ case errors .Is (err , os .ErrNotExist ):
271+ // Create image directory if it doesn't yet exist.
272+ if err := common .Mkdir (imageDir ); err != nil {
273+ return "" , fmt .Errorf ("failed to create persistent overlay image directory: %w" , err )
274+ }
275+
276+ // Create the persistent overlay image.
277+ cmd := exec .Command (
278+ defaultQemuImgPath ,
279+ "create" ,
280+ "-f" , "qcow2" ,
281+ "-b" , image ,
282+ "-F" , format ,
283+ imageFn ,
284+ )
285+ var out strings.Builder
286+ cmd .Stderr = & out
287+ cmd .Stdout = & out
288+ if err := cmd .Run (); err != nil {
289+ return "" , fmt .Errorf ("failed to create persistent overlay image: %s\n %w" , out .String (), err )
290+ }
291+ default :
292+ return "" , fmt .Errorf ("failed to stat persistent overlay image: %w" , err )
293+ }
294+ return imageFn , nil
295+ }
296+
214297func (q * qemuProvisioner ) updateCapabilityTEE (ctx context.Context , hp * sandbox.HostInitializerParams ) (cap * node.CapabilityTEE , aerr error ) {
215298 defer func () {
216299 sgxCommon .UpdateAttestationMetrics (hp .Runtime .ID (), component .TEEKindTDX , aerr )
0 commit comments