Skip to content

Commit ae93ae0

Browse files
committed
C-WCOW: Enforce hashes on already mounted CIMs
Signed-off-by: Mahati Chamarthy <mahati.chamarthy@gmail.com>
1 parent d5a05aa commit ae93ae0

File tree

5 files changed

+83
-12
lines changed

5 files changed

+83
-12
lines changed

internal/gcs-sidecar/handlers.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
"strings"
1313
"time"
1414

15+
"regexp"
16+
1517
"github.com/Microsoft/hcsshim/hcn"
1618
"github.com/Microsoft/hcsshim/internal/bridgeutils/commonutils"
1719
"github.com/Microsoft/hcsshim/internal/fsformatter"
@@ -37,6 +39,8 @@ const (
3739
UVMContainerID = "00000000-0000-0000-0000-000000000000"
3840
)
3941

42+
var volumeGUIDRe = regexp.MustCompile(`^\\\\\?\\Volume\{([0-9A-Fa-f\-]+)\}\\Files$`)
43+
4044
// - Handler functions handle the incoming message requests. It
4145
// also enforces security policy for confidential cwcow containers.
4246
// - These handler functions may do some additional processing before
@@ -544,6 +548,14 @@ func (b *Bridge) lifecycleNotification(req *request) (err error) {
544548
return nil
545549
}
546550

551+
func volumeGUIDFromLayerPath(p string) (string, bool) {
552+
m := volumeGUIDRe.FindStringSubmatch(p)
553+
if len(m) != 2 {
554+
return "", false
555+
}
556+
return m[1], true
557+
}
558+
547559
func (b *Bridge) modifySettings(req *request) (err error) {
548560
ctx, span := oc.StartSpan(req.ctx, "sidecar::modifySettings")
549561
defer span.End()
@@ -676,6 +688,20 @@ func (b *Bridge) modifySettings(req *request) (err error) {
676688
return errors.Wrap(err, "CIM mount is denied by policy")
677689
}
678690

691+
// Volume GUID from request
692+
volGUID := wcowBlockCimMounts.VolumeGUID.String()
693+
694+
// Cache hashes along with volGUID
695+
b.hostState.blockCIMVolumeHashes[volGUID] = hashesToVerify
696+
697+
// Store the containerID (associated with volGUID) to mark that hashes are verified for this container
698+
if _, ok := b.hostState.blockCIMVolumeContainers[volGUID]; !ok {
699+
b.hostState.blockCIMVolumeContainers[volGUID] = make(map[string]struct{})
700+
}
701+
b.hostState.blockCIMVolumeContainers[volGUID][containerID] = struct{}{}
702+
703+
log.G(ctx).Tracef("Cached %d verified CIM layer hashes for volume %s (container %s)", len(hashesToVerify), volGUID, containerID)
704+
679705
if len(layerCIMs) > 1 {
680706
_, err = cimfs.MountMergedVerifiedBlockCIMs(layerCIMs[0], layerCIMs[1:], wcowBlockCimMounts.MountFlags, wcowBlockCimMounts.VolumeGUID, layerDigests[0])
681707
if err != nil {
@@ -710,6 +736,28 @@ func (b *Bridge) modifySettings(req *request) (err error) {
710736
log.G(ctx).Tracef("CWCOWCombinedLayers:: ContainerID: %v, ContainerRootPath: %v, Layers: %v, ScratchPath: %v",
711737
containerID, settings.CombinedLayers.ContainerRootPath, settings.CombinedLayers.Layers, settings.CombinedLayers.ScratchPath)
712738

739+
// The layers size is only one, this is just defensive checking
740+
if len(settings.CombinedLayers.Layers) == 1 {
741+
layerPath := settings.CombinedLayers.Layers[0].Path
742+
if guidStr, ok := volumeGUIDFromLayerPath(layerPath); ok {
743+
hashes, haveHashes := b.hostState.blockCIMVolumeHashes[guidStr]
744+
if haveHashes {
745+
// Only do this if it wasn't already enforced by the ResourceTypeWCOWBlockCims request
746+
containers := b.hostState.blockCIMVolumeContainers[guidStr]
747+
if _, seen := containers[containerID]; !seen {
748+
// This is a container with CIMs already mounted (container with similar layers as an existing container). Call EnforceVerifiedCIMsPolicy on this new container
749+
log.G(ctx).Tracef("Verified CIM hashes for reused mount volume %s (container %s)", guidStr, containerID)
750+
if err := b.hostState.securityPolicyEnforcer.EnforceVerifiedCIMsPolicy(ctx, containerID, hashes); err != nil {
751+
return fmt.Errorf("CIM mount is denied by policy for this container: %w", err)
752+
}
753+
containers[containerID] = struct{}{}
754+
}
755+
} else {
756+
log.G(ctx).Debugf("No cached CIM hashes found for volume %s", guidStr)
757+
}
758+
}
759+
}
760+
713761
//Since unencrypted scratch is not an option, always pass true
714762
if err := b.hostState.securityPolicyEnforcer.EnforceScratchMountPolicy(ctx, settings.CombinedLayers.ContainerRootPath, true); err != nil {
715763
return fmt.Errorf("scratch mounting denied by policy: %w", err)

internal/gcs-sidecar/host.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ type Host struct {
3232
containersMutex sync.Mutex
3333
containers map[string]*Container
3434

35+
// mapping of volumeGUID to container layer hashes
36+
blockCIMVolumeHashes map[string][]string
37+
// mapping of volumeGUID to container IDs
38+
blockCIMVolumeContainers map[string]map[string]struct{}
39+
3540
// state required for the security policy enforcement
3641
policyMutex sync.Mutex
3742
securityPolicyEnforcer securitypolicy.SecurityPolicyEnforcer
@@ -58,6 +63,8 @@ type containerProcess struct {
5863
func NewHost(initialEnforcer securitypolicy.SecurityPolicyEnforcer) *Host {
5964
return &Host{
6065
containers: make(map[string]*Container),
66+
blockCIMVolumeHashes: make(map[string][]string),
67+
blockCIMVolumeContainers: make(map[string]map[string]struct{}),
6168
securityPolicyEnforcer: initialEnforcer,
6269
securityPolicyEnforcerSet: false,
6370
}

internal/regopolicyinterpreter/regopolicyinterpreter.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,26 @@ func (r *RegoPolicyInterpreter) RawQuery(rule string, input map[string]interface
585585
return resultSet, nil
586586
}
587587

588+
// MetadataJSON returns the entire metadata object as a JSON string.
589+
// The returned JSON is a snapshot (deep-copied via marshal/unmarshal).
590+
func (r *RegoPolicyInterpreter) MetadataJSON() (string, error) {
591+
r.dataAndModulesMutex.Lock()
592+
defer r.dataAndModulesMutex.Unlock()
593+
594+
root, ok := r.data["metadata"].(regoMetadata)
595+
if !ok {
596+
return "", errors.New("incorrect interpreter state: invalid metadata object type")
597+
}
598+
599+
// Deep copy to avoid callers modifying internal maps.
600+
b, err := json.Marshal(root)
601+
if err != nil {
602+
return "", fmt.Errorf("unable to marshal metadata: %w", err)
603+
}
604+
605+
return string(b), nil
606+
}
607+
588608
// Query queries the policy with the given rule and input data and returns the result.
589609
func (r *RegoPolicyInterpreter) Query(rule string, input map[string]interface{}) (RegoQueryResult, error) {
590610
// this mutex ensures no other threads modify the data and compiledModules fields during query execution

pkg/securitypolicy/framework.rego

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1360,6 +1360,7 @@ env_rule_matches(rule) {
13601360
}
13611361

13621362
errors["missing required environment variable"] {
1363+
is_linux
13631364
input.rule == "create_container"
13641365

13651366
not container_started

pkg/securitypolicy/securitypolicyenforcer_rego.go

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,13 @@ func (policy *regoEnforcer) EnforceCreateContainerPolicyV2(
729729
"seccompProfileSHA256": opts.SeccompProfileSHA256,
730730
}
731731
case "windows":
732+
// Dump full interpreter metadata for debugging diagnostics.
733+
if mdJSON, err := policy.rego.MetadataJSON(); err == nil {
734+
log.G(ctx).Debugf("Current policy metadata: %s", mdJSON)
735+
} else {
736+
log.G(ctx).WithError(err).Warn("failed to obtain policy metadata snapshot")
737+
}
738+
732739
input = inputData{
733740
"containerID": containerID,
734741
"argList": argList,
@@ -792,18 +799,6 @@ func appendMountData(mountData []interface{}, mounts []oci.Mount) []interface{}
792799
return mountData
793800
}
794801

795-
func appendMountDataWindows(mountData []interface{}, mounts []oci.Mount) []interface{} {
796-
for _, mount := range mounts {
797-
mountData = append(mountData, inputData{
798-
"destination": mount.Destination,
799-
"source": mount.Source,
800-
"options": mount.Options,
801-
})
802-
}
803-
804-
return mountData
805-
}
806-
807802
func (policy *regoEnforcer) ExtendDefaultMounts(mounts []oci.Mount) error {
808803
policy.defaultMounts = append(policy.defaultMounts, mounts...)
809804
defaultMounts := appendMountData([]interface{}{}, policy.defaultMounts)

0 commit comments

Comments
 (0)