1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.security; 18 19 import static android.system.OsConstants.PROT_READ; 20 import static android.system.OsConstants.PROT_WRITE; 21 22 import android.annotation.NonNull; 23 import android.os.SharedMemory; 24 import android.system.ErrnoException; 25 import android.system.Os; 26 import android.util.apk.ApkSignatureVerifier; 27 import android.util.apk.ByteBufferFactory; 28 import android.util.apk.SignatureNotFoundException; 29 import android.util.Pair; 30 import android.util.Slog; 31 32 import java.io.FileDescriptor; 33 import java.io.IOException; 34 import java.nio.ByteBuffer; 35 import java.security.DigestException; 36 import java.security.NoSuchAlgorithmException; 37 import java.util.Arrays; 38 39 /** Provides fsverity related operations. */ 40 abstract public class VerityUtils { 41 private static final String TAG = "VerityUtils"; 42 43 private static final boolean DEBUG = false; 44 45 /** 46 * Generates Merkle tree and fsverity metadata. 47 * 48 * @return {@code SetupResult} that contains the {@code EsetupResultCode}, and when success, the 49 * {@code FileDescriptor} to read all the data from. 50 */ generateApkVeritySetupData(@onNull String apkPath)51 public static SetupResult generateApkVeritySetupData(@NonNull String apkPath) { 52 if (DEBUG) Slog.d(TAG, "Trying to install apk verity to " + apkPath); 53 SharedMemory shm = null; 54 try { 55 byte[] signedRootHash = ApkSignatureVerifier.getVerityRootHash(apkPath); 56 if (signedRootHash == null) { 57 if (DEBUG) { 58 Slog.d(TAG, "Skip verity tree generation since there is no root hash"); 59 } 60 return SetupResult.skipped(); 61 } 62 63 Pair<SharedMemory, Integer> result = generateApkVerityIntoSharedMemory(apkPath, 64 signedRootHash); 65 shm = result.first; 66 int contentSize = result.second; 67 FileDescriptor rfd = shm.getFileDescriptor(); 68 if (rfd == null || !rfd.valid()) { 69 return SetupResult.failed(); 70 } 71 return SetupResult.ok(Os.dup(rfd), contentSize); 72 } catch (IOException | SecurityException | DigestException | NoSuchAlgorithmException | 73 SignatureNotFoundException | ErrnoException e) { 74 Slog.e(TAG, "Failed to set up apk verity: ", e); 75 return SetupResult.failed(); 76 } finally { 77 if (shm != null) { 78 shm.close(); 79 } 80 } 81 } 82 83 /** 84 * {@see ApkSignatureVerifier#generateFsverityRootHash(String)}. 85 */ generateFsverityRootHash(@onNull String apkPath)86 public static byte[] generateFsverityRootHash(@NonNull String apkPath) 87 throws NoSuchAlgorithmException, DigestException, IOException { 88 return ApkSignatureVerifier.generateFsverityRootHash(apkPath); 89 } 90 91 /** 92 * {@see ApkSignatureVerifier#getVerityRootHash(String)}. 93 */ getVerityRootHash(@onNull String apkPath)94 public static byte[] getVerityRootHash(@NonNull String apkPath) 95 throws IOException, SignatureNotFoundException, SecurityException { 96 return ApkSignatureVerifier.getVerityRootHash(apkPath); 97 } 98 99 /** 100 * Returns a pair of {@code SharedMemory} and {@code Integer}. The {@code SharedMemory} contains 101 * Merkle tree and fsverity headers for the given apk, in the form that can immediately be used 102 * for fsverity setup. The data is aligned to the beginning of {@code SharedMemory}, and has 103 * length equals to the returned {@code Integer}. 104 */ generateApkVerityIntoSharedMemory( String apkPath, byte[] expectedRootHash)105 private static Pair<SharedMemory, Integer> generateApkVerityIntoSharedMemory( 106 String apkPath, byte[] expectedRootHash) 107 throws IOException, SecurityException, DigestException, NoSuchAlgorithmException, 108 SignatureNotFoundException { 109 TrackedShmBufferFactory shmBufferFactory = new TrackedShmBufferFactory(); 110 byte[] generatedRootHash = ApkSignatureVerifier.generateApkVerity(apkPath, 111 shmBufferFactory); 112 // We only generate Merkle tree once here, so it's important to make sure the root hash 113 // matches the signed one in the apk. 114 if (!Arrays.equals(expectedRootHash, generatedRootHash)) { 115 throw new SecurityException("Locally generated verity root hash does not match"); 116 } 117 118 int contentSize = shmBufferFactory.getBufferLimit(); 119 SharedMemory shm = shmBufferFactory.releaseSharedMemory(); 120 if (shm == null) { 121 throw new IllegalStateException("Failed to generate verity tree into shared memory"); 122 } 123 if (!shm.setProtect(PROT_READ)) { 124 throw new SecurityException("Failed to set up shared memory correctly"); 125 } 126 return Pair.create(shm, contentSize); 127 } 128 129 public static class SetupResult { 130 /** Result code if verity is set up correctly. */ 131 private static final int RESULT_OK = 1; 132 133 /** Result code if the apk does not contain a verity root hash. */ 134 private static final int RESULT_SKIPPED = 2; 135 136 /** Result code if the setup failed. */ 137 private static final int RESULT_FAILED = 3; 138 139 private final int mCode; 140 private final FileDescriptor mFileDescriptor; 141 private final int mContentSize; 142 ok(@onNull FileDescriptor fileDescriptor, int contentSize)143 public static SetupResult ok(@NonNull FileDescriptor fileDescriptor, int contentSize) { 144 return new SetupResult(RESULT_OK, fileDescriptor, contentSize); 145 } 146 skipped()147 public static SetupResult skipped() { 148 return new SetupResult(RESULT_SKIPPED, null, -1); 149 } 150 failed()151 public static SetupResult failed() { 152 return new SetupResult(RESULT_FAILED, null, -1); 153 } 154 SetupResult(int code, FileDescriptor fileDescriptor, int contentSize)155 private SetupResult(int code, FileDescriptor fileDescriptor, int contentSize) { 156 this.mCode = code; 157 this.mFileDescriptor = fileDescriptor; 158 this.mContentSize = contentSize; 159 } 160 isFailed()161 public boolean isFailed() { 162 return mCode == RESULT_FAILED; 163 } 164 isOk()165 public boolean isOk() { 166 return mCode == RESULT_OK; 167 } 168 getUnownedFileDescriptor()169 public @NonNull FileDescriptor getUnownedFileDescriptor() { 170 return mFileDescriptor; 171 } 172 getContentSize()173 public int getContentSize() { 174 return mContentSize; 175 } 176 } 177 178 /** A {@code ByteBufferFactory} that creates a shared memory backed {@code ByteBuffer}. */ 179 private static class TrackedShmBufferFactory implements ByteBufferFactory { 180 private SharedMemory mShm; 181 private ByteBuffer mBuffer; 182 183 @Override create(int capacity)184 public ByteBuffer create(int capacity) throws SecurityException { 185 try { 186 if (DEBUG) Slog.d(TAG, "Creating shared memory for apk verity"); 187 // NB: This method is supposed to be called once according to the contract with 188 // ApkSignatureSchemeV2Verifier. 189 if (mBuffer != null) { 190 throw new IllegalStateException("Multiple instantiation from this factory"); 191 } 192 mShm = SharedMemory.create("apkverity", capacity); 193 if (!mShm.setProtect(PROT_READ | PROT_WRITE)) { 194 throw new SecurityException("Failed to set protection"); 195 } 196 mBuffer = mShm.mapReadWrite(); 197 return mBuffer; 198 } catch (ErrnoException e) { 199 throw new SecurityException("Failed to set protection", e); 200 } 201 } 202 releaseSharedMemory()203 public SharedMemory releaseSharedMemory() { 204 if (mBuffer != null) { 205 SharedMemory.unmap(mBuffer); 206 mBuffer = null; 207 } 208 SharedMemory tmp = mShm; 209 mShm = null; 210 return tmp; 211 } 212 getBufferLimit()213 public int getBufferLimit() { 214 return mBuffer == null ? -1 : mBuffer.limit(); 215 } 216 } 217 } 218