xref: /aosp_15_r20/build/soong/filesystem/bootimg.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	"strings"
21
22	"github.com/google/blueprint"
23	"github.com/google/blueprint/proptools"
24
25	"android/soong/android"
26)
27
28func init() {
29	android.RegisterModuleType("bootimg", BootimgFactory)
30}
31
32type bootimg struct {
33	android.ModuleBase
34
35	properties BootimgProperties
36
37	output     android.Path
38	installDir android.InstallPath
39
40	bootImageType bootImageType
41}
42
43type BootimgProperties struct {
44	// Set the name of the output. Defaults to <module_name>.img.
45	Stem *string
46
47	// Path to the linux kernel prebuilt file
48	Kernel_prebuilt *string `android:"arch_variant,path"`
49
50	// Filesystem module that is used as ramdisk
51	Ramdisk_module *string
52
53	// Path to the device tree blob (DTB) prebuilt file to add to this boot image
54	Dtb_prebuilt *string `android:"arch_variant,path"`
55
56	// Header version number. Must be set to one of the version numbers that are currently
57	// supported. Refer to
58	// https://source.android.com/devices/bootloader/boot-image-header
59	Header_version *string
60
61	// Determines the specific type of boot image this module is building. Can be boot,
62	// vendor_boot or init_boot. Defaults to boot.
63	// Refer to https://source.android.com/devices/bootloader/partitions/vendor-boot-partitions
64	// for vendor_boot.
65	// Refer to https://source.android.com/docs/core/architecture/partitions/generic-boot for
66	// init_boot.
67	Boot_image_type *string
68
69	// Optional kernel commandline arguments
70	Cmdline []string `android:"arch_variant"`
71
72	// File that contains bootconfig parameters. This can be set only when `vendor_boot` is true
73	// and `header_version` is greater than or equal to 4.
74	Bootconfig *string `android:"arch_variant,path"`
75
76	// The size of the partition on the device. It will be a build error if this built partition
77	// image exceeds this size.
78	Partition_size *int64
79
80	// When set to true, sign the image with avbtool. Default is false.
81	Use_avb *bool
82
83	// This can either be "default", or "make_legacy". "make_legacy" will sign the boot image
84	// like how build/make/core/Makefile does, to get bit-for-bit backwards compatibility. But
85	// we may want to reconsider if it's necessary to have two modes in the future. The default
86	// is "default"
87	Avb_mode *string
88
89	// Name of the partition stored in vbmeta desc. Defaults to the name of this module.
90	Partition_name *string
91
92	// Path to the private key that avbtool will use to sign this filesystem image.
93	// TODO(jiyong): allow apex_key to be specified here
94	Avb_private_key *string `android:"path_device_first"`
95
96	// Hash and signing algorithm for avbtool. Default is SHA256_RSA4096.
97	Avb_algorithm *string
98
99	// The index used to prevent rollback of the image on device.
100	Avb_rollback_index *int64
101
102	// The security patch passed to as the com.android.build.<type>.security_patch avb property.
103	// Replacement for the make variables BOOT_SECURITY_PATCH / INIT_BOOT_SECURITY_PATCH.
104	Security_patch *string
105}
106
107type bootImageType int
108
109const (
110	unsupported bootImageType = iota
111	boot
112	vendorBoot
113	initBoot
114)
115
116func toBootImageType(ctx android.ModuleContext, bootImageType string) bootImageType {
117	switch bootImageType {
118	case "boot":
119		return boot
120	case "vendor_boot":
121		return vendorBoot
122	case "init_boot":
123		return initBoot
124	default:
125		ctx.ModuleErrorf("Unknown boot_image_type %s. Must be one of \"boot\", \"vendor_boot\", or \"init_boot\"", bootImageType)
126	}
127	return unsupported
128}
129
130func (b bootImageType) String() string {
131	switch b {
132	case boot:
133		return "boot"
134	case vendorBoot:
135		return "vendor_boot"
136	case initBoot:
137		return "init_boot"
138	default:
139		panic("unknown boot image type")
140	}
141}
142
143func (b bootImageType) isBoot() bool {
144	return b == boot
145}
146
147func (b bootImageType) isVendorBoot() bool {
148	return b == vendorBoot
149}
150
151func (b bootImageType) isInitBoot() bool {
152	return b == initBoot
153}
154
155// bootimg is the image for the boot partition. It consists of header, kernel, ramdisk, and dtb.
156func BootimgFactory() android.Module {
157	module := &bootimg{}
158	module.AddProperties(&module.properties)
159	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
160	return module
161}
162
163type bootimgDep struct {
164	blueprint.BaseDependencyTag
165	kind string
166}
167
168var bootimgRamdiskDep = bootimgDep{kind: "ramdisk"}
169
170func (b *bootimg) DepsMutator(ctx android.BottomUpMutatorContext) {
171	ramdisk := proptools.String(b.properties.Ramdisk_module)
172	if ramdisk != "" {
173		ctx.AddDependency(ctx.Module(), bootimgRamdiskDep, ramdisk)
174	}
175}
176
177func (b *bootimg) installFileName() string {
178	return proptools.StringDefault(b.properties.Stem, b.BaseModuleName()+".img")
179}
180
181func (b *bootimg) partitionName() string {
182	return proptools.StringDefault(b.properties.Partition_name, b.BaseModuleName())
183}
184
185func (b *bootimg) GenerateAndroidBuildActions(ctx android.ModuleContext) {
186	b.bootImageType = toBootImageType(ctx, proptools.StringDefault(b.properties.Boot_image_type, "boot"))
187	if b.bootImageType == unsupported {
188		return
189	}
190
191	kernelProp := proptools.String(b.properties.Kernel_prebuilt)
192	if b.bootImageType.isVendorBoot() && kernelProp != "" {
193		ctx.PropertyErrorf("kernel_prebuilt", "vendor_boot partition can't have kernel")
194		return
195	}
196	if b.bootImageType.isBoot() && kernelProp == "" {
197		ctx.PropertyErrorf("kernel_prebuilt", "boot partition must have kernel")
198		return
199	}
200	var kernel android.Path
201	if kernelProp != "" {
202		kernel = android.PathForModuleSrc(ctx, kernelProp)
203	}
204
205	unsignedOutput := b.buildBootImage(ctx, kernel)
206
207	output := unsignedOutput
208	if proptools.Bool(b.properties.Use_avb) {
209		// This bootimg module supports 2 modes of avb signing. It is not clear to this author
210		// why there are differences, but one of them is to match the behavior of make-built boot
211		// images.
212		switch proptools.StringDefault(b.properties.Avb_mode, "default") {
213		case "default":
214			output = b.signImage(ctx, unsignedOutput)
215		case "make_legacy":
216			output = b.addAvbFooter(ctx, unsignedOutput, kernel)
217		default:
218			ctx.PropertyErrorf("avb_mode", `Unknown value for avb_mode, expected "default" or "make_legacy", got: %q`, *b.properties.Avb_mode)
219		}
220	}
221
222	b.installDir = android.PathForModuleInstall(ctx, "etc")
223	ctx.InstallFile(b.installDir, b.installFileName(), output)
224
225	ctx.SetOutputFiles([]android.Path{output}, "")
226	b.output = output
227}
228
229func (b *bootimg) buildBootImage(ctx android.ModuleContext, kernel android.Path) android.Path {
230	output := android.PathForModuleOut(ctx, "unsigned", b.installFileName())
231
232	builder := android.NewRuleBuilder(pctx, ctx)
233	cmd := builder.Command().BuiltTool("mkbootimg")
234
235	if kernel != nil {
236		cmd.FlagWithInput("--kernel ", kernel)
237	}
238
239	// These arguments are passed for boot.img and init_boot.img generation
240	if b.bootImageType.isBoot() || b.bootImageType.isInitBoot() {
241		cmd.FlagWithArg("--os_version ", ctx.Config().PlatformVersionLastStable())
242		cmd.FlagWithArg("--os_patch_level ", ctx.Config().PlatformSecurityPatch())
243	}
244
245	dtbName := proptools.String(b.properties.Dtb_prebuilt)
246	if dtbName != "" {
247		dtb := android.PathForModuleSrc(ctx, dtbName)
248		cmd.FlagWithInput("--dtb ", dtb)
249	}
250
251	cmdline := strings.Join(b.properties.Cmdline, " ")
252	if cmdline != "" {
253		flag := "--cmdline "
254		if b.bootImageType.isVendorBoot() {
255			flag = "--vendor_cmdline "
256		}
257		cmd.FlagWithArg(flag, proptools.ShellEscapeIncludingSpaces(cmdline))
258	}
259
260	headerVersion := proptools.String(b.properties.Header_version)
261	if headerVersion == "" {
262		ctx.PropertyErrorf("header_version", "must be set")
263		return output
264	}
265	verNum, err := strconv.Atoi(headerVersion)
266	if err != nil {
267		ctx.PropertyErrorf("header_version", "%q is not a number", headerVersion)
268		return output
269	}
270	if verNum < 3 {
271		ctx.PropertyErrorf("header_version", "must be 3 or higher for vendor_boot")
272		return output
273	}
274	cmd.FlagWithArg("--header_version ", headerVersion)
275
276	ramdiskName := proptools.String(b.properties.Ramdisk_module)
277	if ramdiskName != "" {
278		ramdisk := ctx.GetDirectDepWithTag(ramdiskName, bootimgRamdiskDep)
279		if filesystem, ok := ramdisk.(*filesystem); ok {
280			flag := "--ramdisk "
281			if b.bootImageType.isVendorBoot() {
282				flag = "--vendor_ramdisk "
283			}
284			cmd.FlagWithInput(flag, filesystem.OutputPath())
285		} else {
286			ctx.PropertyErrorf("ramdisk", "%q is not android_filesystem module", ramdisk.Name())
287			return output
288		}
289	}
290
291	bootconfig := proptools.String(b.properties.Bootconfig)
292	if bootconfig != "" {
293		if !b.bootImageType.isVendorBoot() {
294			ctx.PropertyErrorf("bootconfig", "requires vendor_boot: true")
295			return output
296		}
297		if verNum < 4 {
298			ctx.PropertyErrorf("bootconfig", "requires header_version: 4 or later")
299			return output
300		}
301		cmd.FlagWithInput("--vendor_bootconfig ", android.PathForModuleSrc(ctx, bootconfig))
302	}
303
304	// Output flag for boot.img and init_boot.img
305	flag := "--output "
306	if b.bootImageType.isVendorBoot() {
307		flag = "--vendor_boot "
308	}
309	cmd.FlagWithOutput(flag, output)
310
311	if b.properties.Partition_size != nil {
312		assertMaxImageSize(builder, output, *b.properties.Partition_size, proptools.Bool(b.properties.Use_avb))
313	}
314
315	builder.Build("build_bootimg", fmt.Sprintf("Creating %s", b.BaseModuleName()))
316	return output
317}
318
319func (b *bootimg) addAvbFooter(ctx android.ModuleContext, unsignedImage android.Path, kernel android.Path) android.Path {
320	output := android.PathForModuleOut(ctx, b.installFileName())
321	builder := android.NewRuleBuilder(pctx, ctx)
322	builder.Command().Text("cp").Input(unsignedImage).Output(output)
323	cmd := builder.Command().BuiltTool("avbtool").
324		Text("add_hash_footer").
325		FlagWithInput("--image ", output)
326
327	if b.properties.Partition_size != nil {
328		cmd.FlagWithArg("--partition_size ", strconv.FormatInt(*b.properties.Partition_size, 10))
329	} else {
330		cmd.Flag("--dynamic_partition_size")
331	}
332
333	// If you don't provide a salt, avbtool will use random bytes for the salt.
334	// This is bad for determinism (cached builds and diff tests are affected), so instead,
335	// we try to provide a salt. The requirements for a salt are not very clear, one aspect of it
336	// is that if it's unpredictable, attackers trying to change the contents of a partition need
337	// to find a new hash collision every release, because the salt changed.
338	if kernel != nil {
339		cmd.Textf(`--salt $(sha256sum "%s" | cut -d " " -f 1)`, kernel.String())
340		cmd.Implicit(kernel)
341	} else {
342		cmd.Textf(`--salt $(sha256sum "%s" "%s" | cut -d " " -f 1 | tr -d '\n')`, ctx.Config().BuildNumberFile(ctx), ctx.Config().Getenv("BUILD_DATETIME_FILE"))
343		cmd.OrderOnly(ctx.Config().BuildNumberFile(ctx))
344	}
345
346	cmd.FlagWithArg("--partition_name ", b.bootImageType.String())
347
348	if b.properties.Avb_algorithm != nil {
349		cmd.FlagWithArg("--algorithm ", proptools.NinjaAndShellEscape(*b.properties.Avb_algorithm))
350	}
351
352	if b.properties.Avb_private_key != nil {
353		key := android.PathForModuleSrc(ctx, proptools.String(b.properties.Avb_private_key))
354		cmd.FlagWithInput("--key ", key)
355	}
356
357	if !b.bootImageType.isVendorBoot() {
358		cmd.FlagWithArg("--prop ", proptools.NinjaAndShellEscape(fmt.Sprintf(
359			"com.android.build.%s.os_version:%s", b.bootImageType.String(), ctx.Config().PlatformVersionLastStable())))
360	}
361
362	fingerprintFile := ctx.Config().BuildFingerprintFile(ctx)
363	cmd.FlagWithArg("--prop ", fmt.Sprintf("com.android.build.%s.fingerprint:$(cat %s)", b.bootImageType.String(), fingerprintFile.String()))
364	cmd.OrderOnly(fingerprintFile)
365
366	if b.properties.Security_patch != nil {
367		cmd.FlagWithArg("--prop ", proptools.NinjaAndShellEscape(fmt.Sprintf(
368			"com.android.build.%s.security_patch:%s", b.bootImageType.String(), *b.properties.Security_patch)))
369	}
370
371	if b.properties.Avb_rollback_index != nil {
372		cmd.FlagWithArg("--rollback_index ", strconv.FormatInt(*b.properties.Avb_rollback_index, 10))
373	}
374
375	builder.Build("add_avb_footer", fmt.Sprintf("Adding avb footer to %s", b.BaseModuleName()))
376	return output
377}
378
379func (b *bootimg) signImage(ctx android.ModuleContext, unsignedImage android.Path) android.Path {
380	propFile, toolDeps := b.buildPropFile(ctx)
381
382	output := android.PathForModuleOut(ctx, b.installFileName())
383	builder := android.NewRuleBuilder(pctx, ctx)
384	builder.Command().Text("cp").Input(unsignedImage).Output(output)
385	builder.Command().BuiltTool("verity_utils").
386		Input(propFile).
387		Implicits(toolDeps).
388		Output(output)
389
390	builder.Build("sign_bootimg", fmt.Sprintf("Signing %s", b.BaseModuleName()))
391	return output
392}
393
394// Calculates avb_salt from some input for deterministic output.
395func (b *bootimg) salt() string {
396	var input []string
397	input = append(input, b.properties.Cmdline...)
398	input = append(input, proptools.StringDefault(b.properties.Partition_name, b.Name()))
399	input = append(input, proptools.String(b.properties.Header_version))
400	return sha1sum(input)
401}
402
403func (b *bootimg) buildPropFile(ctx android.ModuleContext) (android.Path, android.Paths) {
404	var sb strings.Builder
405	var deps android.Paths
406	addStr := func(name string, value string) {
407		fmt.Fprintf(&sb, "%s=%s\n", name, value)
408	}
409	addPath := func(name string, path android.Path) {
410		addStr(name, path.String())
411		deps = append(deps, path)
412	}
413
414	addStr("avb_hash_enable", "true")
415	addPath("avb_avbtool", ctx.Config().HostToolPath(ctx, "avbtool"))
416	algorithm := proptools.StringDefault(b.properties.Avb_algorithm, "SHA256_RSA4096")
417	addStr("avb_algorithm", algorithm)
418	key := android.PathForModuleSrc(ctx, proptools.String(b.properties.Avb_private_key))
419	addPath("avb_key_path", key)
420	addStr("avb_add_hash_footer_args", "") // TODO(jiyong): add --rollback_index
421	partitionName := proptools.StringDefault(b.properties.Partition_name, b.Name())
422	addStr("partition_name", partitionName)
423	addStr("avb_salt", b.salt())
424
425	propFile := android.PathForModuleOut(ctx, "prop")
426	android.WriteFileRule(ctx, propFile, sb.String())
427	return propFile, deps
428}
429
430var _ android.AndroidMkEntriesProvider = (*bootimg)(nil)
431
432// Implements android.AndroidMkEntriesProvider
433func (b *bootimg) AndroidMkEntries() []android.AndroidMkEntries {
434	return []android.AndroidMkEntries{android.AndroidMkEntries{
435		Class:      "ETC",
436		OutputFile: android.OptionalPathForPath(b.output),
437		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
438			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
439				entries.SetString("LOCAL_MODULE_PATH", b.installDir.String())
440				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", b.installFileName())
441			},
442		},
443	}}
444}
445
446var _ Filesystem = (*bootimg)(nil)
447
448func (b *bootimg) OutputPath() android.Path {
449	return b.output
450}
451
452func (b *bootimg) SignedOutputPath() android.Path {
453	if proptools.Bool(b.properties.Use_avb) {
454		return b.OutputPath()
455	}
456	return nil
457}
458