Loading services/core/java/com/android/server/pm/Installer.java +12 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,8 @@ import com.android.server.SystemService; import dalvik.system.VMRuntime; import java.io.FileDescriptor; public class Installer extends SystemService { private static final String TAG = "Installer"; Loading Loading @@ -478,6 +480,16 @@ public class Installer extends SystemService { } } public void installApkVerity(String filePath, FileDescriptor verityInput) throws InstallerException { if (!checkBeforeRemote()) return; try { mInstalld.installApkVerity(filePath, verityInput); } 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++) { Loading services/core/java/com/android/server/pm/PackageManagerService.java +38 −0 Original line number Diff line number Diff line Loading @@ -314,6 +314,7 @@ import com.android.server.pm.permission.DefaultPermissionGrantPolicy.DefaultPerm import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback; import com.android.server.pm.permission.PermissionsState; import com.android.server.pm.permission.PermissionsState.PermissionState; import com.android.server.security.VerityUtils; import com.android.server.storage.DeviceStorageMonitorInternal; import dalvik.system.CloseGuard; Loading Loading @@ -16987,6 +16988,43 @@ Slog.e("TODD", return; } if (PackageManagerServiceUtils.isApkVerityEnabled()) { String apkPath = null; synchronized (mPackages) { // Note that if the attacker managed to skip verify setup, for example by tampering // with the package settings, upon reboot we will do full apk verification when // verity is not detected. final PackageSetting ps = mSettings.mPackages.get(pkgName); if (ps != null && ps.isPrivileged()) { apkPath = pkg.baseCodePath; } } if (apkPath != null) { final VerityUtils.SetupResult result = VerityUtils.generateApkVeritySetupData(apkPath); if (result.isOk()) { if (Build.IS_DEBUGGABLE) Slog.i(TAG, "Enabling apk verity to " + apkPath); FileDescriptor fd = result.getUnownedFileDescriptor(); try { mInstaller.installApkVerity(apkPath, fd); } catch (InstallerException e) { res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Failed to set up verity: " + e); return; } finally { IoUtils.closeQuietly(fd); } } else if (result.isFailed()) { res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Failed to generate verity"); return; } else { // Do nothing if verity is skipped. Will fall back to full apk verification on // reboot. } } } if (!instantApp) { startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg); } else { services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +6 −3 Original line number Diff line number Diff line Loading @@ -579,7 +579,6 @@ public class PackageManagerServiceUtils { return true; } /** * Checks the signing certificates to see if the provided certificate is a member. Invalid for * {@code SigningDetails} with multiple signing certificates. Loading Loading @@ -642,10 +641,14 @@ public class PackageManagerServiceUtils { return false; } /** Returns true if APK Verity is enabled. */ static boolean isApkVerityEnabled() { return SystemProperties.getInt("ro.apk_verity.mode", 0) != 0; } /** Returns true to force apk verification if the updated package (in /data) is a priv app. */ static boolean isApkVerificationForced(@Nullable PackageSetting disabledPs) { return disabledPs != null && disabledPs.isPrivileged() && SystemProperties.getInt("ro.apk_verity.mode", 0) != 0; return disabledPs != null && disabledPs.isPrivileged() && isApkVerityEnabled(); } /** Loading services/core/java/com/android/server/security/VerityUtils.java 0 → 100644 +184 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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 com.android.server.security; import static android.system.OsConstants.PROT_READ; import static android.system.OsConstants.PROT_WRITE; import android.annotation.NonNull; import android.os.SharedMemory; import android.system.ErrnoException; import android.system.Os; import android.util.apk.ApkSignatureVerifier; import android.util.apk.ByteBufferFactory; import android.util.apk.SignatureNotFoundException; import android.util.Slog; import java.io.FileDescriptor; import java.io.IOException; import java.nio.ByteBuffer; import java.security.DigestException; import java.security.NoSuchAlgorithmException; import java.util.Arrays; /** Provides fsverity related operations. */ abstract public class VerityUtils { private static final String TAG = "VerityUtils"; private static final boolean DEBUG = false; /** * Generates Merkle tree and fsverity metadata. * * @return {@code SetupResult} that contains the {@code EsetupResultCode}, and when success, the * {@code FileDescriptor} to read all the data from. */ public static SetupResult generateApkVeritySetupData(@NonNull String apkPath) { if (DEBUG) Slog.d(TAG, "Trying to install apk verity to " + apkPath); SharedMemory shm = null; try { byte[] signedRootHash = ApkSignatureVerifier.getVerityRootHash(apkPath); if (signedRootHash == null) { if (DEBUG) { Slog.d(TAG, "Skip verity tree generation since there is no root hash"); } return SetupResult.skipped(); } shm = generateApkVerityIntoSharedMemory(apkPath, signedRootHash); FileDescriptor rfd = shm.getFileDescriptor(); if (rfd == null || !rfd.valid()) { return SetupResult.failed(); } return SetupResult.ok(Os.dup(rfd)); } 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(); } } } /** * Returns a {@code SharedMemory} that contains Merkle tree and fsverity headers for the given * apk, in the form that can immediately be used for fsverity setup. */ private static SharedMemory generateApkVerityIntoSharedMemory( String apkPath, byte[] expectedRootHash) throws IOException, SecurityException, 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("Locally generated verity root hash does not match"); } SharedMemory shm = shmBufferFactory.releaseSharedMemory(); if (shm == null) { throw new IllegalStateException("Failed to generate verity tree into shared memory"); } if (!shm.setProtect(PROT_READ)) { throw new SecurityException("Failed to set up shared memory correctly"); } return shm; } public static class SetupResult { /** Result code if verity is set up correctly. */ private static final int RESULT_OK = 1; /** Result code if the apk does not contain a verity root hash. */ 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; public static SetupResult ok(@NonNull FileDescriptor fileDescriptor) { return new SetupResult(RESULT_OK, fileDescriptor); } public static SetupResult skipped() { return new SetupResult(RESULT_SKIPPED, null); } public static SetupResult failed() { return new SetupResult(RESULT_FAILED, null); } private SetupResult(int code, FileDescriptor fileDescriptor) { this.mCode = code; this.mFileDescriptor = fileDescriptor; } public boolean isFailed() { return mCode == RESULT_FAILED; } public boolean isOk() { return mCode == RESULT_OK; } public @NonNull FileDescriptor getUnownedFileDescriptor() { return mFileDescriptor; } } /** 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) throws SecurityException { 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(PROT_READ | 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; } } } Loading
services/core/java/com/android/server/pm/Installer.java +12 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,8 @@ import com.android.server.SystemService; import dalvik.system.VMRuntime; import java.io.FileDescriptor; public class Installer extends SystemService { private static final String TAG = "Installer"; Loading Loading @@ -478,6 +480,16 @@ public class Installer extends SystemService { } } public void installApkVerity(String filePath, FileDescriptor verityInput) throws InstallerException { if (!checkBeforeRemote()) return; try { mInstalld.installApkVerity(filePath, verityInput); } 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++) { Loading
services/core/java/com/android/server/pm/PackageManagerService.java +38 −0 Original line number Diff line number Diff line Loading @@ -314,6 +314,7 @@ import com.android.server.pm.permission.DefaultPermissionGrantPolicy.DefaultPerm import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback; import com.android.server.pm.permission.PermissionsState; import com.android.server.pm.permission.PermissionsState.PermissionState; import com.android.server.security.VerityUtils; import com.android.server.storage.DeviceStorageMonitorInternal; import dalvik.system.CloseGuard; Loading Loading @@ -16987,6 +16988,43 @@ Slog.e("TODD", return; } if (PackageManagerServiceUtils.isApkVerityEnabled()) { String apkPath = null; synchronized (mPackages) { // Note that if the attacker managed to skip verify setup, for example by tampering // with the package settings, upon reboot we will do full apk verification when // verity is not detected. final PackageSetting ps = mSettings.mPackages.get(pkgName); if (ps != null && ps.isPrivileged()) { apkPath = pkg.baseCodePath; } } if (apkPath != null) { final VerityUtils.SetupResult result = VerityUtils.generateApkVeritySetupData(apkPath); if (result.isOk()) { if (Build.IS_DEBUGGABLE) Slog.i(TAG, "Enabling apk verity to " + apkPath); FileDescriptor fd = result.getUnownedFileDescriptor(); try { mInstaller.installApkVerity(apkPath, fd); } catch (InstallerException e) { res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Failed to set up verity: " + e); return; } finally { IoUtils.closeQuietly(fd); } } else if (result.isFailed()) { res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Failed to generate verity"); return; } else { // Do nothing if verity is skipped. Will fall back to full apk verification on // reboot. } } } if (!instantApp) { startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg); } else {
services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +6 −3 Original line number Diff line number Diff line Loading @@ -579,7 +579,6 @@ public class PackageManagerServiceUtils { return true; } /** * Checks the signing certificates to see if the provided certificate is a member. Invalid for * {@code SigningDetails} with multiple signing certificates. Loading Loading @@ -642,10 +641,14 @@ public class PackageManagerServiceUtils { return false; } /** Returns true if APK Verity is enabled. */ static boolean isApkVerityEnabled() { return SystemProperties.getInt("ro.apk_verity.mode", 0) != 0; } /** Returns true to force apk verification if the updated package (in /data) is a priv app. */ static boolean isApkVerificationForced(@Nullable PackageSetting disabledPs) { return disabledPs != null && disabledPs.isPrivileged() && SystemProperties.getInt("ro.apk_verity.mode", 0) != 0; return disabledPs != null && disabledPs.isPrivileged() && isApkVerityEnabled(); } /** Loading
services/core/java/com/android/server/security/VerityUtils.java 0 → 100644 +184 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 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 com.android.server.security; import static android.system.OsConstants.PROT_READ; import static android.system.OsConstants.PROT_WRITE; import android.annotation.NonNull; import android.os.SharedMemory; import android.system.ErrnoException; import android.system.Os; import android.util.apk.ApkSignatureVerifier; import android.util.apk.ByteBufferFactory; import android.util.apk.SignatureNotFoundException; import android.util.Slog; import java.io.FileDescriptor; import java.io.IOException; import java.nio.ByteBuffer; import java.security.DigestException; import java.security.NoSuchAlgorithmException; import java.util.Arrays; /** Provides fsverity related operations. */ abstract public class VerityUtils { private static final String TAG = "VerityUtils"; private static final boolean DEBUG = false; /** * Generates Merkle tree and fsverity metadata. * * @return {@code SetupResult} that contains the {@code EsetupResultCode}, and when success, the * {@code FileDescriptor} to read all the data from. */ public static SetupResult generateApkVeritySetupData(@NonNull String apkPath) { if (DEBUG) Slog.d(TAG, "Trying to install apk verity to " + apkPath); SharedMemory shm = null; try { byte[] signedRootHash = ApkSignatureVerifier.getVerityRootHash(apkPath); if (signedRootHash == null) { if (DEBUG) { Slog.d(TAG, "Skip verity tree generation since there is no root hash"); } return SetupResult.skipped(); } shm = generateApkVerityIntoSharedMemory(apkPath, signedRootHash); FileDescriptor rfd = shm.getFileDescriptor(); if (rfd == null || !rfd.valid()) { return SetupResult.failed(); } return SetupResult.ok(Os.dup(rfd)); } 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(); } } } /** * Returns a {@code SharedMemory} that contains Merkle tree and fsverity headers for the given * apk, in the form that can immediately be used for fsverity setup. */ private static SharedMemory generateApkVerityIntoSharedMemory( String apkPath, byte[] expectedRootHash) throws IOException, SecurityException, 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("Locally generated verity root hash does not match"); } SharedMemory shm = shmBufferFactory.releaseSharedMemory(); if (shm == null) { throw new IllegalStateException("Failed to generate verity tree into shared memory"); } if (!shm.setProtect(PROT_READ)) { throw new SecurityException("Failed to set up shared memory correctly"); } return shm; } public static class SetupResult { /** Result code if verity is set up correctly. */ private static final int RESULT_OK = 1; /** Result code if the apk does not contain a verity root hash. */ 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; public static SetupResult ok(@NonNull FileDescriptor fileDescriptor) { return new SetupResult(RESULT_OK, fileDescriptor); } public static SetupResult skipped() { return new SetupResult(RESULT_SKIPPED, null); } public static SetupResult failed() { return new SetupResult(RESULT_FAILED, null); } private SetupResult(int code, FileDescriptor fileDescriptor) { this.mCode = code; this.mFileDescriptor = fileDescriptor; } public boolean isFailed() { return mCode == RESULT_FAILED; } public boolean isOk() { return mCode == RESULT_OK; } public @NonNull FileDescriptor getUnownedFileDescriptor() { return mFileDescriptor; } } /** 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) throws SecurityException { 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(PROT_READ | 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; } } }