| // Copyright (C) 2024 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package filesystem |
| |
| import ( |
| "fmt" |
| "path/filepath" |
| "strings" |
| |
| "android/soong/android" |
| |
| "github.com/google/blueprint" |
| "github.com/google/blueprint/proptools" |
| ) |
| |
| func init() { |
| pctx.HostBinToolVariable("fsverity_metadata_generator", "fsverity_metadata_generator") |
| pctx.HostBinToolVariable("fsverity_manifest_generator", "fsverity_manifest_generator") |
| pctx.HostBinToolVariable("fsverity", "fsverity") |
| } |
| |
| var ( |
| buildFsverityMeta = pctx.AndroidStaticRule("build_fsverity_meta", blueprint.RuleParams{ |
| Command: `$fsverity_metadata_generator --fsverity-path $fsverity --signature none --hash-alg sha256 --output $out $in`, |
| CommandDeps: []string{"$fsverity_metadata_generator", "$fsverity"}, |
| }) |
| buildFsverityManifest = pctx.AndroidStaticRule("build_fsverity_manifest", blueprint.RuleParams{ |
| Command: `$fsverity_manifest_generator --fsverity-path $fsverity --output $out @$in`, |
| CommandDeps: []string{"$fsverity_manifest_generator", "$fsverity"}, |
| }) |
| ) |
| |
| type fsverityProperties struct { |
| // Patterns of files for fsverity metadata generation. For each matched file, a .fsv_meta file |
| // will be generated and included to the filesystem image. |
| // etc/security/fsverity/BuildManifest.apk will also be generated which contains information |
| // about generated .fsv_meta files. |
| Inputs proptools.Configurable[[]string] |
| |
| // APK libraries to link against, for etc/security/fsverity/BuildManifest.apk |
| Libs proptools.Configurable[[]string] `android:"path"` |
| } |
| |
| // Mapping of a given fsverity file, which may be a real file or a symlink, and the on-device |
| // path it should have relative to the filesystem root. |
| type fsveritySrcDest struct { |
| src android.Path |
| dest string |
| } |
| |
| func (f *filesystem) writeManifestGeneratorListFile( |
| ctx android.ModuleContext, |
| outputPath android.WritablePath, |
| matchedFiles []fsveritySrcDest, |
| rootDir android.OutputPath, |
| rebasedDir android.OutputPath, |
| ) []android.Path { |
| prefix, err := filepath.Rel(rootDir.String(), rebasedDir.String()) |
| if err != nil { |
| panic("rebasedDir should be relative to rootDir") |
| } |
| if prefix == "." { |
| prefix = "" |
| } |
| if f.PartitionType() == "system_ext" { |
| // Use the equivalent of $PRODUCT_OUT as the base dir. |
| // This ensures that the paths in build_manifest.pb contain on-device paths |
| // e.g. system_ext/framework/javalib.jar |
| // and not framework/javalib.jar. |
| // |
| // Although base-dir is outside the rootdir provided for packaging, this action |
| // is hermetic since it uses `manifestGeneratorListPath` to filter the files to be written to build_manifest.pb |
| prefix = "system_ext" |
| } |
| |
| var deps []android.Path |
| var buf strings.Builder |
| for _, spec := range matchedFiles { |
| src := spec.src.String() |
| dst := filepath.Join(prefix, spec.dest) |
| if strings.Contains(src, ",") { |
| ctx.ModuleErrorf("Path cannot contain a comma: %s", src) |
| } |
| if strings.Contains(dst, ",") { |
| ctx.ModuleErrorf("Path cannot contain a comma: %s", dst) |
| } |
| buf.WriteString(src) |
| buf.WriteString(",") |
| buf.WriteString(dst) |
| buf.WriteString("\n") |
| deps = append(deps, spec.src) |
| } |
| android.WriteFileRuleVerbatim(ctx, outputPath, buf.String()) |
| return deps |
| } |
| |
| func (f *filesystem) buildFsverityMetadataFiles( |
| ctx android.ModuleContext, |
| builder *android.RuleBuilder, |
| specs map[string]android.PackagingSpec, |
| rootDir android.OutputPath, |
| rebasedDir android.OutputPath, |
| fullInstallPaths *[]FullInstallPathInfo, |
| platformGeneratedFiles *[]string, |
| ) { |
| match := func(path string) bool { |
| for _, pattern := range f.properties.Fsverity.Inputs.GetOrDefault(ctx, nil) { |
| if matched, err := filepath.Match(pattern, path); matched { |
| return true |
| } else if err != nil { |
| ctx.PropertyErrorf("fsverity.inputs", "bad pattern %q", pattern) |
| return false |
| } |
| } |
| return false |
| } |
| |
| var matchedFiles []android.PackagingSpec |
| var matchedSymlinks []android.PackagingSpec |
| for _, relPath := range android.SortedKeys(specs) { |
| if match(relPath) { |
| spec := specs[relPath] |
| if spec.SrcPath() != nil { |
| matchedFiles = append(matchedFiles, spec) |
| } else if spec.SymlinkTarget() != "" { |
| matchedSymlinks = append(matchedSymlinks, spec) |
| } else { |
| ctx.ModuleErrorf("Expected a file or symlink for fsverity packaging spec") |
| } |
| } |
| } |
| |
| if len(matchedFiles) == 0 && len(matchedSymlinks) == 0 { |
| return |
| } |
| |
| // STEP 1: generate .fsv_meta |
| var fsverityFileSpecs []fsveritySrcDest |
| for _, spec := range matchedFiles { |
| rel := spec.RelPathInPackage() + ".fsv_meta" |
| outPath := android.PathForModuleOut(ctx, "fsverity/meta_files", rel) |
| destPath := rebasedDir.Join(ctx, rel) |
| // srcPath is copied by CopySpecsToDir() |
| ctx.Build(pctx, android.BuildParams{ |
| Rule: buildFsverityMeta, |
| Input: spec.SrcPath(), |
| Output: outPath, |
| }) |
| builder.Command().Textf("cp").Input(outPath).Output(destPath) |
| f.appendToEntry(ctx, destPath) |
| *fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{ |
| SourcePath: destPath, |
| FullInstallPath: android.PathForModuleInPartitionInstall(ctx, f.PartitionType(), rel), |
| }) |
| fsverityFileSpecs = append(fsverityFileSpecs, fsveritySrcDest{ |
| src: spec.SrcPath(), |
| dest: spec.RelPathInPackage(), |
| }) |
| } |
| for _, spec := range matchedSymlinks { |
| rel := spec.RelPathInPackage() + ".fsv_meta" |
| outPath := android.PathForModuleOut(ctx, "fsverity/meta_files", rel) |
| destPath := rebasedDir.Join(ctx, rel) |
| target := spec.SymlinkTarget() + ".fsv_meta" |
| ctx.Build(pctx, android.BuildParams{ |
| Rule: android.Symlink, |
| Output: outPath, |
| Args: map[string]string{ |
| "fromPath": target, |
| }, |
| }) |
| builder.Command(). |
| Textf("cp"). |
| Flag(ctx.Config().CpPreserveSymlinksFlags()). |
| Input(outPath). |
| Output(destPath) |
| f.appendToEntry(ctx, destPath) |
| *fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{ |
| SymlinkTarget: target, |
| FullInstallPath: android.PathForModuleInPartitionInstall(ctx, f.PartitionType(), rel), |
| }) |
| // The fsverity manifest tool needs to actually look at the symlink. But symlink |
| // packagingSpecs are not actually created on disk, at least until the staging dir is |
| // built for the partition. Create a fake one now so the tool can see it. |
| realizedSymlink := android.PathForModuleOut(ctx, "fsverity/realized_symlinks", spec.RelPathInPackage()) |
| ctx.Build(pctx, android.BuildParams{ |
| Rule: android.Symlink, |
| Output: realizedSymlink, |
| Args: map[string]string{ |
| "fromPath": spec.SymlinkTarget(), |
| }, |
| }) |
| fsverityFileSpecs = append(fsverityFileSpecs, fsveritySrcDest{ |
| src: realizedSymlink, |
| dest: spec.RelPathInPackage(), |
| }) |
| } |
| |
| // STEP 2: generate signed BuildManifest.apk |
| // STEP 2-1: generate build_manifest.pb |
| manifestGeneratorListPath := android.PathForModuleOut(ctx, "fsverity/fsverity_manifest.list") |
| manifestDeps := f.writeManifestGeneratorListFile(ctx, manifestGeneratorListPath, fsverityFileSpecs, rootDir, rebasedDir) |
| manifestPbPath := android.PathForModuleOut(ctx, "fsverity/build_manifest.pb") |
| ctx.Build(pctx, android.BuildParams{ |
| Rule: buildFsverityManifest, |
| Input: manifestGeneratorListPath, |
| Implicits: manifestDeps, |
| Output: manifestPbPath, |
| }) |
| |
| // STEP 2-2: generate BuildManifest.apk (unsigned) |
| apkNameSuffix := "" |
| if f.PartitionType() == "system_ext" { |
| //https://source.corp.google.com/h/googleplex-android/platform/build/+/e392d2b486c2d4187b20a72b1c67cc737ecbcca5:core/Makefile;l=3410;drc=ea8f34bc1d6e63656b4ec32f2391e9d54b3ebb6b;bpv=1;bpt=0 |
| apkNameSuffix = "SystemExt" |
| } |
| apkPath := android.PathForModuleOut(ctx, "fsverity", fmt.Sprintf("BuildManifest%s.apk", apkNameSuffix)) |
| idsigPath := android.PathForModuleOut(ctx, "fsverity", fmt.Sprintf("BuildManifest%s.apk.idsig", apkNameSuffix)) |
| manifestTemplatePath := android.PathForSource(ctx, "system/security/fsverity/AndroidManifest.xml") |
| libs := android.PathsForModuleSrc(ctx, f.properties.Fsverity.Libs.GetOrDefault(ctx, nil)) |
| |
| minSdkVersion := ctx.Config().PlatformSdkCodename() |
| if minSdkVersion == "REL" { |
| minSdkVersion = ctx.Config().PlatformSdkVersion().String() |
| } |
| |
| apkBuilder := android.NewRuleBuilder(pctx, ctx) |
| |
| // aapt2 doesn't support adding individual asset files. Create a temp directory to hold asset |
| // files and pass it to aapt2. |
| tmpAssetDir := android.PathForModuleOut(ctx, "fsverity/tmp_asset_dir") |
| stagedManifestPbPath := tmpAssetDir.Join(ctx, "build_manifest.pb") |
| apkBuilder.Command(). |
| Text("rm -rf").Text(tmpAssetDir.String()). |
| Text("&&"). |
| Text("mkdir -p").Text(tmpAssetDir.String()) |
| apkBuilder.Command().Text("cp").Input(manifestPbPath).Output(stagedManifestPbPath) |
| |
| unsignedApkCommand := apkBuilder.Command(). |
| BuiltTool("aapt2"). |
| Text("link"). |
| FlagWithOutput("-o ", apkPath). |
| FlagWithArg("-A ", tmpAssetDir.String()).Implicit(stagedManifestPbPath) |
| for _, lib := range libs { |
| unsignedApkCommand.FlagWithInput("-I ", lib) |
| } |
| unsignedApkCommand. |
| FlagWithArg("--min-sdk-version ", minSdkVersion). |
| FlagWithArg("--version-code ", ctx.Config().PlatformSdkVersion().String()). |
| FlagWithArg("--version-name ", ctx.Config().AppsDefaultVersionName()). |
| FlagWithInput("--manifest ", manifestTemplatePath). |
| Text(" --rename-manifest-package com.android.security.fsverity_metadata." + f.partitionName()) |
| |
| // STEP 2-3: sign BuildManifest.apk |
| pemPath, keyPath := ctx.Config().DefaultAppCertificate(ctx) |
| apkBuilder.Command(). |
| BuiltTool("apksigner"). |
| Text("sign"). |
| FlagWithArg("--in ", apkPath.String()). |
| FlagWithInput("--cert ", pemPath). |
| FlagWithInput("--key ", keyPath). |
| ImplicitOutput(idsigPath) |
| apkBuilder.Build(fmt.Sprintf("%s_fsverity_apk", ctx.ModuleName()), "build fsverity apk") |
| |
| // STEP 2-4: Install the apk into the staging directory |
| installedApkPath := rebasedDir.Join(ctx, "etc", "security", "fsverity", fmt.Sprintf("BuildManifest%s.apk", apkNameSuffix)) |
| installedIdsigPath := rebasedDir.Join(ctx, "etc", "security", "fsverity", fmt.Sprintf("BuildManifest%s.apk.idsig", apkNameSuffix)) |
| builder.Command().Text("mkdir -p").Text(filepath.Dir(installedApkPath.String())) |
| builder.Command().Text("cp").Input(apkPath).Text(installedApkPath.String()) |
| builder.Command().Text("cp").Input(idsigPath).Text(installedIdsigPath.String()) |
| |
| apkFullInstallPath := android.PathForModuleInPartitionInstall(ctx, f.PartitionType(), fmt.Sprintf("etc/security/fsverity/BuildManifest%s.apk", apkNameSuffix)) |
| *fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{ |
| SourcePath: apkPath, |
| FullInstallPath: apkFullInstallPath, |
| }) |
| |
| f.appendToEntry(ctx, installedApkPath) |
| |
| idsigFullInstallPath := android.PathForModuleInPartitionInstall(ctx, f.PartitionType(), fmt.Sprintf("etc/security/fsverity/BuildManifest%s.apk.idsig", apkNameSuffix)) |
| *fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{ |
| SourcePath: idsigPath, |
| FullInstallPath: idsigFullInstallPath, |
| }) |
| |
| f.appendToEntry(ctx, installedIdsigPath) |
| *platformGeneratedFiles = append(*platformGeneratedFiles, apkFullInstallPath.String(), idsigFullInstallPath.String()) |
| } |