Delete legacy fs-verity support

The "legacy" fs-verity was introduced in P in Pixel 3 kernel. During
fs-verity upstream to Linux, the API has changed. During Pixel 4 / Q
development, fs-verity was upstreamed to Linux kernel. By setting
ro.apk_verity.mode = 2, device vendors such as Pixel 4 can opt in to
enable the support. The feature has become mandatory for new devices
shipped with R.

Since Pixel 3 family is no longer supported, it's time to remove the
dead code.

Bug: 120629632
Test: m
Test: TH
Change-Id: I6dacd9bbd38b502ee510ff12970e76342d7b72dd
Merged-In: I6dacd9bbd38b502ee510ff12970e76342d7b72dd
This commit is contained in:
Victor Hsieh
2022-02-01 23:08:53 +00:00
parent 86e1a45b56
commit e97a8700b0
8 changed files with 25 additions and 410 deletions

View File

@@ -407,20 +407,6 @@ public class ApkSignatureSchemeV2Verifier {
}
}
static byte[] generateApkVerityRootHash(String apkPath)
throws IOException, SignatureNotFoundException, DigestException,
NoSuchAlgorithmException {
try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
SignatureInfo signatureInfo = findSignature(apk);
VerifiedSigner vSigner = verify(apk, false);
if (vSigner.verityRootHash == null) {
return null;
}
return VerityBuilder.generateApkVerityRootHash(
apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo);
}
}
/**
* Verified APK Signature Scheme v2 signer.
*

View File

@@ -444,20 +444,6 @@ public class ApkSignatureSchemeV3Verifier {
}
}
static byte[] generateApkVerityRootHash(String apkPath)
throws NoSuchAlgorithmException, DigestException, IOException,
SignatureNotFoundException {
try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
SignatureInfo signatureInfo = findSignature(apk);
VerifiedSigner vSigner = verify(apk, false);
if (vSigner.verityRootHash == null) {
return null;
}
return VerityBuilder.generateApkVerityRootHash(
apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo);
}
}
/**
* Verified APK Signature Scheme v3 signer, including the proof of rotation structure.
*

View File

@@ -550,27 +550,6 @@ public class ApkSignatureVerifier {
return ApkSignatureSchemeV2Verifier.generateApkVerity(apkPath, bufferFactory);
}
/**
* Generates the FSVerity root hash from FSVerity header, extensions and Merkle tree root hash
* in Signing Block.
*
* @return FSverity root hash
*/
public static byte[] generateApkVerityRootHash(String apkPath)
throws NoSuchAlgorithmException, DigestException, IOException {
// first try v3
try {
return ApkSignatureSchemeV3Verifier.generateApkVerityRootHash(apkPath);
} catch (SignatureNotFoundException e) {
// try older version
}
try {
return ApkSignatureSchemeV2Verifier.generateApkVerityRootHash(apkPath);
} catch (SignatureNotFoundException e) {
return null;
}
}
/**
* Extended signing details.
* @hide for internal use only.

View File

@@ -143,25 +143,6 @@ public abstract class VerityBuilder {
return generateFsVerityTreeInternal(apk, salt, levelOffset, tree);
}
}
/**
* Calculates the apk-verity root hash for integrity measurement. This needs to be consistent
* to what kernel returns.
*/
@NonNull
static byte[] generateApkVerityRootHash(@NonNull RandomAccessFile apk,
@NonNull ByteBuffer apkDigest, @NonNull SignatureInfo signatureInfo)
throws NoSuchAlgorithmException, DigestException, IOException {
assertSigningBlockAlignedAndHasFullPages(signatureInfo);
ByteBuffer footer = ByteBuffer.allocate(CHUNK_SIZE_BYTES).order(ByteOrder.LITTLE_ENDIAN);
generateApkVerityFooter(apk, signatureInfo, footer);
footer.flip();
MessageDigest md = MessageDigest.getInstance(JCA_DIGEST_ALGORITHM);
md.update(footer);
md.update(apkDigest);
return md.digest();
}
/**
* Generates the apk-verity header and hash tree to be used by kernel for the given apk. This

View File

@@ -18,28 +18,15 @@ package com.android.internal.security;
import android.annotation.NonNull;
import android.os.Build;
import android.os.SharedMemory;
import android.os.SystemProperties;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.util.Pair;
import android.util.Slog;
import android.util.apk.ApkSignatureVerifier;
import android.util.apk.ByteBufferFactory;
import android.util.apk.SignatureNotFoundException;
import libcore.util.HexEncoding;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.DigestException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
/** Provides fsverity related operations. */
public abstract class VerityUtils {
@@ -57,8 +44,6 @@ public abstract class VerityUtils {
/** SHA256 hash size. */
private static final int HASH_SIZE_BYTES = 32;
private static final boolean DEBUG = false;
public static boolean isFsVeritySupported() {
return Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.R
|| SystemProperties.getInt("ro.apk_verity.mode", 0) == 2;
@@ -123,204 +108,4 @@ public abstract class VerityUtils {
private static native int measureFsverityNative(@NonNull String filePath,
@NonNull byte[] digest);
private static native int statxForFsverityNative(@NonNull String filePath);
/**
* Generates legacy Merkle tree and fs-verity metadata with Signing Block skipped.
*
* @deprecated This is only used for previous fs-verity implementation, and should never be used
* on new devices.
* @return {@code SetupResult} that contains the result code, and when success, the
* {@code FileDescriptor} to read all the data from.
*/
@Deprecated
public static SetupResult generateApkVeritySetupData(@NonNull String apkPath) {
if (DEBUG) {
Slog.d(TAG, "Trying to install legacy apk verity to " + apkPath);
}
SharedMemory shm = null;
try {
final byte[] signedVerityHash = ApkSignatureVerifier.getVerityRootHash(apkPath);
if (signedVerityHash == null) {
if (DEBUG) {
Slog.d(TAG, "Skip verity tree generation since there is no signed root hash");
}
return SetupResult.skipped();
}
Pair<SharedMemory, Integer> result =
generateFsVerityIntoSharedMemory(apkPath, signedVerityHash);
shm = result.first;
int contentSize = result.second;
FileDescriptor rfd = shm.getFileDescriptor();
if (rfd == null || !rfd.valid()) {
return SetupResult.failed();
}
return SetupResult.ok(Os.dup(rfd), contentSize);
} catch (IOException | SecurityException | DigestException | NoSuchAlgorithmException
| SignatureNotFoundException | ErrnoException e) {
Slog.e(TAG, "Failed to set up apk verity: ", e);
return SetupResult.failed();
} finally {
if (shm != null) {
shm.close();
}
}
}
/**
* {@see ApkSignatureVerifier#generateApkVerityRootHash(String)}.
* @deprecated This is only used for previous fs-verity implementation, and should never be used
* on new devices.
*/
@Deprecated
public static byte[] generateApkVerityRootHash(@NonNull String apkPath)
throws NoSuchAlgorithmException, DigestException, IOException {
return ApkSignatureVerifier.generateApkVerityRootHash(apkPath);
}
/**
* {@see ApkSignatureVerifier#getVerityRootHash(String)}.
* @deprecated This is only used for previous fs-verity implementation, and should never be used
* on new devices.
*/
@Deprecated
public static byte[] getVerityRootHash(@NonNull String apkPath)
throws IOException, SignatureNotFoundException {
return ApkSignatureVerifier.getVerityRootHash(apkPath);
}
/**
* Returns a pair of {@code SharedMemory} and {@code Integer}. The {@code SharedMemory} contains
* Merkle tree and fsverity headers for the given apk, in the form that can immediately be used
* for fsverity setup. The data is aligned to the beginning of {@code SharedMemory}, and has
* length equals to the returned {@code Integer}.
*/
private static Pair<SharedMemory, Integer> generateFsVerityIntoSharedMemory(String apkPath,
@NonNull byte[] expectedRootHash)
throws IOException, DigestException, NoSuchAlgorithmException,
SignatureNotFoundException {
TrackedShmBufferFactory shmBufferFactory = new TrackedShmBufferFactory();
byte[] generatedRootHash =
ApkSignatureVerifier.generateApkVerity(apkPath, shmBufferFactory);
// We only generate Merkle tree once here, so it's important to make sure the root hash
// matches the signed one in the apk.
if (!Arrays.equals(expectedRootHash, generatedRootHash)) {
throw new SecurityException("verity hash mismatch: "
+ bytesToString(generatedRootHash) + " != " + bytesToString(expectedRootHash));
}
int contentSize = shmBufferFactory.getBufferLimit();
SharedMemory shm = shmBufferFactory.releaseSharedMemory();
if (shm == null) {
throw new IllegalStateException("Failed to generate verity tree into shared memory");
}
if (!shm.setProtect(OsConstants.PROT_READ)) {
throw new SecurityException("Failed to set up shared memory correctly");
}
return Pair.create(shm, contentSize);
}
private static String bytesToString(byte[] bytes) {
return HexEncoding.encodeToString(bytes);
}
/**
* @deprecated This is only used for previous fs-verity implementation, and should never be used
* on new devices.
*/
@Deprecated
public static class SetupResult {
/** Result code if verity is set up correctly. */
private static final int RESULT_OK = 1;
/** Result code if signature is not provided. */
private static final int RESULT_SKIPPED = 2;
/** Result code if the setup failed. */
private static final int RESULT_FAILED = 3;
private final int mCode;
private final FileDescriptor mFileDescriptor;
private final int mContentSize;
/** @deprecated */
@Deprecated
public static SetupResult ok(@NonNull FileDescriptor fileDescriptor, int contentSize) {
return new SetupResult(RESULT_OK, fileDescriptor, contentSize);
}
/** @deprecated */
@Deprecated
public static SetupResult skipped() {
return new SetupResult(RESULT_SKIPPED, null, -1);
}
/** @deprecated */
@Deprecated
public static SetupResult failed() {
return new SetupResult(RESULT_FAILED, null, -1);
}
private SetupResult(int code, FileDescriptor fileDescriptor, int contentSize) {
this.mCode = code;
this.mFileDescriptor = fileDescriptor;
this.mContentSize = contentSize;
}
public boolean isFailed() {
return mCode == RESULT_FAILED;
}
public boolean isOk() {
return mCode == RESULT_OK;
}
public @NonNull FileDescriptor getUnownedFileDescriptor() {
return mFileDescriptor;
}
public int getContentSize() {
return mContentSize;
}
}
/** A {@code ByteBufferFactory} that creates a shared memory backed {@code ByteBuffer}. */
private static class TrackedShmBufferFactory implements ByteBufferFactory {
private SharedMemory mShm;
private ByteBuffer mBuffer;
@Override
public ByteBuffer create(int capacity) {
try {
if (DEBUG) Slog.d(TAG, "Creating shared memory for apk verity");
// NB: This method is supposed to be called once according to the contract with
// ApkSignatureSchemeV2Verifier.
if (mBuffer != null) {
throw new IllegalStateException("Multiple instantiation from this factory");
}
mShm = SharedMemory.create("apkverity", capacity);
if (!mShm.setProtect(OsConstants.PROT_READ | OsConstants.PROT_WRITE)) {
throw new SecurityException("Failed to set protection");
}
mBuffer = mShm.mapReadWrite();
return mBuffer;
} catch (ErrnoException e) {
throw new SecurityException("Failed to set protection", e);
}
}
public SharedMemory releaseSharedMemory() {
if (mBuffer != null) {
SharedMemory.unmap(mBuffer);
mBuffer = null;
}
SharedMemory tmp = mShm;
mShm = null;
return tmp;
}
public int getBufferLimit() {
return mBuffer == null ? -1 : mBuffer.limit();
}
}
}

View File

@@ -40,7 +40,6 @@ import com.android.server.SystemService;
import dalvik.system.BlockGuard;
import dalvik.system.VMRuntime;
import java.io.FileDescriptor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -681,28 +680,6 @@ public class Installer extends SystemService {
}
}
public void installApkVerity(String filePath, FileDescriptor verityInput, int contentSize)
throws InstallerException {
if (!checkBeforeRemote()) return;
BlockGuard.getVmPolicy().onPathAccess(filePath);
try {
mInstalld.installApkVerity(filePath, verityInput, contentSize);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
public void assertFsverityRootHashMatches(String filePath, @NonNull byte[] expectedHash)
throws InstallerException {
if (!checkBeforeRemote()) return;
BlockGuard.getVmPolicy().onPathAccess(filePath);
try {
mInstalld.assertFsverityRootHashMatches(filePath, expectedHash);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
public boolean reconcileSecondaryDexFile(String apkPath, String packageName, int uid,
String[] isas, @Nullable String volumeUuid, int flags) throws InstallerException {
for (int i = 0; i < isas.length; i++) {

View File

@@ -12118,14 +12118,14 @@ public class PackageManagerService extends IPackageManager.Stub
* Returns if forced apk verification can be skipped for the whole package, including splits.
*/
private boolean canSkipForcedPackageVerification(AndroidPackage pkg) {
if (!canSkipForcedApkVerification(pkg.getBaseApkPath())) {
if (!VerityUtils.hasFsverity(pkg.getBaseApkPath())) {
return false;
}
// TODO: Allow base and splits to be verified individually.
String[] splitCodePaths = pkg.getSplitCodePaths();
if (!ArrayUtils.isEmpty(splitCodePaths)) {
for (int i = 0; i < splitCodePaths.length; i++) {
if (!canSkipForcedApkVerification(splitCodePaths[i])) {
if (!VerityUtils.hasFsverity(splitCodePaths[i])) {
return false;
}
}
@@ -12133,33 +12133,6 @@ public class PackageManagerService extends IPackageManager.Stub
return true;
}
/**
* Returns if forced apk verification can be skipped, depending on current FSVerity setup and
* whether the apk contains signed root hash. Note that the signer's certificate still needs to
* match one in a trusted source, and should be done separately.
*/
private boolean canSkipForcedApkVerification(String apkPath) {
if (!PackageManagerServiceUtils.isLegacyApkVerityEnabled()) {
return VerityUtils.hasFsverity(apkPath);
}
try {
final byte[] rootHashObserved = VerityUtils.generateApkVerityRootHash(apkPath);
if (rootHashObserved == null) {
return false; // APK does not contain Merkle tree root hash.
}
synchronized (mInstallLock) {
// Returns whether the observed root hash matches what kernel has.
mInstaller.assertFsverityRootHashMatches(apkPath, rootHashObserved);
return true;
}
} catch (InstallerException | IOException | DigestException |
NoSuchAlgorithmException e) {
Slog.w(TAG, "Error in fsverity check. Fallback to full apk verification.", e);
}
return false;
}
/**
* Adds a new package to the internal data structures during platform initialization.
* <p>After adding, the package is known to the system and available for querying.
@@ -21192,9 +21165,7 @@ public class PackageManagerService extends IPackageManager.Stub
*/
private void setUpFsVerityIfPossible(AndroidPackage pkg) throws InstallerException,
PrepareFailure, IOException, DigestException, NoSuchAlgorithmException {
final boolean standardMode = PackageManagerServiceUtils.isApkVerityEnabled();
final boolean legacyMode = PackageManagerServiceUtils.isLegacyApkVerityEnabled();
if (!standardMode && !legacyMode) {
if (!PackageManagerServiceUtils.isApkVerityEnabled()) {
return;
}
@@ -21205,39 +21176,25 @@ public class PackageManagerService extends IPackageManager.Stub
// Collect files we care for fs-verity setup.
ArrayMap<String, String> fsverityCandidates = new ArrayMap<>();
if (legacyMode) {
synchronized (mLock) {
final PackageSetting ps = mSettings.getPackageLPr(pkg.getPackageName());
if (ps != null && ps.isPrivileged()) {
fsverityCandidates.put(pkg.getBaseApkPath(), null);
if (pkg.getSplitCodePaths() != null) {
for (String splitPath : pkg.getSplitCodePaths()) {
fsverityCandidates.put(splitPath, null);
}
}
}
}
} else {
// NB: These files will become only accessible if the signing key is loaded in kernel's
// .fs-verity keyring.
fsverityCandidates.put(pkg.getBaseApkPath(),
VerityUtils.getFsveritySignatureFilePath(pkg.getBaseApkPath()));
// NB: These files will become only accessible if the signing key is loaded in kernel's
// .fs-verity keyring.
fsverityCandidates.put(pkg.getBaseApkPath(),
VerityUtils.getFsveritySignatureFilePath(pkg.getBaseApkPath()));
final String dmPath = DexMetadataHelper.buildDexMetadataPathForApk(
pkg.getBaseApkPath());
if (new File(dmPath).exists()) {
fsverityCandidates.put(dmPath, VerityUtils.getFsveritySignatureFilePath(dmPath));
}
final String dmPath = DexMetadataHelper.buildDexMetadataPathForApk(
pkg.getBaseApkPath());
if (new File(dmPath).exists()) {
fsverityCandidates.put(dmPath, VerityUtils.getFsveritySignatureFilePath(dmPath));
}
if (pkg.getSplitCodePaths() != null) {
for (String path : pkg.getSplitCodePaths()) {
fsverityCandidates.put(path, VerityUtils.getFsveritySignatureFilePath(path));
if (pkg.getSplitCodePaths() != null) {
for (String path : pkg.getSplitCodePaths()) {
fsverityCandidates.put(path, VerityUtils.getFsveritySignatureFilePath(path));
final String splitDmPath = DexMetadataHelper.buildDexMetadataPathForApk(path);
if (new File(splitDmPath).exists()) {
fsverityCandidates.put(splitDmPath,
VerityUtils.getFsveritySignatureFilePath(splitDmPath));
}
final String splitDmPath = DexMetadataHelper.buildDexMetadataPathForApk(path);
if (new File(splitDmPath).exists()) {
fsverityCandidates.put(splitDmPath,
VerityUtils.getFsveritySignatureFilePath(splitDmPath));
}
}
}
@@ -21246,40 +21203,14 @@ public class PackageManagerService extends IPackageManager.Stub
final String filePath = entry.getKey();
final String signaturePath = entry.getValue();
if (!legacyMode) {
// fs-verity is optional for now. Only set up if signature is provided.
if (new File(signaturePath).exists() && !VerityUtils.hasFsverity(filePath)) {
try {
VerityUtils.setUpFsverity(filePath, signaturePath);
} catch (IOException e) {
throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
"Failed to enable fs-verity: " + e);
}
}
continue;
}
// In legacy mode, fs-verity can only be enabled by process with CAP_SYS_ADMIN.
final VerityUtils.SetupResult result = VerityUtils.generateApkVeritySetupData(filePath);
if (result.isOk()) {
if (Build.IS_DEBUGGABLE) Slog.i(TAG, "Enabling verity to " + filePath);
final FileDescriptor fd = result.getUnownedFileDescriptor();
// fs-verity is optional for now. Only set up if signature is provided.
if (new File(signaturePath).exists() && !VerityUtils.hasFsverity(filePath)) {
try {
final byte[] rootHash = VerityUtils.generateApkVerityRootHash(filePath);
try {
// A file may already have fs-verity, e.g. when reused during a split
// install. If the measurement succeeds, no need to attempt to set up.
mInstaller.assertFsverityRootHashMatches(filePath, rootHash);
} catch (InstallerException e) {
mInstaller.installApkVerity(filePath, fd, result.getContentSize());
mInstaller.assertFsverityRootHashMatches(filePath, rootHash);
}
} finally {
IoUtils.closeQuietly(fd);
VerityUtils.setUpFsverity(filePath, signaturePath);
} catch (IOException e) {
throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
"Failed to enable fs-verity: " + e);
}
} else if (result.isFailed()) {
throw new PrepareFailure(PackageManager.INSTALL_FAILED_BAD_SIGNATURE,
"Failed to generate verity");
}
}
}

View File

@@ -591,12 +591,6 @@ public class PackageManagerServiceUtils {
/** Default is to not use fs-verity since it depends on kernel support. */
private static final int FSVERITY_DISABLED = 0;
/**
* Experimental implementation targeting priv apps, with Android specific kernel patches to
* extend fs-verity.
*/
private static final int FSVERITY_LEGACY = 1;
/** Standard fs-verity. */
private static final int FSVERITY_ENABLED = 2;
@@ -607,10 +601,6 @@ public class PackageManagerServiceUtils {
== FSVERITY_ENABLED;
}
static boolean isLegacyApkVerityEnabled() {
return SystemProperties.getInt("ro.apk_verity.mode", FSVERITY_DISABLED) == FSVERITY_LEGACY;
}
/** Returns true to force apk verification if the package is considered privileged. */
static boolean isApkVerificationForced(@Nullable PackageSetting ps) {
// TODO(b/154310064): re-enable.