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