1// Copyright 2024 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package android 16 17import ( 18 "fmt" 19 "reflect" 20 "slices" 21 "strings" 22 23 "github.com/google/blueprint" 24) 25 26// ---------------------------------------------------------------------------- 27// Start of the definitions of exception functions and the lookup table. 28// 29// Functions cannot be used as a value passed in providers, because functions are not 30// hashable. As a workaround, the [exceptionHandleFuncLabel] enum values are passed using providers, 31// and the corresponding functions are called from [exceptionHandleFunctionsTable] map. 32// ---------------------------------------------------------------------------- 33 34type exceptionHandleFunc func(ModuleContext, Module, Module) bool 35 36type StubsAvailableModule interface { 37 IsStubsModule() bool 38} 39 40// Returns true if the dependency module is a stubs module 41var depIsStubsModule exceptionHandleFunc = func(_ ModuleContext, _, dep Module) bool { 42 if stubsModule, ok := dep.(StubsAvailableModule); ok { 43 return stubsModule.IsStubsModule() 44 } 45 return false 46} 47 48// Returns true if the dependency module belongs to any of the apexes. 49var depIsApexModule exceptionHandleFunc = func(mctx ModuleContext, _, dep Module) bool { 50 depContainersInfo, _ := getContainerModuleInfo(mctx, dep) 51 return InList(ApexContainer, depContainersInfo.belongingContainers) 52} 53 54// Returns true if the module and the dependent module belongs to common apexes. 55var belongsToCommonApexes exceptionHandleFunc = func(mctx ModuleContext, m, dep Module) bool { 56 mContainersInfo, _ := getContainerModuleInfo(mctx, m) 57 depContainersInfo, _ := getContainerModuleInfo(mctx, dep) 58 59 return HasIntersection(mContainersInfo.ApexNames(), depContainersInfo.ApexNames()) 60} 61 62// Returns true when all apexes that the module belongs to are non updatable. 63// For an apex module to be allowed to depend on a non-apex partition module, 64// all apexes that the module belong to must be non updatable. 65var belongsToNonUpdatableApex exceptionHandleFunc = func(mctx ModuleContext, m, _ Module) bool { 66 mContainersInfo, _ := getContainerModuleInfo(mctx, m) 67 68 return !mContainersInfo.UpdatableApex() 69} 70 71// Returns true if the dependency is added via dependency tags that are not used to tag dynamic 72// dependency tags. 73var depIsNotDynamicDepTag exceptionHandleFunc = func(ctx ModuleContext, m, dep Module) bool { 74 mInstallable, _ := m.(InstallableModule) 75 depTag := ctx.OtherModuleDependencyTag(dep) 76 return !InList(depTag, mInstallable.DynamicDependencyTags()) 77} 78 79// Returns true if the dependency is added via dependency tags that are not used to tag static 80// or dynamic dependency tags. These dependencies do not affect the module in compile time or in 81// runtime, thus are not significant enough to raise an error. 82var depIsNotStaticOrDynamicDepTag exceptionHandleFunc = func(ctx ModuleContext, m, dep Module) bool { 83 mInstallable, _ := m.(InstallableModule) 84 depTag := ctx.OtherModuleDependencyTag(dep) 85 return !InList(depTag, append(mInstallable.StaticDependencyTags(), mInstallable.DynamicDependencyTags()...)) 86} 87 88var globallyAllowlistedDependencies = []string{ 89 // Modules that provide annotations used within the platform and apexes. 90 "aconfig-annotations-lib", 91 "framework-annotations-lib", 92 "unsupportedappusage", 93 94 // TODO(b/363016634): Remove from the allowlist when the module is converted 95 // to java_sdk_library and the java_aconfig_library modules depend on the stub. 96 "aconfig_storage_stub", 97 98 // framework-res provides core resources essential for building apps and system UI. 99 // This module is implicitly added as a dependency for java modules even when the 100 // dependency specifies sdk_version. 101 "framework-res", 102 103 // jacocoagent is implicitly added as a dependency in coverage builds, and is not installed 104 // on the device. 105 "jacocoagent", 106} 107 108// Returns true when the dependency is globally allowlisted for inter-container dependency 109var depIsGloballyAllowlisted exceptionHandleFunc = func(_ ModuleContext, _, dep Module) bool { 110 return InList(dep.Name(), globallyAllowlistedDependencies) 111} 112 113// Labels of exception functions, which are used to determine special dependencies that allow 114// otherwise restricted inter-container dependencies 115type exceptionHandleFuncLabel int 116 117const ( 118 checkStubs exceptionHandleFuncLabel = iota 119 checkApexModule 120 checkInCommonApexes 121 checkApexIsNonUpdatable 122 checkNotDynamicDepTag 123 checkNotStaticOrDynamicDepTag 124 checkGlobalAllowlistedDep 125) 126 127// Map of [exceptionHandleFuncLabel] to the [exceptionHandleFunc] 128var exceptionHandleFunctionsTable = map[exceptionHandleFuncLabel]exceptionHandleFunc{ 129 checkStubs: depIsStubsModule, 130 checkApexModule: depIsApexModule, 131 checkInCommonApexes: belongsToCommonApexes, 132 checkApexIsNonUpdatable: belongsToNonUpdatableApex, 133 checkNotDynamicDepTag: depIsNotDynamicDepTag, 134 checkNotStaticOrDynamicDepTag: depIsNotStaticOrDynamicDepTag, 135 checkGlobalAllowlistedDep: depIsGloballyAllowlisted, 136} 137 138// ---------------------------------------------------------------------------- 139// Start of the definitions of container determination functions. 140// 141// Similar to the above section, below defines the functions used to determine 142// the container of each modules. 143// ---------------------------------------------------------------------------- 144 145type containerBoundaryFunc func(mctx ModuleContext) bool 146 147var vendorContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool { 148 m, ok := mctx.Module().(ImageInterface) 149 return mctx.Module().InstallInVendor() || (ok && m.VendorVariantNeeded(mctx)) 150} 151 152var systemContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool { 153 module := mctx.Module() 154 155 return !module.InstallInTestcases() && 156 !module.InstallInData() && 157 !module.InstallInRamdisk() && 158 !module.InstallInVendorRamdisk() && 159 !module.InstallInDebugRamdisk() && 160 !module.InstallInRecovery() && 161 !module.InstallInVendor() && 162 !module.InstallInOdm() && 163 !module.InstallInProduct() && 164 determineModuleKind(module.base(), mctx.blueprintBaseModuleContext()) == platformModule 165} 166 167var productContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool { 168 m, ok := mctx.Module().(ImageInterface) 169 return mctx.Module().InstallInProduct() || (ok && m.ProductVariantNeeded(mctx)) 170} 171 172var apexContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool { 173 _, ok := ModuleProvider(mctx, AllApexInfoProvider) 174 return ok 175} 176 177var ctsContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool { 178 props := mctx.Module().GetProperties() 179 for _, prop := range props { 180 val := reflect.ValueOf(prop).Elem() 181 if val.Kind() == reflect.Struct { 182 testSuites := val.FieldByName("Test_suites") 183 if testSuites.IsValid() && testSuites.Kind() == reflect.Slice && slices.Contains(testSuites.Interface().([]string), "cts") { 184 return true 185 } 186 } 187 } 188 return false 189} 190 191type unstableInfo struct { 192 // Determines if the module contains the private APIs of the platform. 193 ContainsPlatformPrivateApis bool 194} 195 196var unstableInfoProvider = blueprint.NewProvider[unstableInfo]() 197 198func determineUnstableModule(mctx ModuleContext) bool { 199 module := mctx.Module() 200 unstableModule := module.Name() == "framework-minus-apex" 201 if installable, ok := module.(InstallableModule); ok { 202 for _, staticDepTag := range installable.StaticDependencyTags() { 203 mctx.VisitDirectDepsWithTag(staticDepTag, func(dep Module) { 204 if unstableInfo, ok := OtherModuleProvider(mctx, dep, unstableInfoProvider); ok { 205 unstableModule = unstableModule || unstableInfo.ContainsPlatformPrivateApis 206 } 207 }) 208 } 209 } 210 return unstableModule 211} 212 213var unstableContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool { 214 return determineUnstableModule(mctx) 215} 216 217// Map of [*container] to the [containerBoundaryFunc] 218var containerBoundaryFunctionsTable = map[*container]containerBoundaryFunc{ 219 VendorContainer: vendorContainerBoundaryFunc, 220 SystemContainer: systemContainerBoundaryFunc, 221 ProductContainer: productContainerBoundaryFunc, 222 ApexContainer: apexContainerBoundaryFunc, 223 CtsContainer: ctsContainerBoundaryFunc, 224 UnstableContainer: unstableContainerBoundaryFunc, 225} 226 227// ---------------------------------------------------------------------------- 228// End of the definitions of container determination functions. 229// ---------------------------------------------------------------------------- 230 231type InstallableModule interface { 232 StaticDependencyTags() []blueprint.DependencyTag 233 DynamicDependencyTags() []blueprint.DependencyTag 234} 235 236type restriction struct { 237 // container of the dependency 238 dependency *container 239 240 // Error message to be emitted to the user when the dependency meets this restriction 241 errorMessage string 242 243 // List of labels of allowed exception functions that allows bypassing this restriction. 244 // If any of the functions mapped to each labels returns true, this dependency would be 245 // considered allowed and an error will not be thrown. 246 allowedExceptions []exceptionHandleFuncLabel 247} 248type container struct { 249 // The name of the container i.e. partition, api domain 250 name string 251 252 // Map of dependency restricted containers. 253 restricted []restriction 254} 255 256var ( 257 VendorContainer = &container{ 258 name: VendorVariation, 259 restricted: nil, 260 } 261 262 SystemContainer = &container{ 263 name: "system", 264 restricted: []restriction{ 265 { 266 dependency: VendorContainer, 267 errorMessage: "Module belonging to the system partition other than HALs is " + 268 "not allowed to depend on the vendor partition module, in order to support " + 269 "independent development/update cycles and to support the Generic System " + 270 "Image. Try depending on HALs, VNDK or AIDL instead.", 271 allowedExceptions: []exceptionHandleFuncLabel{ 272 checkStubs, 273 checkNotDynamicDepTag, 274 checkGlobalAllowlistedDep, 275 }, 276 }, 277 }, 278 } 279 280 ProductContainer = &container{ 281 name: ProductVariation, 282 restricted: []restriction{ 283 { 284 dependency: VendorContainer, 285 errorMessage: "Module belonging to the product partition is not allowed to " + 286 "depend on the vendor partition module, as this may lead to security " + 287 "vulnerabilities. Try depending on the HALs or utilize AIDL instead.", 288 allowedExceptions: []exceptionHandleFuncLabel{ 289 checkStubs, 290 checkNotDynamicDepTag, 291 checkGlobalAllowlistedDep, 292 }, 293 }, 294 }, 295 } 296 297 ApexContainer = initializeApexContainer() 298 299 CtsContainer = &container{ 300 name: "cts", 301 restricted: []restriction{ 302 { 303 dependency: UnstableContainer, 304 errorMessage: "CTS module should not depend on the modules that contain the " + 305 "platform implementation details, including \"framework\". Depending on these " + 306 "modules may lead to disclosure of implementation details and regression " + 307 "due to API changes across platform versions. Try depending on the stubs instead " + 308 "and ensure that the module sets an appropriate 'sdk_version'.", 309 allowedExceptions: []exceptionHandleFuncLabel{ 310 checkStubs, 311 checkNotStaticOrDynamicDepTag, 312 checkGlobalAllowlistedDep, 313 }, 314 }, 315 }, 316 } 317 318 // Container signifying that the module contains unstable platform private APIs 319 UnstableContainer = &container{ 320 name: "unstable", 321 restricted: nil, 322 } 323 324 allContainers = []*container{ 325 VendorContainer, 326 SystemContainer, 327 ProductContainer, 328 ApexContainer, 329 CtsContainer, 330 UnstableContainer, 331 } 332) 333 334func initializeApexContainer() *container { 335 apexContainer := &container{ 336 name: "apex", 337 restricted: []restriction{ 338 { 339 dependency: SystemContainer, 340 errorMessage: "Module belonging to Apex(es) is not allowed to depend on the " + 341 "modules belonging to the system partition. Either statically depend on the " + 342 "module or convert the depending module to java_sdk_library and depend on " + 343 "the stubs.", 344 allowedExceptions: []exceptionHandleFuncLabel{ 345 checkStubs, 346 checkApexModule, 347 checkInCommonApexes, 348 checkApexIsNonUpdatable, 349 checkNotStaticOrDynamicDepTag, 350 checkGlobalAllowlistedDep, 351 }, 352 }, 353 }, 354 } 355 356 apexContainer.restricted = append(apexContainer.restricted, restriction{ 357 dependency: apexContainer, 358 errorMessage: "Module belonging to Apex(es) is not allowed to depend on the " + 359 "modules belonging to other Apex(es). Either include the depending " + 360 "module in the Apex or convert the depending module to java_sdk_library " + 361 "and depend on its stubs.", 362 allowedExceptions: []exceptionHandleFuncLabel{ 363 checkStubs, 364 checkInCommonApexes, 365 checkNotStaticOrDynamicDepTag, 366 checkGlobalAllowlistedDep, 367 }, 368 }) 369 370 return apexContainer 371} 372 373type ContainersInfo struct { 374 belongingContainers []*container 375 376 belongingApexes []ApexInfo 377} 378 379func (c *ContainersInfo) BelongingContainers() []*container { 380 return c.belongingContainers 381} 382 383func (c *ContainersInfo) ApexNames() (ret []string) { 384 for _, apex := range c.belongingApexes { 385 ret = append(ret, apex.InApexVariants...) 386 } 387 slices.Sort(ret) 388 return ret 389} 390 391// Returns true if any of the apex the module belongs to is updatable. 392func (c *ContainersInfo) UpdatableApex() bool { 393 for _, apex := range c.belongingApexes { 394 if apex.Updatable { 395 return true 396 } 397 } 398 return false 399} 400 401var ContainersInfoProvider = blueprint.NewProvider[ContainersInfo]() 402 403func satisfyAllowedExceptions(ctx ModuleContext, allowedExceptionLabels []exceptionHandleFuncLabel, m, dep Module) bool { 404 for _, label := range allowedExceptionLabels { 405 if exceptionHandleFunctionsTable[label](ctx, m, dep) { 406 return true 407 } 408 } 409 return false 410} 411 412func (c *ContainersInfo) GetViolations(mctx ModuleContext, m, dep Module, depInfo ContainersInfo) []string { 413 var violations []string 414 415 // Any containers that the module belongs to but the dependency does not belong to must be examined. 416 _, containersUniqueToModule, _ := ListSetDifference(c.belongingContainers, depInfo.belongingContainers) 417 418 // Apex container should be examined even if both the module and the dependency belong to 419 // the apex container to check that the two modules belong to the same apex. 420 if InList(ApexContainer, c.belongingContainers) && !InList(ApexContainer, containersUniqueToModule) { 421 containersUniqueToModule = append(containersUniqueToModule, ApexContainer) 422 } 423 424 for _, containerUniqueToModule := range containersUniqueToModule { 425 for _, restriction := range containerUniqueToModule.restricted { 426 if InList(restriction.dependency, depInfo.belongingContainers) { 427 if !satisfyAllowedExceptions(mctx, restriction.allowedExceptions, m, dep) { 428 violations = append(violations, restriction.errorMessage) 429 } 430 } 431 } 432 } 433 434 return violations 435} 436 437func generateContainerInfo(ctx ModuleContext) ContainersInfo { 438 var containers []*container 439 440 for _, cnt := range allContainers { 441 if containerBoundaryFunctionsTable[cnt](ctx) { 442 containers = append(containers, cnt) 443 } 444 } 445 446 var belongingApexes []ApexInfo 447 if apexInfo, ok := ModuleProvider(ctx, AllApexInfoProvider); ok { 448 belongingApexes = apexInfo.ApexInfos 449 } 450 451 return ContainersInfo{ 452 belongingContainers: containers, 453 belongingApexes: belongingApexes, 454 } 455} 456 457func getContainerModuleInfo(ctx ModuleContext, module Module) (ContainersInfo, bool) { 458 if ctx.Module() == module { 459 return ctx.getContainersInfo(), true 460 } 461 462 return OtherModuleProvider(ctx, module, ContainersInfoProvider) 463} 464 465func setContainerInfo(ctx ModuleContext) { 466 // Required to determine the unstable container. This provider is set here instead of the 467 // unstableContainerBoundaryFunc in order to prevent setting the provider multiple times. 468 SetProvider(ctx, unstableInfoProvider, unstableInfo{ 469 ContainsPlatformPrivateApis: determineUnstableModule(ctx), 470 }) 471 472 if _, ok := ctx.Module().(InstallableModule); ok { 473 containersInfo := generateContainerInfo(ctx) 474 ctx.setContainersInfo(containersInfo) 475 SetProvider(ctx, ContainersInfoProvider, containersInfo) 476 } 477} 478 479func checkContainerViolations(ctx ModuleContext) { 480 if _, ok := ctx.Module().(InstallableModule); ok { 481 containersInfo, _ := getContainerModuleInfo(ctx, ctx.Module()) 482 ctx.VisitDirectDeps(func(dep Module) { 483 if !dep.Enabled(ctx) { 484 return 485 } 486 487 // Pre-existing violating dependencies are tracked in containerDependencyViolationAllowlist. 488 // If this dependency is allowlisted, do not check for violation. 489 // If not, check if this dependency matches any restricted dependency and 490 // satisfies any exception functions, which allows bypassing the 491 // restriction. If all of the exceptions are not satisfied, throw an error. 492 if depContainersInfo, ok := getContainerModuleInfo(ctx, dep); ok { 493 if allowedViolations, ok := ContainerDependencyViolationAllowlist[ctx.ModuleName()]; ok && InList(dep.Name(), allowedViolations) { 494 return 495 } else { 496 violations := containersInfo.GetViolations(ctx, ctx.Module(), dep, depContainersInfo) 497 if len(violations) > 0 { 498 errorMessage := fmt.Sprintf("%s cannot depend on %s. ", ctx.ModuleName(), dep.Name()) 499 errorMessage += strings.Join(violations, " ") 500 ctx.ModuleErrorf(errorMessage) 501 } 502 } 503 } 504 }) 505 } 506} 507