• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2024 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	"path/filepath"
20	"strings"
21
22	"android/soong/android"
23
24	"github.com/google/blueprint"
25	"github.com/google/blueprint/proptools"
26)
27
28func init() {
29	pctx.HostBinToolVariable("fsverity_metadata_generator", "fsverity_metadata_generator")
30	pctx.HostBinToolVariable("fsverity_manifest_generator", "fsverity_manifest_generator")
31	pctx.HostBinToolVariable("fsverity", "fsverity")
32}
33
34var (
35	buildFsverityMeta = pctx.AndroidStaticRule("build_fsverity_meta", blueprint.RuleParams{
36		Command:     `$fsverity_metadata_generator --fsverity-path $fsverity --signature none --hash-alg sha256 --output $out $in`,
37		CommandDeps: []string{"$fsverity_metadata_generator", "$fsverity"},
38	})
39	buildFsverityManifest = pctx.AndroidStaticRule("build_fsverity_manifest", blueprint.RuleParams{
40		Command:     `$fsverity_manifest_generator --fsverity-path $fsverity --output $out @$in`,
41		CommandDeps: []string{"$fsverity_manifest_generator", "$fsverity"},
42	})
43)
44
45type fsverityProperties struct {
46	// Patterns of files for fsverity metadata generation.  For each matched file, a .fsv_meta file
47	// will be generated and included to the filesystem image.
48	// etc/security/fsverity/BuildManifest.apk will also be generated which contains information
49	// about generated .fsv_meta files.
50	Inputs proptools.Configurable[[]string]
51
52	// APK libraries to link against, for etc/security/fsverity/BuildManifest.apk
53	Libs proptools.Configurable[[]string] `android:"path"`
54}
55
56// Mapping of a given fsverity file, which may be a real file or a symlink, and the on-device
57// path it should have relative to the filesystem root.
58type fsveritySrcDest struct {
59	src  android.Path
60	dest string
61}
62
63func (f *filesystem) writeManifestGeneratorListFile(
64	ctx android.ModuleContext,
65	outputPath android.WritablePath,
66	matchedFiles []fsveritySrcDest,
67	rootDir android.OutputPath,
68	rebasedDir android.OutputPath,
69) []android.Path {
70	prefix, err := filepath.Rel(rootDir.String(), rebasedDir.String())
71	if err != nil {
72		panic("rebasedDir should be relative to rootDir")
73	}
74	if prefix == "." {
75		prefix = ""
76	}
77	if f.PartitionType() == "system_ext" {
78		// Use the equivalent of $PRODUCT_OUT as the base dir.
79		// This ensures that the paths in build_manifest.pb contain on-device paths
80		// e.g. system_ext/framework/javalib.jar
81		// and not framework/javalib.jar.
82		//
83		// Although base-dir is outside the rootdir provided for packaging, this action
84		// is hermetic since it uses `manifestGeneratorListPath` to filter the files to be written to build_manifest.pb
85		prefix = "system_ext"
86	}
87
88	var deps []android.Path
89	var buf strings.Builder
90	for _, spec := range matchedFiles {
91		src := spec.src.String()
92		dst := filepath.Join(prefix, spec.dest)
93		if strings.Contains(src, ",") {
94			ctx.ModuleErrorf("Path cannot contain a comma: %s", src)
95		}
96		if strings.Contains(dst, ",") {
97			ctx.ModuleErrorf("Path cannot contain a comma: %s", dst)
98		}
99		buf.WriteString(src)
100		buf.WriteString(",")
101		buf.WriteString(dst)
102		buf.WriteString("\n")
103		deps = append(deps, spec.src)
104	}
105	android.WriteFileRuleVerbatim(ctx, outputPath, buf.String())
106	return deps
107}
108
109func (f *filesystem) buildFsverityMetadataFiles(
110	ctx android.ModuleContext,
111	builder *android.RuleBuilder,
112	specs map[string]android.PackagingSpec,
113	rootDir android.OutputPath,
114	rebasedDir android.OutputPath,
115	fullInstallPaths *[]FullInstallPathInfo,
116) {
117	match := func(path string) bool {
118		for _, pattern := range f.properties.Fsverity.Inputs.GetOrDefault(ctx, nil) {
119			if matched, err := filepath.Match(pattern, path); matched {
120				return true
121			} else if err != nil {
122				ctx.PropertyErrorf("fsverity.inputs", "bad pattern %q", pattern)
123				return false
124			}
125		}
126		return false
127	}
128
129	var matchedFiles []android.PackagingSpec
130	var matchedSymlinks []android.PackagingSpec
131	for _, relPath := range android.SortedKeys(specs) {
132		if match(relPath) {
133			spec := specs[relPath]
134			if spec.SrcPath() != nil {
135				matchedFiles = append(matchedFiles, spec)
136			} else if spec.SymlinkTarget() != "" {
137				matchedSymlinks = append(matchedSymlinks, spec)
138			} else {
139				ctx.ModuleErrorf("Expected a file or symlink for fsverity packaging spec")
140			}
141		}
142	}
143
144	if len(matchedFiles) == 0 && len(matchedSymlinks) == 0 {
145		return
146	}
147
148	// STEP 1: generate .fsv_meta
149	var fsverityFileSpecs []fsveritySrcDest
150	for _, spec := range matchedFiles {
151		rel := spec.RelPathInPackage() + ".fsv_meta"
152		outPath := android.PathForModuleOut(ctx, "fsverity/meta_files", rel)
153		destPath := rebasedDir.Join(ctx, rel)
154		// srcPath is copied by CopySpecsToDir()
155		ctx.Build(pctx, android.BuildParams{
156			Rule:   buildFsverityMeta,
157			Input:  spec.SrcPath(),
158			Output: outPath,
159		})
160		builder.Command().Textf("cp").Input(outPath).Output(destPath)
161		f.appendToEntry(ctx, destPath)
162		*fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{
163			SourcePath:      destPath,
164			FullInstallPath: android.PathForModuleInPartitionInstall(ctx, f.PartitionType(), rel),
165		})
166		fsverityFileSpecs = append(fsverityFileSpecs, fsveritySrcDest{
167			src:  spec.SrcPath(),
168			dest: spec.RelPathInPackage(),
169		})
170	}
171	for _, spec := range matchedSymlinks {
172		rel := spec.RelPathInPackage() + ".fsv_meta"
173		outPath := android.PathForModuleOut(ctx, "fsverity/meta_files", rel)
174		destPath := rebasedDir.Join(ctx, rel)
175		target := spec.SymlinkTarget() + ".fsv_meta"
176		ctx.Build(pctx, android.BuildParams{
177			Rule:   android.Symlink,
178			Output: outPath,
179			Args: map[string]string{
180				"fromPath": target,
181			},
182		})
183		builder.Command().
184			Textf("cp").
185			Flag(ctx.Config().CpPreserveSymlinksFlags()).
186			Input(outPath).
187			Output(destPath)
188		f.appendToEntry(ctx, destPath)
189		*fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{
190			SymlinkTarget:   target,
191			FullInstallPath: android.PathForModuleInPartitionInstall(ctx, f.PartitionType(), rel),
192		})
193		// The fsverity manifest tool needs to actually look at the symlink. But symlink
194		// packagingSpecs are not actually created on disk, at least until the staging dir is
195		// built for the partition. Create a fake one now so the tool can see it.
196		realizedSymlink := android.PathForModuleOut(ctx, "fsverity/realized_symlinks", spec.RelPathInPackage())
197		ctx.Build(pctx, android.BuildParams{
198			Rule:   android.Symlink,
199			Output: realizedSymlink,
200			Args: map[string]string{
201				"fromPath": spec.SymlinkTarget(),
202			},
203		})
204		fsverityFileSpecs = append(fsverityFileSpecs, fsveritySrcDest{
205			src:  realizedSymlink,
206			dest: spec.RelPathInPackage(),
207		})
208	}
209
210	// STEP 2: generate signed BuildManifest.apk
211	// STEP 2-1: generate build_manifest.pb
212	manifestGeneratorListPath := android.PathForModuleOut(ctx, "fsverity/fsverity_manifest.list")
213	manifestDeps := f.writeManifestGeneratorListFile(ctx, manifestGeneratorListPath, fsverityFileSpecs, rootDir, rebasedDir)
214	manifestPbPath := android.PathForModuleOut(ctx, "fsverity/build_manifest.pb")
215	ctx.Build(pctx, android.BuildParams{
216		Rule:      buildFsverityManifest,
217		Input:     manifestGeneratorListPath,
218		Implicits: manifestDeps,
219		Output:    manifestPbPath,
220	})
221
222	// STEP 2-2: generate BuildManifest.apk (unsigned)
223	apkNameSuffix := ""
224	if f.PartitionType() == "system_ext" {
225		//https://source.corp.google.com/h/googleplex-android/platform/build/+/e392d2b486c2d4187b20a72b1c67cc737ecbcca5:core/Makefile;l=3410;drc=ea8f34bc1d6e63656b4ec32f2391e9d54b3ebb6b;bpv=1;bpt=0
226		apkNameSuffix = "SystemExt"
227	}
228	apkPath := android.PathForModuleOut(ctx, "fsverity", fmt.Sprintf("BuildManifest%s.apk", apkNameSuffix))
229	idsigPath := android.PathForModuleOut(ctx, "fsverity", fmt.Sprintf("BuildManifest%s.apk.idsig", apkNameSuffix))
230	manifestTemplatePath := android.PathForSource(ctx, "system/security/fsverity/AndroidManifest.xml")
231	libs := android.PathsForModuleSrc(ctx, f.properties.Fsverity.Libs.GetOrDefault(ctx, nil))
232
233	minSdkVersion := ctx.Config().PlatformSdkCodename()
234	if minSdkVersion == "REL" {
235		minSdkVersion = ctx.Config().PlatformSdkVersion().String()
236	}
237
238	apkBuilder := android.NewRuleBuilder(pctx, ctx)
239
240	// aapt2 doesn't support adding individual asset files. Create a temp directory to hold asset
241	// files and pass it to aapt2.
242	tmpAssetDir := android.PathForModuleOut(ctx, "fsverity/tmp_asset_dir")
243	stagedManifestPbPath := tmpAssetDir.Join(ctx, "build_manifest.pb")
244	apkBuilder.Command().
245		Text("rm -rf").Text(tmpAssetDir.String()).
246		Text("&&").
247		Text("mkdir -p").Text(tmpAssetDir.String())
248	apkBuilder.Command().Text("cp").Input(manifestPbPath).Output(stagedManifestPbPath)
249
250	unsignedApkCommand := apkBuilder.Command().
251		BuiltTool("aapt2").
252		Text("link").
253		FlagWithOutput("-o ", apkPath).
254		FlagWithArg("-A ", tmpAssetDir.String()).Implicit(stagedManifestPbPath)
255	for _, lib := range libs {
256		unsignedApkCommand.FlagWithInput("-I ", lib)
257	}
258	unsignedApkCommand.
259		FlagWithArg("--min-sdk-version ", minSdkVersion).
260		FlagWithArg("--version-code ", ctx.Config().PlatformSdkVersion().String()).
261		FlagWithArg("--version-name ", ctx.Config().AppsDefaultVersionName()).
262		FlagWithInput("--manifest ", manifestTemplatePath).
263		Text(" --rename-manifest-package com.android.security.fsverity_metadata." + f.partitionName())
264
265	// STEP 2-3: sign BuildManifest.apk
266	pemPath, keyPath := ctx.Config().DefaultAppCertificate(ctx)
267	apkBuilder.Command().
268		BuiltTool("apksigner").
269		Text("sign").
270		FlagWithArg("--in ", apkPath.String()).
271		FlagWithInput("--cert ", pemPath).
272		FlagWithInput("--key ", keyPath).
273		ImplicitOutput(idsigPath)
274	apkBuilder.Build(fmt.Sprintf("%s_fsverity_apk", ctx.ModuleName()), "build fsverity apk")
275
276	// STEP 2-4: Install the apk into the staging directory
277	installedApkPath := rebasedDir.Join(ctx, "etc", "security", "fsverity", fmt.Sprintf("BuildManifest%s.apk", apkNameSuffix))
278	installedIdsigPath := rebasedDir.Join(ctx, "etc", "security", "fsverity", fmt.Sprintf("BuildManifest%s.apk.idsig", apkNameSuffix))
279	builder.Command().Text("mkdir -p").Text(filepath.Dir(installedApkPath.String()))
280	builder.Command().Text("cp").Input(apkPath).Text(installedApkPath.String())
281	builder.Command().Text("cp").Input(idsigPath).Text(installedIdsigPath.String())
282
283	*fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{
284		SourcePath:      apkPath,
285		FullInstallPath: android.PathForModuleInPartitionInstall(ctx, f.PartitionType(), fmt.Sprintf("etc/security/fsverity/BuildManifest%s.apk", apkNameSuffix)),
286	})
287
288	f.appendToEntry(ctx, installedApkPath)
289
290	*fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{
291		SourcePath:      idsigPath,
292		FullInstallPath: android.PathForModuleInPartitionInstall(ctx, f.PartitionType(), fmt.Sprintf("etc/security/fsverity/BuildManifest%s.apk.idsig", apkNameSuffix)),
293	})
294
295	f.appendToEntry(ctx, installedIdsigPath)
296}
297