-
Notifications
You must be signed in to change notification settings - Fork 298
CWCOW: Enforce registry entries on containers #2611
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| //go:build windows | ||
| // +build windows | ||
|
|
||
| package bridge | ||
|
|
||
| import ( | ||
| "math" | ||
| "reflect" | ||
| "slices" | ||
| "strconv" | ||
|
|
||
| hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" | ||
| ) | ||
|
|
||
| // DefaultRegistryValues contains the registry values that are always allowed | ||
| // without requiring policy validation. These are common system settings needed | ||
| // for proper UVM operation. | ||
| var defaultRegistryValues = []hcsschema.RegistryValue{ | ||
| { | ||
| Key: &hcsschema.RegistryKey{ | ||
| Hive: hcsschema.RegistryHive_SYSTEM, | ||
| Name: "ControlSet001\\Control", | ||
| }, | ||
| Name: "WaitToKillServiceTimeout", | ||
| StringValue: strconv.Itoa(math.MaxInt32), | ||
| Type_: hcsschema.RegistryValueType_STRING, | ||
| }, | ||
| } | ||
|
|
||
| // isDefaultRegistryValue checks if the given registry value matches one of the default allowed values | ||
| func isDefaultRegistryValue(value hcsschema.RegistryValue) bool { | ||
| return slices.ContainsFunc(defaultRegistryValues, func(rv hcsschema.RegistryValue) bool { | ||
| return registryValuesMatch(rv, value) | ||
| }) | ||
| } | ||
|
|
||
| // registryValuesMatch checks if two registry values are equivalent. | ||
| // Assumes registry values are well-formed (only relevant value fields are populated for each Type_). | ||
| func registryValuesMatch(a, b hcsschema.RegistryValue) bool { | ||
| return reflect.DeepEqual(a, b) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1231,6 +1231,116 @@ scratch_unmount := {"metadata": [remove_scratch_mount], "allowed": true} { | |
| } | ||
| } | ||
|
|
||
| # Registry changes validation | ||
| default registry_changes := {"allowed": false} | ||
|
|
||
| # Helper function to compare registry keys | ||
| registry_keys_match(policy_key, input_key) { | ||
| policy_key.hive == input_key.Hive | ||
| policy_key.name == input_key.Name | ||
| # Volatile field comparison (default to false if not specified) | ||
| policy_volatile := object.get(policy_key, "volatile", false) | ||
| input_volatile := object.get(input_key, "Volatile", false) | ||
| policy_volatile == input_volatile | ||
| } | ||
|
|
||
| # Helper function to compare registry values | ||
| # STRING type | ||
| registry_value_matches(policy_value, input_value) { | ||
| registry_keys_match(policy_value.key, input_value.Key) | ||
| policy_value.name == input_value.Name | ||
| policy_value.type == input_value.Type | ||
| policy_value.type == "String" | ||
| policy_value.string_value == input_value.StringValue | ||
| } | ||
|
|
||
| # EXPANDED_STRING type (uses StringValue field) | ||
| registry_value_matches(policy_value, input_value) { | ||
| registry_keys_match(policy_value.key, input_value.Key) | ||
| policy_value.name == input_value.Name | ||
| policy_value.type == input_value.Type | ||
| policy_value.type == "ExpandedString" | ||
| policy_value.string_value == input_value.StringValue | ||
| } | ||
|
|
||
| # MULTI_STRING type (uses StringValue field) | ||
| registry_value_matches(policy_value, input_value) { | ||
| registry_keys_match(policy_value.key, input_value.Key) | ||
| policy_value.name == input_value.Name | ||
| policy_value.type == input_value.Type | ||
| policy_value.type == "MultiString" | ||
| policy_value.string_value == input_value.StringValue | ||
| } | ||
|
|
||
| # D_WORD type | ||
| registry_value_matches(policy_value, input_value) { | ||
| registry_keys_match(policy_value.key, input_value.Key) | ||
| policy_value.name == input_value.Name | ||
| policy_value.type == input_value.Type | ||
| policy_value.type == "DWord" | ||
| policy_value.dword_value == input_value.DWordValue | ||
| } | ||
|
|
||
| # Q_WORD type | ||
| registry_value_matches(policy_value, input_value) { | ||
| registry_keys_match(policy_value.key, input_value.Key) | ||
| policy_value.name == input_value.Name | ||
| policy_value.type == input_value.Type | ||
| policy_value.type == "QWord" | ||
| policy_value.qword_value == input_value.QWordValue | ||
| } | ||
|
|
||
| # BINARY type | ||
| registry_value_matches(policy_value, input_value) { | ||
| registry_keys_match(policy_value.key, input_value.Key) | ||
| policy_value.name == input_value.Name | ||
| policy_value.type == input_value.Type | ||
| policy_value.type == "Binary" | ||
| policy_value.binary_value == input_value.BinaryValue | ||
| } | ||
|
|
||
| # CUSTOM_TYPE - both CustomType field and BinaryValue must match | ||
| registry_value_matches(policy_value, input_value) { | ||
| registry_keys_match(policy_value.key, input_value.Key) | ||
| policy_value.name == input_value.Name | ||
| policy_value.type == input_value.Type | ||
| policy_value.type == "CustomType" | ||
| policy_value.custom_type == input_value.CustomType | ||
| policy_value.binary_value == input_value.BinaryValue | ||
| } | ||
|
|
||
| # NONE type - no value to compare, just key and name | ||
| registry_value_matches(policy_value, input_value) { | ||
| registry_keys_match(policy_value.key, input_value.Key) | ||
| policy_value.name == input_value.Name | ||
| policy_value.type == input_value.Type | ||
| policy_value.type == "None" | ||
| } | ||
|
|
||
| # Filter input registry values to only include those that match policy | ||
| filtered_registry_values(input_values, policy_values) := [input_val | | ||
| input_val := input_values[_] | ||
| some policy_val in policy_values | ||
| registry_value_matches(policy_val, input_val) | ||
| ] | ||
|
|
||
| registry_changes := {"allowed": true} { | ||
| containers := data.metadata.matches[input.containerID] | ||
| container := containers[_] | ||
|
|
||
| # Check if container has registry_changes defined in policy | ||
| container.registry_changes | ||
|
|
||
| # If input has registry changes, filter to only matching ones | ||
| input.registryChanges.AddValues | ||
| matched_values := filtered_registry_values(input.registryChanges.AddValues, container.registry_changes.add_values) | ||
|
|
||
| # Build result with filtered AddValues | ||
| result := { | ||
| "AddValues": matched_values | ||
| } | ||
| } | ||
|
Comment on lines
+1327
to
+1342
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Apologies for resurfacing this merged PR, but I have some worries about this bit. First, I don't think Also, since we bail out if My second concern is whether this check should in fact be part of the I'm not sure how important the second concern is, because registry changes are not part of the OCI spec and so the customer does not have any way to use this feature anyway, but I do feel like the cleanest solution should be checking it as part of
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, this new field should go into (also without adding this to
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should also create a denial reason for this (and also mount_cims), e.g. errors["registry changes denied"] {
input.rule == "registry_changes"
data.metadata.matches[input.containerID]
}
errors["no matching containers"] {
input.rule == "registry_changes"
not data.metadata.matches[input.containerID]
}(this will be different (simpler) if we move this into For mount_cims maybe errors["No matching containers for CIM layer hashes"] {
not overlay_exists
containers := [container |
container := candidate_containers[_]
layerHashes_ok(container.layers)
]
count(containers) == 0
}
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
registry changes are not used for container narrowing intentionally. We must allow certain registry entries from the host (like servercore at this point), so it is not used as part of container narrowing. I'll chat offline instead of a wall of text here :) |
||
|
|
||
| reason := { | ||
| "errors": errors, | ||
| "error_objects": error_objects | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.