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