• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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