diff options
4 files changed, 240 insertions, 3 deletions
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 4b3758d2e293..03bf876cd5bc 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -33,6 +33,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"; @@ -473,6 +475,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++) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 78200f29ca60..24f7dc3fe77f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -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; @@ -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 { diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 021c4b8a24c1..bf3eb8eaae1f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -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. @@ -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(); } /** diff --git a/services/core/java/com/android/server/security/VerityUtils.java b/services/core/java/com/android/server/security/VerityUtils.java new file mode 100644 index 000000000000..3908df46d3a4 --- /dev/null +++ b/services/core/java/com/android/server/security/VerityUtils.java @@ -0,0 +1,184 @@ +/* + * 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; + } + } +} |