Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 62 additions & 1 deletion internal/gcs-sidecar/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package bridge

import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
Expand All @@ -15,6 +16,7 @@ import (
"github.com/Microsoft/go-winio/pkg/guid"
"github.com/Microsoft/hcsshim/hcn"
"github.com/Microsoft/hcsshim/internal/bridgeutils/commonutils"
"github.com/Microsoft/hcsshim/internal/copyfile"
"github.com/Microsoft/hcsshim/internal/fsformatter"
"github.com/Microsoft/hcsshim/internal/gcs/prot"
hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
Expand All @@ -29,13 +31,18 @@ import (
"github.com/Microsoft/hcsshim/pkg/cimfs"
"github.com/Microsoft/hcsshim/pkg/securitypolicy"
"github.com/pkg/errors"
"golang.org/x/sys/windows"
)

const (
sandboxStateDirName = "WcSandboxState"
hivesDirName = "Hives"
devPathFormat = "\\\\.\\PHYSICALDRIVE%d"
UVMContainerID = "00000000-0000-0000-0000-000000000000"
// amdSnpPspDLLName is the AMD SNP PSP API DLL used to fetch SNP attestation
// reports. It is staged from the UVM's System32 into each confidential
// container's security-context directory so workloads can load it.
amdSnpPspDLLName = "amdsnppspapi.dll"
)

// - Handler functions handle the incoming message requests. It
Expand Down Expand Up @@ -153,9 +160,20 @@ func (b *Bridge) createContainer(req *request) (err error) {
}()

if oci.ParseAnnotationsBool(ctx, spec.Annotations, annotations.WCOWSecurityPolicyEnv, true) {
if err := b.hostState.securityOptions.WriteSecurityContextDir(&spec); err != nil {
securityContextDir, err := b.hostState.securityOptions.WriteSecurityContextDir(&spec)
if err != nil {
return fmt.Errorf("failed to write security context dir: %w", err)
}

// Stage the AMD SNP PSP API DLL into the container's security-context
// directory so the workload can fetch SNP attestation reports. This
// happens after security policy enforcement, consistent with the
// UVM_SECURITY_CONTEXT_DIR env injection done by WriteSecurityContextDir.
if securityContextDir != "" {
if err := stageSnpPspDLL(ctx, securityContextDir); err != nil {
return fmt.Errorf("failed to stage %s: %w", amdSnpPspDLLName, err)
}
}
Comment on lines +171 to +176

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Untested, but I'm not sure if this will work, we might need to add the environment variables in executeProcess's processParams.Environment (but maybe @takuro-sato is making that processParams.Environment come from a stored OCI spec instead?)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding this env should happen in executeProcess at least for now as env vars are not passed to inbox inbox in createContainer.

After I finish my change it can be in between createContainer enforcement and b.hostState.AddContainer(req.ctx, containerID, c); (, where the enforcement result is stored) too.

Env var enforcement will be like:

  • createContaiener enforces and drops env vars and save the result in memory (resulting env vars are not passed to inbox gcs)
  • executeProcess gets a request including env vars and check they match the env vars in memory (currently this part is missing)

cwcowHostedSystemConfig.Spec = spec
}

Expand Down Expand Up @@ -201,6 +219,49 @@ func (b *Bridge) createContainer(req *request) (err error) {
return nil
}

// stageSnpPspDLL copies the AMD SNP PSP API DLL from the UVM's System32 into the
// container's security-context directory so the workload can fetch SNP
// attestation reports. The directory is exposed to the container via the
// UVM_SECURITY_CONTEXT_DIR environment variable. If the DLL is not present in
// the UVM (e.g. a non-SNP UVM), staging is skipped without error.
func stageSnpPspDLL(ctx context.Context, securityContextDir string) error {
sysDir, err := windows.GetSystemDirectory()
if err != nil {
return fmt.Errorf("failed to get system directory: %w", err)
}

srcPath := filepath.Join(sysDir, amdSnpPspDLLName)
staged, err := stageDLL(ctx, srcPath, securityContextDir)
if err != nil {
return err
}
if staged {
log.G(ctx).Debugf("staged %s into %s", amdSnpPspDLLName, securityContextDir)
} else {
log.G(ctx).Debugf("%s not found in %s; skipping staging", amdSnpPspDLLName, sysDir)
}
return nil
}

// stageDLL copies the DLL at srcPath into dstDir. If the source DLL does not
// exist it is a no-op and returns false without error, so callers can tolerate
// environments where the DLL is not present.
func stageDLL(ctx context.Context, srcPath, dstDir string) (bool, error) {
if _, err := os.Stat(srcPath); err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, fmt.Errorf("failed to stat %s: %w", srcPath, err)
}

dstPath := filepath.Join(dstDir, filepath.Base(srcPath))
if err := copyfile.CopyFile(ctx, srcPath, dstPath, true); err != nil {
return false, fmt.Errorf("failed to copy %s to %s: %w", srcPath, dstPath, err)
}

return true, 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 "<variable>=<value>") which
Expand Down
61 changes: 61 additions & 0 deletions internal/gcs-sidecar/stage_dll_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//go:build windows
// +build windows

package bridge

import (
"bytes"
"context"
"os"
"path/filepath"
"testing"
)

func TestStageDLL_Copies(t *testing.T) {
srcDir := t.TempDir()
dstDir := t.TempDir()

contents := []byte("fake-dll-bytes")
srcPath := filepath.Join(srcDir, amdSnpPspDLLName)
if err := os.WriteFile(srcPath, contents, 0644); err != nil {
t.Fatalf("failed to write source dll: %v", err)
}

staged, err := stageDLL(context.Background(), srcPath, dstDir)
if err != nil {
t.Fatalf("stageDLL returned error: %v", err)
}
if !staged {
t.Fatal("expected staged to be true")
}

// The DLL should be copied into dstDir with identical contents.
dstPath := filepath.Join(dstDir, amdSnpPspDLLName)
got, err := os.ReadFile(dstPath)
if err != nil {
t.Fatalf("failed to read staged dll: %v", err)
}
if !bytes.Equal(got, contents) {
t.Errorf("staged dll contents = %q, want %q", got, contents)
}
}

func TestStageDLL_MissingSourceIsNoOp(t *testing.T) {
dstDir := t.TempDir()
srcPath := filepath.Join(t.TempDir(), amdSnpPspDLLName) // does not exist

staged, err := stageDLL(context.Background(), srcPath, dstDir)
if err != nil {
t.Fatalf("stageDLL returned error: %v", err)
}
if staged {
t.Fatal("expected staged to be false when source is missing")
}

// No file should have been written to dstDir.
if entries, err := os.ReadDir(dstDir); err != nil {
t.Fatalf("failed to read dstDir: %v", err)
} else if len(entries) != 0 {
t.Errorf("expected dstDir to be empty, found %d entries", len(entries))
}
}
2 changes: 1 addition & 1 deletion internal/guest/runtime/hcsv2/uvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,7 @@ func (h *Host) CreateContainer(ctx context.Context, id string, settings *prot.VM
}

if oci.ParseAnnotationsBool(ctx, settings.OCISpecification.Annotations, annotations.LCOWSecurityPolicyEnv, true) {
if err := h.securityOptions.WriteSecurityContextDir(settings.OCISpecification); err != nil {
if _, err := h.securityOptions.WriteSecurityContextDir(settings.OCISpecification); err != nil {
return nil, fmt.Errorf("failed to write security context dir: %w", err)
}
}
Expand Down
21 changes: 13 additions & 8 deletions pkg/securitypolicy/securitypolicy_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,46 +191,51 @@ func writeFileInDir(dir string, filename string, data []byte, perm os.FileMode)
// containing the files is exposed via UVM_SECURITY_CONTEXT_DIR env var.
// It may be an error to have a security policy but not expose it to the
// container as in that case it can never be checked as correct by a verifier.
func (s *SecurityOptions) WriteSecurityContextDir(spec *specs.Spec) error {
//
// On success it returns the path (in the UVM/guest namespace) of the created
// security context directory, or an empty string if no directory was created
// because there was nothing to write.
func (s *SecurityOptions) WriteSecurityContextDir(spec *specs.Spec) (string, error) {
encodedPolicy := s.PolicyEnforcer.EncodedSecurityPolicy()
Comment thread
anmaxvl marked this conversation as resolved.
hostAMDCert := spec.Annotations[annotations.WCOWHostAMDCertificate]
if len(encodedPolicy) > 0 || len(hostAMDCert) > 0 || len(s.UvmReferenceInfo) > 0 || len(s.UvmHashEnvelopeReferenceInfo) > 0 {
// Use os.MkdirTemp to make sure that the directory is unique.
securityContextDir, err := os.MkdirTemp(spec.Root.Path, SecurityContextDirTemplate)
if err != nil {
return fmt.Errorf("failed to create security context directory: %w", err)
return "", fmt.Errorf("failed to create security context directory: %w", err)
}
// Make sure that files inside directory are readable
if err := os.Chmod(securityContextDir, 0755); err != nil {
return fmt.Errorf("failed to chmod security context directory: %w", err)
return "", fmt.Errorf("failed to chmod security context directory: %w", err)
}

if len(encodedPolicy) > 0 {
if err := writeFileInDir(securityContextDir, PolicyFilename, []byte(encodedPolicy), 0777); err != nil {
return fmt.Errorf("failed to write security policy: %w", err)
return "", fmt.Errorf("failed to write security policy: %w", err)
}
}
if len(s.UvmReferenceInfo) > 0 {
if err := writeFileInDir(securityContextDir, ReferenceInfoFilename, []byte(s.UvmReferenceInfo), 0777); err != nil {
return fmt.Errorf("failed to write UVM reference info: %w", err)
return "", fmt.Errorf("failed to write UVM reference info: %w", err)
}
}
if len(s.UvmHashEnvelopeReferenceInfo) > 0 {
if err := writeFileInDir(securityContextDir, HashEnvelopeReferenceInfoFilename, []byte(s.UvmHashEnvelopeReferenceInfo), 0777); err != nil {
return fmt.Errorf("failed to write UVM hash envelope reference info: %w", err)
return "", fmt.Errorf("failed to write UVM hash envelope reference info: %w", err)
}
}

if len(hostAMDCert) > 0 {
if err := writeFileInDir(securityContextDir, HostAMDCertFilename, []byte(hostAMDCert), 0777); err != nil {
return fmt.Errorf("failed to write host AMD certificate: %w", err)
return "", fmt.Errorf("failed to write host AMD certificate: %w", err)
}
}

containerCtxDir := fmt.Sprintf("/%s", filepath.Base(securityContextDir))
secCtxEnv := fmt.Sprintf("UVM_SECURITY_CONTEXT_DIR=%s", containerCtxDir)
spec.Process.Env = append(spec.Process.Env, secCtxEnv)

return securityContextDir, nil
}
return nil
return "", nil
}
Loading