diff --git a/internal/gcs-sidecar/handlers.go b/internal/gcs-sidecar/handlers.go index 1cf1ebff39..c6430224f8 100644 --- a/internal/gcs-sidecar/handlers.go +++ b/internal/gcs-sidecar/handlers.go @@ -19,6 +19,7 @@ import ( "github.com/Microsoft/hcsshim/internal/copyfile" "github.com/Microsoft/hcsshim/internal/fsformatter" "github.com/Microsoft/hcsshim/internal/gcs/prot" + "github.com/Microsoft/hcsshim/internal/guestpath" hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" "github.com/Microsoft/hcsshim/internal/log" oci "github.com/Microsoft/hcsshim/internal/oci" @@ -137,6 +138,15 @@ func (b *Bridge) createContainer(req *request) (err error) { return fmt.Errorf("CreateContainer operation is denied by policy: %w", err) } + // Create the source directory for each mapped directory if it does not + // already exist. In non-confidential WCOW the host does this for + // sandbox:// mounts by exec'ing `cmd /c mkdir ... & dir ...` inside the + // UVM (see resources_wcow.go:setupMounts), but for confidential, we + // handle this here in the sidecar GCS. + if err := createSandboxMountSourceDirs(ctx, container.MappedDirectories); err != nil { + return fmt.Errorf("failed to create mapped directory source directories: %w", err) + } + commandLine := len(spec.Process.Args) > 0 c := &Container{ id: containerID, @@ -262,6 +272,32 @@ func stageDLL(ctx context.Context, srcPath, dstDir string) (bool, error) { return true, nil } +// createSandboxMountSourceDirs creates source directories for sandbox +// mounts if they do not already exist. +func createSandboxMountSourceDirs(ctx context.Context, mappedDirectories []hcsschema.MappedDirectory) error { + for _, md := range mappedDirectories { + source := md.HostPath + if strings.EqualFold(source, guestpath.WCOWSandboxMountPath) || + strings.HasPrefix(strings.ToLower(source), strings.ToLower(guestpath.WCOWSandboxMountPath+`\`)) { + + // do this stat rather than call MkdirAll unconditionally, + // since the latter will fail with a source file (not dir) + if _, err := os.Stat(source); err == nil { + log.G(ctx).WithField("source", source).Debug("source of mapped directory mount exists, not creating directories") + continue + } else if !os.IsNotExist(err) { + return fmt.Errorf("failed to stat mapped directory source %q: %w", source, err) + } + + if err := os.MkdirAll(source, 0755); err != nil { + return fmt.Errorf("failed to create mapped directory source %q: %w", source, err) + } + log.G(ctx).WithField("source", source).Debug("created mapped directory source directory") + } + } + return nil +} + // processParamEnvToOCIEnv converts an Environment field from ProcessParameters // (a map from environment variable to value) into an array of environment // variable assignments (where each is in the form "=") which diff --git a/internal/gcs-sidecar/handlers_test.go b/internal/gcs-sidecar/handlers_test.go index 6de3a0a605..e8b07bfd4c 100644 --- a/internal/gcs-sidecar/handlers_test.go +++ b/internal/gcs-sidecar/handlers_test.go @@ -7,16 +7,60 @@ import ( "context" "encoding/json" "io" + "os" + "path/filepath" + "strings" "testing" "time" "github.com/Microsoft/go-winio/pkg/guid" "github.com/Microsoft/hcsshim/internal/gcs/prot" + "github.com/Microsoft/hcsshim/internal/guestpath" + hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" "github.com/Microsoft/hcsshim/internal/protocol/guestresource" "github.com/Microsoft/hcsshim/pkg/securitypolicy" ) +func TestCreateSandboxMountSourceDirs(t *testing.T) { + testRoot := filepath.Join(guestpath.WCOWSandboxMountPath, filepath.Base(t.TempDir())) + if err := os.MkdirAll(testRoot, 0755); err != nil { + t.Fatalf("failed to create test root: %v", err) + } + t.Cleanup(func() { _ = os.RemoveAll(testRoot) }) + + existingFile := filepath.Join(testRoot, "existing-file") + if err := os.WriteFile(existingFile, nil, 0644); err != nil { + t.Fatalf("failed to create existing source file: %v", err) + } + + createdDir := filepath.Join(strings.ToLower(testRoot), "created", "nested") + outsideDir := filepath.Join(t.TempDir(), "outside") + mappedDirectories := []hcsschema.MappedDirectory{ + {HostPath: createdDir}, + {HostPath: existingFile}, + {HostPath: outsideDir}, + } + + if err := createSandboxMountSourceDirs(context.Background(), mappedDirectories); err != nil { + t.Fatalf("createSandboxMountSourceDirs returned error: %v", err) + } + + if info, err := os.Stat(createdDir); err != nil { + t.Fatalf("failed to stat created sandbox directory: %v", err) + } else if !info.IsDir() { + t.Fatalf("sandbox source %q is not a directory", createdDir) + } + if info, err := os.Stat(existingFile); err != nil { + t.Fatalf("failed to stat existing sandbox source file: %v", err) + } else if info.IsDir() { + t.Fatalf("existing sandbox source file %q was replaced by a directory", existingFile) + } + if _, err := os.Stat(outsideDir); !os.IsNotExist(err) { + t.Fatalf("outside source %q was created or returned unexpected error: %v", outsideDir, err) + } +} + // buildModifySettingsRequest creates a serialized ModifySettings request message // for the given resource type and settings. func buildModifySettingsRequest(t *testing.T, resourceType guestrequest.ResourceType, requestType guestrequest.RequestType, settings interface{}) []byte { diff --git a/internal/guestpath/paths.go b/internal/guestpath/paths.go index d616efc646..b5baccd5dd 100644 --- a/internal/guestpath/paths.go +++ b/internal/guestpath/paths.go @@ -11,6 +11,9 @@ const ( // WCOWRootPrefixInUVM is the path inside UVM where WCOW container's root // file system will be mounted WCOWRootPrefixInUVM = `C:\c` + // WCOWSandboxMountPath is the path inside the UVM where WCOW sandbox mounts + // are created. + WCOWSandboxMountPath = `C:\SandboxMounts` // SandboxMountPrefix is mount prefix used in container spec to mark a // sandbox-mount SandboxMountPrefix = "sandbox://" diff --git a/internal/hcsoci/resources_wcow.go b/internal/hcsoci/resources_wcow.go index ebf353546d..6cf56f2c4c 100644 --- a/internal/hcsoci/resources_wcow.go +++ b/internal/hcsoci/resources_wcow.go @@ -27,8 +27,6 @@ import ( "github.com/Microsoft/hcsshim/internal/uvm/scsi" ) -const wcowSandboxMountPath = "C:\\SandboxMounts" - func allocateWindowsResources(ctx context.Context, coi *createOptionsInternal, r *resources.Resources, isSandbox bool) error { if coi.Spec.Root == nil { coi.Spec.Root = &specs.Root{} @@ -203,23 +201,30 @@ func setupMounts(ctx context.Context, coi *createOptionsInternal, r *resources.R // so first convert to a path in the sandboxmounts path itself. sandboxPath := convertToWCOWSandboxMountPath(mount.Source) - // Now we need to exec a process in the vm that will make these directories as theres + // Now we need to exec a process in the vm that will make these directories as there's // no functionality in the Windows gcs to create an arbitrary directory. // - // Create the directory, but also run dir afterwards regardless of if mkdir succeeded to handle the case where the directory already exists - // e.g. from a previous container specifying the same mount (and thus creating the same directory). - b := &bytes.Buffer{} - stderr, err := cmd.CreatePipeAndListen(b, false) - if err != nil { - return err - } - req := &cmd.CmdProcessRequest{ - Args: []string{"cmd", "/c", "mkdir", sandboxPath, "&", "dir", sandboxPath}, - Stderr: stderr, - } - exitCode, err := coi.HostingSystem.ExecInUVM(ctx, req) - if err != nil { - return errors.Wrapf(err, "failed to create sandbox mount directory in utility VM with exit code %d %q", exitCode, b.String()) + // We do not need to do this for Confidential WCOW, because in that case the gcs-sidecar + // handles the create container request and will do this for us. This way the policy + // does not have to have exceptions for allowing such mkdir commands. + // + // Create the directory, but also run dir afterwards regardless of if mkdir succeeded to + // handle the case where the directory already exists e.g. from a previous container + // specifying the same mount (and thus creating the same directory). + if !coi.HostingSystem.HasConfidentialPolicy() { + b := &bytes.Buffer{} + stderr, err := cmd.CreatePipeAndListen(b, false) + if err != nil { + return err + } + req := &cmd.CmdProcessRequest{ + Args: []string{"cmd", "/c", "mkdir", sandboxPath, "&", "dir", sandboxPath}, + Stderr: stderr, + } + exitCode, err := coi.HostingSystem.ExecInUVM(ctx, req) + if err != nil { + return errors.Wrapf(err, "failed to create sandbox mount directory in utility VM with exit code %d %q", exitCode, b.String()) + } } } else if np, ok := uvm.ParseNamedPipe(coi.HostingSystem, mount); ok { if !np.UVMPipe { @@ -248,5 +253,5 @@ func setupMounts(ctx context.Context, coi *createOptionsInternal, r *resources.R func convertToWCOWSandboxMountPath(source string) string { subPath := strings.TrimPrefix(source, guestpath.SandboxMountPrefix) - return filepath.Join(wcowSandboxMountPath, subPath) + return filepath.Join(guestpath.WCOWSandboxMountPath, subPath) }