1 /* <lambda>null2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.libraries.pcc.chronicle.analysis.impl 18 19 import com.android.libraries.pcc.chronicle.analysis.ChronicleContext 20 import com.android.libraries.pcc.chronicle.analysis.ManagementStrategyComparator.compare 21 import com.android.libraries.pcc.chronicle.analysis.PolicyEngine 22 import com.android.libraries.pcc.chronicle.api.Connection 23 import com.android.libraries.pcc.chronicle.api.ConnectionRequest 24 import com.android.libraries.pcc.chronicle.api.DataType 25 import com.android.libraries.pcc.chronicle.api.DataTypeDescriptor 26 import com.android.libraries.pcc.chronicle.api.FieldType 27 import com.android.libraries.pcc.chronicle.api.SandboxProcessorNode 28 import com.android.libraries.pcc.chronicle.api.isWriteConnection 29 import com.android.libraries.pcc.chronicle.api.policy.Policy 30 import com.android.libraries.pcc.chronicle.api.policy.PolicyField 31 import com.android.libraries.pcc.chronicle.api.policy.UsageType 32 import com.android.libraries.pcc.chronicle.api.policy.builder.PolicyCheck 33 import com.android.libraries.pcc.chronicle.api.policy.builder.PolicyCheckResult 34 import com.android.libraries.pcc.chronicle.api.policy.canEgress 35 36 /** 37 * Uses the policy engine from Arcs to check policy adherence. 38 * 39 * When [checkPolicy] is called, this implementation will perform the following: 40 * 41 * 1. Verify all the fields requested by the connection is marked as allowed for egress. 42 * * Return [PolicyCheckResult.Pass] if the [ManagementStrategies] for the [ConnectionProviders] 43 * being used by the `connectionRequester` are valid for the [Policy], and all of the checks 44 * performed by data flow analysis pass. 45 * * Return [PolicyCheckResult.Fail] otherwise. 46 */ 47 class ChroniclePolicyEngine : PolicyEngine { 48 override fun checkPolicy( 49 policy: Policy, 50 request: ConnectionRequest<*>, 51 context: ChronicleContext, 52 ): PolicyCheckResult { 53 // Verify the ManagementStrategies involved against the Policy, and verify the checks using 54 // the results of the data-flow analysis. 55 val violations = 56 policy.verifyManagementStrategies(context.connectionProviders) + 57 checkAllFieldsAreAllowedForEgress(context, policy, request) + 58 policy.verifyContext(context.connectionContext) 59 60 return PolicyCheckResult.make(violations) 61 } 62 63 override fun checkWriteConnections(context: ChronicleContext): PolicyCheckResult { 64 val violations = 65 context.connectionProviders.flatMap { checkWriteConnectionManagement(it.dataType, context) } 66 67 return PolicyCheckResult.make(violations) 68 } 69 70 private fun checkWriteConnectionManagement( 71 dataType: DataType, 72 context: ChronicleContext 73 ): List<PolicyCheck> { 74 // If the managed data type doesn't declare any write connections, there's nothing to do here. 75 if (dataType.connectionTypes.none { it.isWriteConnection }) return emptyList() 76 77 // Find the canonical strategy from the policy set for the data type. If we had no strategy 78 // laid out for the data type by any known policy; this is a problem. 79 val managementStrategies = context.policySet.findManagementStrategies(dataType.descriptor) 80 if (managementStrategies.isEmpty()) { 81 return listOf(PolicyCheck("h:${dataType.descriptor.name} is $MUST_HAVE_POLICY_PREDICATE")) 82 } 83 84 // If the provider's management strategy is less restrictive than all of management strategies, 85 // associated with a data type, that's a problem. 86 if (managementStrategies.all { compare(dataType.managementStrategy, it) > 0 }) { 87 return listOf( 88 PolicyCheck( 89 "h:${dataType.descriptor.name} is $MANAGEMENT_STRATEGY_NOT_STRICT_ENOUGH_PREDICATE" 90 ) 91 ) 92 } 93 return emptyList() 94 } 95 96 private fun <T : Connection> checkAllFieldsAreAllowedForEgress( 97 context: ChronicleContext, 98 policy: Policy, 99 request: ConnectionRequest<T>, 100 ): List<PolicyCheck> { 101 val dtd = 102 context.findDataType(request.connectionType) 103 ?: return listOf( 104 PolicyCheck("s:${request.connectionType} is $CONNECTON_NOT_FOUND_PREDICATE") 105 ) 106 107 val policyTarget = 108 policy.targets.find { it.schemaName == dtd.name } 109 ?: return listOf(PolicyCheck("s:${dtd.name} is $DTD_NOT_FOUND_PREDICATE")) 110 111 val violatingFields = mutableListOf<String>() 112 dtd.fields.forEach { (fieldName, type) -> 113 policyTarget.fields 114 .find { it.fieldPath.last() == fieldName } 115 ?.let { policyField -> 116 violatingFields.addAll( 117 type.findPolicyViolations( 118 "${dtd.name}.$fieldName", 119 policyField, 120 if (request.requester is SandboxProcessorNode) { 121 { !(it.rawUsages.canEgress() || it.rawUsages.contains(UsageType.SANDBOX)) } 122 } else { 123 { !it.rawUsages.canEgress() } 124 }, 125 context 126 ) 127 ) 128 } 129 ?: violatingFields.add("${dtd.name}.$fieldName") 130 } 131 132 return violatingFields.map { PolicyCheck("s:$it is $FIELD_CANNOT_BE_EGRESSED_PREDICATE") } 133 } 134 135 private fun FieldType.findPolicyViolations( 136 prefix: String, 137 policyField: PolicyField, 138 hasPolicyViolation: (PolicyField) -> Boolean, 139 context: ChronicleContext 140 ): List<String> { 141 return when (this) { 142 FieldType.Boolean, 143 FieldType.Byte, 144 FieldType.ByteArray, 145 FieldType.Char, 146 FieldType.Double, 147 FieldType.Duration, 148 FieldType.Float, 149 FieldType.Instant, 150 FieldType.Integer, 151 FieldType.Long, 152 FieldType.Short, 153 FieldType.String, 154 is FieldType.Enum, 155 is FieldType.Opaque -> if (hasPolicyViolation(policyField)) listOf(prefix) else emptyList() 156 is FieldType.Array -> 157 this.itemFieldType.findPolicyViolations(prefix, policyField, hasPolicyViolation, context) 158 is FieldType.List -> 159 this.itemFieldType.findPolicyViolations(prefix, policyField, hasPolicyViolation, context) 160 is FieldType.Nullable -> 161 this.itemFieldType.findPolicyViolations(prefix, policyField, hasPolicyViolation, context) 162 is FieldType.Nested -> { 163 context.dataTypeDescriptorSet[this.name].findPolicyViolations( 164 prefix, 165 policyField, 166 hasPolicyViolation, 167 context 168 ) 169 } 170 is FieldType.Reference -> 171 throw IllegalArgumentException("References are not supported in policy yet.") 172 is FieldType.Tuple -> 173 throw IllegalArgumentException("Tuples are not supported in policy yet.") 174 } 175 } 176 177 private fun DataTypeDescriptor.findPolicyViolations( 178 prefix: String, 179 policyField: PolicyField, 180 hasPolicyViolation: (PolicyField) -> Boolean, 181 context: ChronicleContext 182 ): List<String> { 183 val violatingFields = mutableListOf<String>() 184 this.fields.forEach { (fieldName, type) -> 185 policyField.subfields 186 .find { it.fieldPath.last() == fieldName } 187 ?.let { 188 violatingFields.addAll( 189 type.findPolicyViolations("$prefix.$fieldName", it, hasPolicyViolation, context) 190 ) 191 } 192 ?: violatingFields.add("$prefix.$fieldName") 193 } 194 return violatingFields.toList() 195 } 196 197 companion object { 198 /** 199 * Predicate with special label used in the [Checks][Check] rendered as errors for a 200 * [PolicyCheckResult.Fail] to let the user know that a given [DataType] does not have policies 201 * matching its [ManagementStrategy]. 202 * 203 * Not intended to be propagated with label propagation, it's for error-messaging only. 204 */ 205 private const val MUST_HAVE_POLICY_PREDICATE = "\"must have a corresponding policy\"" 206 207 /** 208 * Predicate with special label used in the [Checks][Check] rendered as errors for a 209 * [PolicyCheckResult.Fail] to let the user know that a given [DataType] is insufficiently 210 * restrained according to policy. 211 * 212 * Not intended to be propagated with label propagation, it's for error-messaging only. 213 */ 214 private const val MANAGEMENT_STRATEGY_NOT_STRICT_ENOUGH_PREDICATE = 215 "\"management is at least as restrained as the most conservative policy\"" 216 217 /** 218 * Predicate with special label used in the [Checks][Check] rendered as errors for a 219 * [PolicyCheckResult.Fail] to let the user know that a given [Connection], the corresponding 220 * [DataTypeDescriptor] is not registered with [Chronicle]. 221 * 222 * Not intended to be propagated with label propagation, it's for error-messaging only. 223 */ 224 private const val CONNECTON_NOT_FOUND_PREDICATE = "not a registered connection" 225 226 /** 227 * Predicate with special label used in the [Checks][Check] rendered as errors for a 228 * [PolicyCheckResult.Fail] to let the user know that a given [Connection], the corresponding 229 * [DataTypeDescriptor] is not found specified in the given [Policy]. 230 * 231 * Not intended to be propagated with label propagation, it's for error-messaging only. 232 */ 233 private const val DTD_NOT_FOUND_PREDICATE = "not found in the given policy" 234 235 /** 236 * Predicate with special label used in the [Checks][Check] rendered as errors for a 237 * [PolicyCheckResult.Fail] to let the user know that a given [Connection], the corresponding 238 * field in the [DataTypeDescriptor] is not allowed for [Usage.ANY] in the given [Policy]. 239 * 240 * Not intended to be propagated with label propagation, it's for error-messaging only. 241 */ 242 private const val FIELD_CANNOT_BE_EGRESSED_PREDICATE = "not allowed for egress" 243 } 244 } 245