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.internal.security; 18 19 import android.annotation.NonNull; 20 import android.os.Build; 21 import android.os.SharedMemory; 22 import android.os.SystemProperties; 23 import android.system.ErrnoException; 24 import android.system.Os; 25 import android.system.OsConstants; 26 import android.util.Pair; 27 import android.util.Slog; 28 import android.util.apk.ApkSignatureVerifier; 29 import android.util.apk.ByteBufferFactory; 30 import android.util.apk.SignatureNotFoundException; 31 32 import libcore.util.HexEncoding; 33 34 import java.io.File; 35 import java.io.FileDescriptor; 36 import java.io.IOException; 37 import java.nio.ByteBuffer; 38 import java.nio.file.Files; 39 import java.nio.file.Paths; 40 import java.security.DigestException; 41 import java.security.NoSuchAlgorithmException; 42 import java.util.Arrays; 43 44 /** Provides fsverity related operations. */ 45 public abstract class VerityUtils { 46 private static final String TAG = "VerityUtils"; 47 48 /** 49 * File extension of the signature file. For example, foo.apk.fsv_sig is the signature file of 50 * foo.apk. 51 */ 52 public static final String FSVERITY_SIGNATURE_FILE_EXTENSION = ".fsv_sig"; 53 54 /** The maximum size of signature file. This is just to avoid potential abuse. */ 55 private static final int MAX_SIGNATURE_FILE_SIZE_BYTES = 8192; 56 57 /** SHA256 hash size. */ 58 private static final int HASH_SIZE_BYTES = 32; 59 60 private static final boolean DEBUG = false; 61 isFsVeritySupported()62 public static boolean isFsVeritySupported() { 63 return Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.R 64 || SystemProperties.getInt("ro.apk_verity.mode", 0) == 2; 65 } 66 67 /** Returns true if the given file looks like containing an fs-verity signature. */ isFsveritySignatureFile(File file)68 public static boolean isFsveritySignatureFile(File file) { 69 return file.getName().endsWith(FSVERITY_SIGNATURE_FILE_EXTENSION); 70 } 71 72 /** Returns the fs-verity signature file path of the given file. */ getFsveritySignatureFilePath(String filePath)73 public static String getFsveritySignatureFilePath(String filePath) { 74 return filePath + FSVERITY_SIGNATURE_FILE_EXTENSION; 75 } 76 77 /** Enables fs-verity for the file with a PKCS#7 detached signature file. */ setUpFsverity(@onNull String filePath, @NonNull String signaturePath)78 public static void setUpFsverity(@NonNull String filePath, @NonNull String signaturePath) 79 throws IOException { 80 if (Files.size(Paths.get(signaturePath)) > MAX_SIGNATURE_FILE_SIZE_BYTES) { 81 throw new SecurityException("Signature file is unexpectedly large: " + signaturePath); 82 } 83 setUpFsverity(filePath, Files.readAllBytes(Paths.get(signaturePath))); 84 } 85 86 /** Enables fs-verity for the file with a PKCS#7 detached signature bytes. */ setUpFsverity(@onNull String filePath, @NonNull byte[] pkcs7Signature)87 public static void setUpFsverity(@NonNull String filePath, @NonNull byte[] pkcs7Signature) 88 throws IOException { 89 // This will fail if the public key is not already in .fs-verity kernel keyring. 90 int errno = enableFsverityNative(filePath, pkcs7Signature); 91 if (errno != 0) { 92 throw new IOException("Failed to enable fs-verity on " + filePath + ": " 93 + Os.strerror(errno)); 94 } 95 } 96 97 /** Returns whether the file has fs-verity enabled. */ hasFsverity(@onNull String filePath)98 public static boolean hasFsverity(@NonNull String filePath) { 99 int retval = statxForFsverityNative(filePath); 100 if (retval < 0) { 101 Slog.e(TAG, "Failed to check whether fs-verity is enabled, errno " + -retval + ": " 102 + filePath); 103 return false; 104 } 105 return (retval == 1); 106 } 107 108 /** Returns hash of a root node for the fs-verity enabled file. */ getFsverityRootHash(@onNull String filePath)109 public static byte[] getFsverityRootHash(@NonNull String filePath) { 110 byte[] result = new byte[HASH_SIZE_BYTES]; 111 int retval = measureFsverityNative(filePath, result); 112 if (retval < 0) { 113 if (retval != -OsConstants.ENODATA) { 114 Slog.e(TAG, "Failed to measure fs-verity, errno " + -retval + ": " + filePath); 115 } 116 return null; 117 } 118 return result; 119 } 120 enableFsverityNative(@onNull String filePath, @NonNull byte[] pkcs7Signature)121 private static native int enableFsverityNative(@NonNull String filePath, 122 @NonNull byte[] pkcs7Signature); measureFsverityNative(@onNull String filePath, @NonNull byte[] digest)123 private static native int measureFsverityNative(@NonNull String filePath, 124 @NonNull byte[] digest); statxForFsverityNative(@onNull String filePath)125 private static native int statxForFsverityNative(@NonNull String filePath); 126 127 /** 128 * Generates legacy Merkle tree and fs-verity metadata with Signing Block skipped. 129 * 130 * @deprecated This is only used for previous fs-verity implementation, and should never be used 131 * on new devices. 132 * @return {@code SetupResult} that contains the result code, and when success, the 133 * {@code FileDescriptor} to read all the data from. 134 */ 135 @Deprecated generateApkVeritySetupData(@onNull String apkPath)136 public static SetupResult generateApkVeritySetupData(@NonNull String apkPath) { 137 if (DEBUG) { 138 Slog.d(TAG, "Trying to install legacy apk verity to " + apkPath); 139 } 140 SharedMemory shm = null; 141 try { 142 final byte[] signedVerityHash = ApkSignatureVerifier.getVerityRootHash(apkPath); 143 if (signedVerityHash == null) { 144 if (DEBUG) { 145 Slog.d(TAG, "Skip verity tree generation since there is no signed root hash"); 146 } 147 return SetupResult.skipped(); 148 } 149 150 Pair<SharedMemory, Integer> result = 151 generateFsVerityIntoSharedMemory(apkPath, signedVerityHash); 152 shm = result.first; 153 int contentSize = result.second; 154 FileDescriptor rfd = shm.getFileDescriptor(); 155 if (rfd == null || !rfd.valid()) { 156 return SetupResult.failed(); 157 } 158 return SetupResult.ok(Os.dup(rfd), contentSize); 159 } catch (IOException | SecurityException | DigestException | NoSuchAlgorithmException 160 | SignatureNotFoundException | ErrnoException e) { 161 Slog.e(TAG, "Failed to set up apk verity: ", e); 162 return SetupResult.failed(); 163 } finally { 164 if (shm != null) { 165 shm.close(); 166 } 167 } 168 } 169 170 /** 171 * {@see ApkSignatureVerifier#generateApkVerityRootHash(String)}. 172 * @deprecated This is only used for previous fs-verity implementation, and should never be used 173 * on new devices. 174 */ 175 @Deprecated generateApkVerityRootHash(@onNull String apkPath)176 public static byte[] generateApkVerityRootHash(@NonNull String apkPath) 177 throws NoSuchAlgorithmException, DigestException, IOException { 178 return ApkSignatureVerifier.generateApkVerityRootHash(apkPath); 179 } 180 181 /** 182 * {@see ApkSignatureVerifier#getVerityRootHash(String)}. 183 * @deprecated This is only used for previous fs-verity implementation, and should never be used 184 * on new devices. 185 */ 186 @Deprecated getVerityRootHash(@onNull String apkPath)187 public static byte[] getVerityRootHash(@NonNull String apkPath) 188 throws IOException, SignatureNotFoundException { 189 return ApkSignatureVerifier.getVerityRootHash(apkPath); 190 } 191 192 /** 193 * Returns a pair of {@code SharedMemory} and {@code Integer}. The {@code SharedMemory} contains 194 * Merkle tree and fsverity headers for the given apk, in the form that can immediately be used 195 * for fsverity setup. The data is aligned to the beginning of {@code SharedMemory}, and has 196 * length equals to the returned {@code Integer}. 197 */ generateFsVerityIntoSharedMemory(String apkPath, @NonNull byte[] expectedRootHash)198 private static Pair<SharedMemory, Integer> generateFsVerityIntoSharedMemory(String apkPath, 199 @NonNull byte[] expectedRootHash) 200 throws IOException, DigestException, NoSuchAlgorithmException, 201 SignatureNotFoundException { 202 TrackedShmBufferFactory shmBufferFactory = new TrackedShmBufferFactory(); 203 byte[] generatedRootHash = 204 ApkSignatureVerifier.generateApkVerity(apkPath, shmBufferFactory); 205 // We only generate Merkle tree once here, so it's important to make sure the root hash 206 // matches the signed one in the apk. 207 if (!Arrays.equals(expectedRootHash, generatedRootHash)) { 208 throw new SecurityException("verity hash mismatch: " 209 + bytesToString(generatedRootHash) + " != " + bytesToString(expectedRootHash)); 210 } 211 212 int contentSize = shmBufferFactory.getBufferLimit(); 213 SharedMemory shm = shmBufferFactory.releaseSharedMemory(); 214 if (shm == null) { 215 throw new IllegalStateException("Failed to generate verity tree into shared memory"); 216 } 217 if (!shm.setProtect(OsConstants.PROT_READ)) { 218 throw new SecurityException("Failed to set up shared memory correctly"); 219 } 220 return Pair.create(shm, contentSize); 221 } 222 bytesToString(byte[] bytes)223 private static String bytesToString(byte[] bytes) { 224 return HexEncoding.encodeToString(bytes); 225 } 226 227 /** 228 * @deprecated This is only used for previous fs-verity implementation, and should never be used 229 * on new devices. 230 */ 231 @Deprecated 232 public static class SetupResult { 233 /** Result code if verity is set up correctly. */ 234 private static final int RESULT_OK = 1; 235 236 /** Result code if signature is not provided. */ 237 private static final int RESULT_SKIPPED = 2; 238 239 /** Result code if the setup failed. */ 240 private static final int RESULT_FAILED = 3; 241 242 private final int mCode; 243 private final FileDescriptor mFileDescriptor; 244 private final int mContentSize; 245 246 /** @deprecated */ 247 @Deprecated ok(@onNull FileDescriptor fileDescriptor, int contentSize)248 public static SetupResult ok(@NonNull FileDescriptor fileDescriptor, int contentSize) { 249 return new SetupResult(RESULT_OK, fileDescriptor, contentSize); 250 } 251 252 /** @deprecated */ 253 @Deprecated skipped()254 public static SetupResult skipped() { 255 return new SetupResult(RESULT_SKIPPED, null, -1); 256 } 257 258 /** @deprecated */ 259 @Deprecated failed()260 public static SetupResult failed() { 261 return new SetupResult(RESULT_FAILED, null, -1); 262 } 263 SetupResult(int code, FileDescriptor fileDescriptor, int contentSize)264 private SetupResult(int code, FileDescriptor fileDescriptor, int contentSize) { 265 this.mCode = code; 266 this.mFileDescriptor = fileDescriptor; 267 this.mContentSize = contentSize; 268 } 269 isFailed()270 public boolean isFailed() { 271 return mCode == RESULT_FAILED; 272 } 273 isOk()274 public boolean isOk() { 275 return mCode == RESULT_OK; 276 } 277 getUnownedFileDescriptor()278 public @NonNull FileDescriptor getUnownedFileDescriptor() { 279 return mFileDescriptor; 280 } 281 getContentSize()282 public int getContentSize() { 283 return mContentSize; 284 } 285 } 286 287 /** A {@code ByteBufferFactory} that creates a shared memory backed {@code ByteBuffer}. */ 288 private static class TrackedShmBufferFactory implements ByteBufferFactory { 289 private SharedMemory mShm; 290 private ByteBuffer mBuffer; 291 292 @Override create(int capacity)293 public ByteBuffer create(int capacity) { 294 try { 295 if (DEBUG) Slog.d(TAG, "Creating shared memory for apk verity"); 296 // NB: This method is supposed to be called once according to the contract with 297 // ApkSignatureSchemeV2Verifier. 298 if (mBuffer != null) { 299 throw new IllegalStateException("Multiple instantiation from this factory"); 300 } 301 mShm = SharedMemory.create("apkverity", capacity); 302 if (!mShm.setProtect(OsConstants.PROT_READ | OsConstants.PROT_WRITE)) { 303 throw new SecurityException("Failed to set protection"); 304 } 305 mBuffer = mShm.mapReadWrite(); 306 return mBuffer; 307 } catch (ErrnoException e) { 308 throw new SecurityException("Failed to set protection", e); 309 } 310 } 311 releaseSharedMemory()312 public SharedMemory releaseSharedMemory() { 313 if (mBuffer != null) { 314 SharedMemory.unmap(mBuffer); 315 mBuffer = null; 316 } 317 SharedMemory tmp = mShm; 318 mShm = null; 319 return tmp; 320 } 321 getBufferLimit()322 public int getBufferLimit() { 323 return mBuffer == null ? -1 : mBuffer.limit(); 324 } 325 } 326 } 327