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