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