1// Copyright (C) 2021 The Android Open Source Project 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 filesystem 16 17import ( 18 "fmt" 19 "sort" 20 "strconv" 21 "strings" 22 "time" 23 24 "github.com/google/blueprint" 25 "github.com/google/blueprint/proptools" 26 27 "android/soong/android" 28) 29 30func init() { 31 android.RegisterModuleType("vbmeta", VbmetaFactory) 32 pctx.HostBinToolVariable("avbtool", "avbtool") 33} 34 35var ( 36 extractPublicKeyRule = pctx.AndroidStaticRule("avb_extract_public_key", 37 blueprint.RuleParams{ 38 Command: `${avbtool} extract_public_key --key $in --output $out`, 39 CommandDeps: []string{ 40 "${avbtool}", 41 }, 42 }) 43) 44 45type vbmeta struct { 46 android.ModuleBase 47 48 properties VbmetaProperties 49 50 output android.Path 51 installDir android.InstallPath 52} 53 54type VbmetaProperties struct { 55 // Name of the partition stored in vbmeta desc. Defaults to the name of this module. 56 Partition_name *string 57 58 // Type of the `android_filesystem` for which the vbmeta.img is created. 59 // Examples are system, vendor, product. 60 Filesystem_partition_type *string 61 62 // Set the name of the output. Defaults to <module_name>.img. 63 Stem *string 64 65 // Path to the private key that avbtool will use to sign this vbmeta image. 66 Private_key *string `android:"path"` 67 68 // Algorithm that avbtool will use to sign this vbmeta image. Default is SHA256_RSA4096. 69 Algorithm *string 70 71 // The rollback index. If unspecified, the rollback index is from PLATFORM_SECURITY_PATCH 72 Rollback_index *int64 73 74 // Rollback index location of this vbmeta image. Must be 0, 1, 2, etc. Default is 0. 75 Rollback_index_location *int64 76 77 // List of filesystem modules that this vbmeta has descriptors for. The filesystem modules 78 // have to be signed (use_avb: true). 79 Partitions proptools.Configurable[[]string] 80 81 // Metadata about the chained partitions that this vbmeta delegates the verification. 82 // This is an alternative to chained_partitions, using chained_partitions instead is simpler 83 // in most cases. However, this property allows building this vbmeta partition without 84 // its chained partitions existing in this build. 85 Chained_partition_metadata []ChainedPartitionProperties 86 87 // List of chained partitions that this vbmeta delegates the verification. They are the 88 // names of other vbmeta modules. 89 Chained_partitions []string 90 91 // List of key-value pair of avb properties 92 Avb_properties []avbProperty 93} 94 95type avbProperty struct { 96 // Key of given avb property 97 Key *string 98 99 // Value of given avb property 100 Value *string 101} 102 103type ChainedPartitionProperties struct { 104 // Name of the chained partition 105 Name *string 106 107 // Rollback index location of the chained partition. Must be 1, 2, 3, etc. Default is the 108 // index of this partition in the list + 1. 109 Rollback_index_location *int64 110 111 // Path to the public key that the chained partition is signed with. If this is specified, 112 // private_key is ignored. 113 Public_key *string `android:"path"` 114 115 // Path to the private key that the chained partition is signed with. If this is specified, 116 // and public_key is not specified, a public key is extracted from this private key and 117 // the extracted public key is embedded in the vbmeta image. 118 Private_key *string `android:"path"` 119} 120 121type vbmetaPartitionInfo struct { 122 // Name of the partition 123 Name string 124 125 // Partition type of the correspdonding android_filesystem. 126 FilesystemPartitionType string 127 128 // Rollback index location, non-negative int 129 RollbackIndexLocation int 130 131 // The path to the public key of the private key used to sign this partition. Derived from 132 // the private key. 133 PublicKey android.Path 134 135 // The output of the vbmeta module 136 Output android.Path 137 138 // Information about the vbmeta partition that will be added to misc_info.txt 139 // created by android_device 140 PropFileForMiscInfo android.Path 141} 142 143var vbmetaPartitionProvider = blueprint.NewProvider[vbmetaPartitionInfo]() 144 145// vbmeta is the partition image that has the verification information for other partitions. 146func VbmetaFactory() android.Module { 147 module := &vbmeta{} 148 module.AddProperties(&module.properties) 149 android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon) 150 return module 151} 152 153type vbmetaDep struct { 154 blueprint.BaseDependencyTag 155} 156 157type chainedPartitionDep struct { 158 blueprint.BaseDependencyTag 159} 160 161var vbmetaPartitionDep = vbmetaDep{} 162var vbmetaChainedPartitionDep = chainedPartitionDep{} 163 164func (v *vbmeta) DepsMutator(ctx android.BottomUpMutatorContext) { 165 ctx.AddVariationDependencies(ctx.Config().AndroidFirstDeviceTarget.Variations(), vbmetaPartitionDep, v.properties.Partitions.GetOrDefault(ctx, nil)...) 166 ctx.AddVariationDependencies(ctx.Config().AndroidFirstDeviceTarget.Variations(), vbmetaChainedPartitionDep, v.properties.Chained_partitions...) 167} 168 169func (v *vbmeta) installFileName() string { 170 return proptools.StringDefault(v.properties.Stem, v.BaseModuleName()+".img") 171} 172 173func (v *vbmeta) partitionName() string { 174 return proptools.StringDefault(v.properties.Partition_name, v.BaseModuleName()) 175} 176 177// See external/avb/libavb/avb_slot_verify.c#VBMETA_MAX_SIZE 178const vbmetaMaxSize = 64 * 1024 179 180func (v *vbmeta) GenerateAndroidBuildActions(ctx android.ModuleContext) { 181 builder := android.NewRuleBuilder(pctx, ctx) 182 cmd := builder.Command().BuiltTool("avbtool").Text("make_vbmeta_image") 183 184 key := android.PathForModuleSrc(ctx, proptools.String(v.properties.Private_key)) 185 cmd.FlagWithInput("--key ", key) 186 187 algorithm := proptools.StringDefault(v.properties.Algorithm, "SHA256_RSA4096") 188 cmd.FlagWithArg("--algorithm ", algorithm) 189 190 cmd.FlagWithArg("--padding_size ", "4096") 191 192 cmd.FlagWithArg("--rollback_index ", v.rollbackIndexCommand(ctx)) 193 ril := proptools.IntDefault(v.properties.Rollback_index_location, 0) 194 if ril < 0 { 195 ctx.PropertyErrorf("rollback_index_location", "must be 0, 1, 2, ...") 196 return 197 } 198 199 for _, avb_prop := range v.properties.Avb_properties { 200 key := proptools.String(avb_prop.Key) 201 if key == "" { 202 ctx.PropertyErrorf("avb_properties", "key must be specified") 203 continue 204 } 205 value := proptools.String(avb_prop.Value) 206 if value == "" { 207 ctx.PropertyErrorf("avb_properties", "value must be specified") 208 continue 209 } 210 cmd.FlagWithArg("--prop ", key+":"+value) 211 } 212 213 for _, p := range ctx.GetDirectDepsWithTag(vbmetaPartitionDep) { 214 f, ok := p.(Filesystem) 215 if !ok { 216 ctx.PropertyErrorf("partitions", "%q(type: %s) is not supported", 217 p.Name(), ctx.OtherModuleType(p)) 218 continue 219 } 220 signedImage := f.SignedOutputPath() 221 if signedImage == nil { 222 ctx.PropertyErrorf("partitions", "%q(type: %s) is not signed. Use `use_avb: true`", 223 p.Name(), ctx.OtherModuleType(p)) 224 continue 225 } 226 cmd.FlagWithInput("--include_descriptors_from_image ", signedImage) 227 } 228 229 seenRils := make(map[int]bool) 230 for _, cp := range ctx.GetDirectDepsWithTag(vbmetaChainedPartitionDep) { 231 info, ok := android.OtherModuleProvider(ctx, cp, vbmetaPartitionProvider) 232 if !ok { 233 ctx.PropertyErrorf("chained_partitions", "Expected all modules in chained_partitions to provide vbmetaPartitionProvider, but %s did not", cp.Name()) 234 continue 235 } 236 if info.Name == "" { 237 ctx.PropertyErrorf("chained_partitions", "name must be specified") 238 continue 239 } 240 241 ril := info.RollbackIndexLocation 242 if ril < 1 { 243 ctx.PropertyErrorf("chained_partitions", "rollback index location must be 1, 2, 3, ...") 244 continue 245 } else if seenRils[ril] { 246 ctx.PropertyErrorf("chained_partitions", "Multiple chained partitions with the same rollback index location %d", ril) 247 continue 248 } 249 seenRils[ril] = true 250 251 publicKey := info.PublicKey 252 cmd.FlagWithArg("--chain_partition ", fmt.Sprintf("%s:%d:%s", info.Name, ril, publicKey.String())) 253 cmd.Implicit(publicKey) 254 } 255 for _, cpm := range v.properties.Chained_partition_metadata { 256 name := proptools.String(cpm.Name) 257 if name == "" { 258 ctx.PropertyErrorf("chained_partition_metadata", "name must be specified") 259 continue 260 } 261 262 ril := proptools.IntDefault(cpm.Rollback_index_location, 0) 263 if ril < 1 { 264 ctx.PropertyErrorf("chained_partition_metadata", "rollback index location must be 1, 2, 3, ...") 265 continue 266 } else if seenRils[ril] { 267 ctx.PropertyErrorf("chained_partition_metadata", "Multiple chained partitions with the same rollback index location %d", ril) 268 continue 269 } 270 seenRils[ril] = true 271 272 var publicKey android.Path 273 if cpm.Public_key != nil { 274 publicKey = android.PathForModuleSrc(ctx, *cpm.Public_key) 275 } else if cpm.Private_key != nil { 276 privateKey := android.PathForModuleSrc(ctx, *cpm.Private_key) 277 extractedPublicKey := android.PathForModuleOut(ctx, "chained_metadata", name+".avbpubkey") 278 ctx.Build(pctx, android.BuildParams{ 279 Rule: extractPublicKeyRule, 280 Input: privateKey, 281 Output: extractedPublicKey, 282 }) 283 publicKey = extractedPublicKey 284 } else { 285 ctx.PropertyErrorf("public_key", "Either public_key or private_key must be specified") 286 continue 287 } 288 289 cmd.FlagWithArg("--chain_partition ", fmt.Sprintf("%s:%d:%s", name, ril, publicKey.String())) 290 cmd.Implicit(publicKey) 291 } 292 293 output := android.PathForModuleOut(ctx, v.installFileName()) 294 cmd.FlagWithOutput("--output ", output) 295 296 // libavb expects to be able to read the maximum vbmeta size, so we must provide a partition 297 // which matches this or the read will fail. 298 builder.Command().Text("truncate"). 299 FlagWithArg("-s ", strconv.Itoa(vbmetaMaxSize)). 300 Output(output) 301 302 builder.Build("vbmeta", fmt.Sprintf("vbmeta %s", ctx.ModuleName())) 303 304 v.installDir = android.PathForModuleInstall(ctx, "etc") 305 ctx.InstallFile(v.installDir, v.installFileName(), output) 306 307 extractedPublicKey := android.PathForModuleOut(ctx, v.partitionName()+".avbpubkey") 308 ctx.Build(pctx, android.BuildParams{ 309 Rule: extractPublicKeyRule, 310 Input: key, 311 Output: extractedPublicKey, 312 }) 313 314 android.SetProvider(ctx, vbmetaPartitionProvider, vbmetaPartitionInfo{ 315 Name: v.partitionName(), 316 FilesystemPartitionType: proptools.String(v.properties.Filesystem_partition_type), 317 RollbackIndexLocation: ril, 318 PublicKey: extractedPublicKey, 319 Output: output, 320 PropFileForMiscInfo: v.buildPropFileForMiscInfo(ctx), 321 }) 322 323 ctx.SetOutputFiles([]android.Path{output}, "") 324 v.output = output 325 326 setCommonFilesystemInfo(ctx, v) 327} 328 329func (v *vbmeta) buildPropFileForMiscInfo(ctx android.ModuleContext) android.Path { 330 var lines []string 331 addStr := func(name string, value string) { 332 lines = append(lines, fmt.Sprintf("%s=%s", name, value)) 333 } 334 335 addStr(fmt.Sprintf("avb_%s_algorithm", v.partitionName()), proptools.StringDefault(v.properties.Algorithm, "SHA256_RSA4096")) 336 if v.properties.Private_key != nil { 337 addStr(fmt.Sprintf("avb_%s_key_path", v.partitionName()), android.PathForModuleSrc(ctx, proptools.String(v.properties.Private_key)).String()) 338 } 339 if v.properties.Rollback_index_location != nil { 340 addStr(fmt.Sprintf("avb_%s_rollback_index_location", v.partitionName()), strconv.FormatInt(*v.properties.Rollback_index_location, 10)) 341 } 342 343 var partitionDepNames []string 344 ctx.VisitDirectDepsProxyWithTag(vbmetaPartitionDep, func(child android.ModuleProxy) { 345 if info, ok := android.OtherModuleProvider(ctx, child, vbmetaPartitionProvider); ok { 346 partitionDepNames = append(partitionDepNames, info.Name) 347 } else { 348 ctx.ModuleErrorf("vbmeta dep %s does not set vbmetaPartitionProvider\n", child) 349 } 350 }) 351 if v.partitionName() != "vbmeta" { // skip for vbmeta to match Make's misc_info.txt 352 addStr(fmt.Sprintf("avb_%s", v.partitionName()), strings.Join(android.SortedUniqueStrings(partitionDepNames), " ")) 353 } 354 355 addStr(fmt.Sprintf("avb_%s_args", v.partitionName()), fmt.Sprintf("--padding_size 4096 --rollback_index %s", v.rollbackIndexString(ctx))) 356 357 sort.Strings(lines) 358 359 propFile := android.PathForModuleOut(ctx, "prop_file_for_misc_info") 360 android.WriteFileRule(ctx, propFile, strings.Join(lines, "\n")) 361 return propFile 362} 363 364// Returns the embedded shell command that prints the rollback index 365func (v *vbmeta) rollbackIndexCommand(ctx android.ModuleContext) string { 366 if v.properties.Rollback_index != nil { 367 return fmt.Sprintf("%d", *v.properties.Rollback_index) 368 } else { 369 // Take the first line and remove the newline char 370 return "$(date -d 'TZ=\"GMT\" " + ctx.Config().PlatformSecurityPatch() + "' +%s | head -1 | tr -d '\n'" + ")" 371 } 372} 373 374// Similar to rollbackIndexCommand, but guarantees that the rollback index is 375// always computed during Soong analysis, even if v.properties.Rollback_index is nil 376func (v *vbmeta) rollbackIndexString(ctx android.ModuleContext) string { 377 if v.properties.Rollback_index != nil { 378 return fmt.Sprintf("%d", *v.properties.Rollback_index) 379 } else { 380 t, _ := time.Parse(time.DateOnly, ctx.Config().PlatformSecurityPatch()) 381 return fmt.Sprintf("%d", t.Unix()) 382 } 383} 384 385var _ android.AndroidMkProviderInfoProducer = (*vbmeta)(nil) 386 387func (v *vbmeta) PrepareAndroidMKProviderInfo(config android.Config) *android.AndroidMkProviderInfo { 388 providerData := android.AndroidMkProviderInfo{ 389 PrimaryInfo: android.AndroidMkInfo{ 390 Class: "ETC", 391 OutputFile: android.OptionalPathForPath(v.output), 392 EntryMap: make(map[string][]string), 393 }, 394 } 395 providerData.PrimaryInfo.SetString("LOCAL_MODULE_PATH", v.installDir.String()) 396 providerData.PrimaryInfo.SetString("LOCAL_INSTALLED_MODULE_STEM", v.installFileName()) 397 return &providerData 398} 399 400var _ Filesystem = (*vbmeta)(nil) 401 402func (v *vbmeta) OutputPath() android.Path { 403 return v.output 404} 405 406func (v *vbmeta) SignedOutputPath() android.Path { 407 return v.OutputPath() // vbmeta is always signed 408} 409