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 import android.util.apk.VerityBuilder; 30 31 import libcore.util.HexEncoding; 32 33 import java.io.File; 34 import java.io.FileDescriptor; 35 import java.io.IOException; 36 import java.io.RandomAccessFile; 37 import java.nio.ByteBuffer; 38 import java.nio.ByteOrder; 39 import java.nio.channels.FileChannel; 40 import java.nio.file.Files; 41 import java.nio.file.Path; 42 import java.nio.file.Paths; 43 import java.security.DigestException; 44 import java.security.MessageDigest; 45 import java.security.NoSuchAlgorithmException; 46 import java.util.Arrays; 47 48 import sun.security.pkcs.PKCS7; 49 50 /** Provides fsverity related operations. */ 51 abstract public class VerityUtils { 52 private static final String TAG = "VerityUtils"; 53 54 /** 55 * File extension of the signature file. For example, foo.apk.fsv_sig is the signature file of 56 * foo.apk. 57 */ 58 public static final String FSVERITY_SIGNATURE_FILE_EXTENSION = ".fsv_sig"; 59 60 /** The maximum size of signature file. This is just to avoid potential abuse. */ 61 private static final int MAX_SIGNATURE_FILE_SIZE_BYTES = 8192; 62 63 private static final int COMMON_LINUX_PAGE_SIZE_IN_BYTES = 4096; 64 65 private static final boolean DEBUG = false; 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 /** Generates Merkle tree and fs-verity metadata then enables fs-verity. */ setUpFsverity(@onNull String filePath, String signaturePath)78 public static void setUpFsverity(@NonNull String filePath, String signaturePath) 79 throws IOException, DigestException, NoSuchAlgorithmException { 80 final PKCS7 pkcs7 = new PKCS7(Files.readAllBytes(Paths.get(signaturePath))); 81 final byte[] expectedMeasurement = pkcs7.getContentInfo().getContentBytes(); 82 if (DEBUG) { 83 Slog.d(TAG, "Enabling fs-verity with signed fs-verity measurement " 84 + bytesToString(expectedMeasurement)); 85 Slog.d(TAG, "PKCS#7 info: " + pkcs7); 86 } 87 88 final TrackedBufferFactory bufferFactory = new TrackedBufferFactory(); 89 final byte[] actualMeasurement = generateFsverityMetadata(filePath, signaturePath, 90 bufferFactory); 91 try (RandomAccessFile raf = new RandomAccessFile(filePath, "rw")) { 92 FileChannel ch = raf.getChannel(); 93 ch.position(roundUpToNextMultiple(ch.size(), COMMON_LINUX_PAGE_SIZE_IN_BYTES)); 94 ByteBuffer buffer = bufferFactory.getBuffer(); 95 96 long offset = buffer.position(); 97 long size = buffer.limit(); 98 while (offset < size) { 99 long s = ch.write(buffer); 100 offset += s; 101 size -= s; 102 } 103 } 104 105 if (!Arrays.equals(expectedMeasurement, actualMeasurement)) { 106 throw new SecurityException("fs-verity measurement mismatch: " 107 + bytesToString(actualMeasurement) + " != " 108 + bytesToString(expectedMeasurement)); 109 } 110 111 // This can fail if the public key is not already in .fs-verity kernel keyring. 112 int errno = enableFsverityNative(filePath); 113 if (errno != 0) { 114 throw new IOException("Failed to enable fs-verity on " + filePath + ": " 115 + Os.strerror(errno)); 116 } 117 } 118 119 /** Returns whether the file has fs-verity enabled. */ hasFsverity(@onNull String filePath)120 public static boolean hasFsverity(@NonNull String filePath) { 121 // NB: only measure but not check the actual measurement here. As long as this succeeds, 122 // the file is on readable if the measurement can be verified against a trusted key, and 123 // this is good enough for installed apps. 124 int errno = measureFsverityNative(filePath); 125 if (errno != 0) { 126 if (errno != OsConstants.ENODATA) { 127 Slog.e(TAG, "Failed to measure fs-verity, errno " + errno + ": " + filePath); 128 } 129 return false; 130 } 131 return true; 132 } 133 134 /** 135 * Generates legacy Merkle tree and fs-verity metadata with Signing Block skipped. 136 * 137 * @return {@code SetupResult} that contains the result code, and when success, the 138 * {@code FileDescriptor} to read all the data from. 139 */ generateApkVeritySetupData(@onNull String apkPath)140 public static SetupResult generateApkVeritySetupData(@NonNull String apkPath) { 141 if (DEBUG) { 142 Slog.d(TAG, "Trying to install legacy apk verity to " + apkPath); 143 } 144 SharedMemory shm = null; 145 try { 146 final byte[] signedVerityHash = ApkSignatureVerifier.getVerityRootHash(apkPath); 147 if (signedVerityHash == null) { 148 if (DEBUG) { 149 Slog.d(TAG, "Skip verity tree generation since there is no signed root hash"); 150 } 151 return SetupResult.skipped(); 152 } 153 154 Pair<SharedMemory, Integer> result = 155 generateFsVerityIntoSharedMemory(apkPath, signedVerityHash); 156 shm = result.first; 157 int contentSize = result.second; 158 FileDescriptor rfd = shm.getFileDescriptor(); 159 if (rfd == null || !rfd.valid()) { 160 return SetupResult.failed(); 161 } 162 return SetupResult.ok(Os.dup(rfd), contentSize); 163 } catch (IOException | SecurityException | DigestException | NoSuchAlgorithmException | 164 SignatureNotFoundException | ErrnoException e) { 165 Slog.e(TAG, "Failed to set up apk verity: ", e); 166 return SetupResult.failed(); 167 } finally { 168 if (shm != null) { 169 shm.close(); 170 } 171 } 172 } 173 174 /** 175 * {@see ApkSignatureVerifier#generateApkVerityRootHash(String)}. 176 */ generateApkVerityRootHash(@onNull String apkPath)177 public static byte[] generateApkVerityRootHash(@NonNull String apkPath) 178 throws NoSuchAlgorithmException, DigestException, IOException { 179 return ApkSignatureVerifier.generateApkVerityRootHash(apkPath); 180 } 181 182 /** 183 * {@see ApkSignatureVerifier#getVerityRootHash(String)}. 184 */ getVerityRootHash(@onNull String apkPath)185 public static byte[] getVerityRootHash(@NonNull String apkPath) 186 throws IOException, SignatureNotFoundException { 187 return ApkSignatureVerifier.getVerityRootHash(apkPath); 188 } 189 190 /** 191 * Generates fs-verity metadata for {@code filePath} in the buffer created by {@code 192 * trackedBufferFactory}. The metadata contains the Merkle tree, fs-verity descriptor and 193 * extensions, including a PKCS#7 signature provided in {@code signaturePath}. 194 * 195 * <p>It is worthy to note that {@code trackedBufferFactory} generates a "tracked" {@code 196 * ByteBuffer}. The data will be used outside this method via the factory itself. 197 * 198 * @return fs-verity signed data (struct fsverity_digest_disk) of {@code filePath}, which 199 * includes SHA-256 of fs-verity descriptor and authenticated extensions. 200 */ generateFsverityMetadata(String filePath, String signaturePath, @NonNull ByteBufferFactory trackedBufferFactory)201 private static byte[] generateFsverityMetadata(String filePath, String signaturePath, 202 @NonNull ByteBufferFactory trackedBufferFactory) 203 throws IOException, DigestException, NoSuchAlgorithmException { 204 try (RandomAccessFile file = new RandomAccessFile(filePath, "r")) { 205 VerityBuilder.VerityResult result = VerityBuilder.generateFsVerityTree( 206 file, trackedBufferFactory); 207 208 ByteBuffer buffer = result.verityData; 209 buffer.position(result.merkleTreeSize); 210 211 final byte[] measurement = generateFsverityDescriptorAndMeasurement(file, 212 result.rootHash, signaturePath, buffer); 213 buffer.flip(); 214 return constructFsveritySignedDataNative(measurement); 215 } 216 } 217 218 /** 219 * Generates fs-verity descriptor including the extensions to the {@code output} and returns the 220 * fs-verity measurement. 221 * 222 * @return fs-verity measurement, which is a SHA-256 of fs-verity descriptor and authenticated 223 * extensions. 224 */ generateFsverityDescriptorAndMeasurement( @onNull RandomAccessFile file, @NonNull byte[] rootHash, @NonNull String pkcs7SignaturePath, @NonNull ByteBuffer output)225 private static byte[] generateFsverityDescriptorAndMeasurement( 226 @NonNull RandomAccessFile file, @NonNull byte[] rootHash, 227 @NonNull String pkcs7SignaturePath, @NonNull ByteBuffer output) 228 throws IOException, NoSuchAlgorithmException, DigestException { 229 final short kRootHashExtensionId = 1; 230 final short kPkcs7SignatureExtensionId = 3; 231 final int origPosition = output.position(); 232 233 // For generating fs-verity file measurement, which consists of the descriptor and 234 // authenticated extensions (but not unauthenticated extensions and the footer). 235 MessageDigest md = MessageDigest.getInstance("SHA-256"); 236 237 // 1. Generate fs-verity descriptor. 238 final byte[] desc = constructFsverityDescriptorNative(file.length()); 239 output.put(desc); 240 md.update(desc); 241 242 // 2. Generate authenticated extensions. 243 final byte[] authExt = 244 constructFsverityExtensionNative(kRootHashExtensionId, rootHash.length); 245 output.put(authExt); 246 output.put(rootHash); 247 md.update(authExt); 248 md.update(rootHash); 249 250 // 3. Generate unauthenticated extensions. 251 ByteBuffer header = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN); 252 output.putShort((short) 1); // number of unauthenticated extensions below 253 output.position(output.position() + 6); 254 255 // Generate PKCS#7 extension. NB: We do not verify agaist trusted certificate (should be 256 // done by the caller if needed). 257 Path path = Paths.get(pkcs7SignaturePath); 258 if (Files.size(path) > MAX_SIGNATURE_FILE_SIZE_BYTES) { 259 throw new IllegalArgumentException("Signature size is unexpectedly large: " 260 + pkcs7SignaturePath); 261 } 262 final byte[] pkcs7Signature = Files.readAllBytes(path); 263 output.put(constructFsverityExtensionNative(kPkcs7SignatureExtensionId, 264 pkcs7Signature.length)); 265 output.put(pkcs7Signature); 266 267 // 4. Generate the footer. 268 output.put(constructFsverityFooterNative(output.position() - origPosition)); 269 270 return md.digest(); 271 } 272 enableFsverityNative(@onNull String filePath)273 private static native int enableFsverityNative(@NonNull String filePath); measureFsverityNative(@onNull String filePath)274 private static native int measureFsverityNative(@NonNull String filePath); constructFsveritySignedDataNative(@onNull byte[] measurement)275 private static native byte[] constructFsveritySignedDataNative(@NonNull byte[] measurement); constructFsverityDescriptorNative(long fileSize)276 private static native byte[] constructFsverityDescriptorNative(long fileSize); constructFsverityExtensionNative(short extensionId, int extensionDataSize)277 private static native byte[] constructFsverityExtensionNative(short extensionId, 278 int extensionDataSize); constructFsverityFooterNative(int offsetToDescriptorHead)279 private static native byte[] constructFsverityFooterNative(int offsetToDescriptorHead); 280 281 /** 282 * Returns a pair of {@code SharedMemory} and {@code Integer}. The {@code SharedMemory} contains 283 * Merkle tree and fsverity headers for the given apk, in the form that can immediately be used 284 * for fsverity setup. The data is aligned to the beginning of {@code SharedMemory}, and has 285 * length equals to the returned {@code Integer}. 286 */ generateFsVerityIntoSharedMemory(String apkPath, @NonNull byte[] expectedRootHash)287 private static Pair<SharedMemory, Integer> generateFsVerityIntoSharedMemory(String apkPath, 288 @NonNull byte[] expectedRootHash) 289 throws IOException, DigestException, NoSuchAlgorithmException, 290 SignatureNotFoundException { 291 TrackedShmBufferFactory shmBufferFactory = new TrackedShmBufferFactory(); 292 byte[] generatedRootHash = 293 ApkSignatureVerifier.generateApkVerity(apkPath, shmBufferFactory); 294 // We only generate Merkle tree once here, so it's important to make sure the root hash 295 // matches the signed one in the apk. 296 if (!Arrays.equals(expectedRootHash, generatedRootHash)) { 297 throw new SecurityException("verity hash mismatch: " 298 + bytesToString(generatedRootHash) + " != " + bytesToString(expectedRootHash)); 299 } 300 301 int contentSize = shmBufferFactory.getBufferLimit(); 302 SharedMemory shm = shmBufferFactory.releaseSharedMemory(); 303 if (shm == null) { 304 throw new IllegalStateException("Failed to generate verity tree into shared memory"); 305 } 306 if (!shm.setProtect(OsConstants.PROT_READ)) { 307 throw new SecurityException("Failed to set up shared memory correctly"); 308 } 309 return Pair.create(shm, contentSize); 310 } 311 bytesToString(byte[] bytes)312 private static String bytesToString(byte[] bytes) { 313 return HexEncoding.encodeToString(bytes); 314 } 315 316 public static class SetupResult { 317 /** Result code if verity is set up correctly. */ 318 private static final int RESULT_OK = 1; 319 320 /** Result code if signature is not provided. */ 321 private static final int RESULT_SKIPPED = 2; 322 323 /** Result code if the setup failed. */ 324 private static final int RESULT_FAILED = 3; 325 326 private final int mCode; 327 private final FileDescriptor mFileDescriptor; 328 private final int mContentSize; 329 ok(@onNull FileDescriptor fileDescriptor, int contentSize)330 public static SetupResult ok(@NonNull FileDescriptor fileDescriptor, int contentSize) { 331 return new SetupResult(RESULT_OK, fileDescriptor, contentSize); 332 } 333 skipped()334 public static SetupResult skipped() { 335 return new SetupResult(RESULT_SKIPPED, null, -1); 336 } 337 failed()338 public static SetupResult failed() { 339 return new SetupResult(RESULT_FAILED, null, -1); 340 } 341 SetupResult(int code, FileDescriptor fileDescriptor, int contentSize)342 private SetupResult(int code, FileDescriptor fileDescriptor, int contentSize) { 343 this.mCode = code; 344 this.mFileDescriptor = fileDescriptor; 345 this.mContentSize = contentSize; 346 } 347 isFailed()348 public boolean isFailed() { 349 return mCode == RESULT_FAILED; 350 } 351 isOk()352 public boolean isOk() { 353 return mCode == RESULT_OK; 354 } 355 getUnownedFileDescriptor()356 public @NonNull FileDescriptor getUnownedFileDescriptor() { 357 return mFileDescriptor; 358 } 359 getContentSize()360 public int getContentSize() { 361 return mContentSize; 362 } 363 } 364 365 /** A {@code ByteBufferFactory} that creates a shared memory backed {@code ByteBuffer}. */ 366 private static class TrackedShmBufferFactory implements ByteBufferFactory { 367 private SharedMemory mShm; 368 private ByteBuffer mBuffer; 369 370 @Override create(int capacity)371 public ByteBuffer create(int capacity) { 372 try { 373 if (DEBUG) Slog.d(TAG, "Creating shared memory for apk verity"); 374 // NB: This method is supposed to be called once according to the contract with 375 // ApkSignatureSchemeV2Verifier. 376 if (mBuffer != null) { 377 throw new IllegalStateException("Multiple instantiation from this factory"); 378 } 379 mShm = SharedMemory.create("apkverity", capacity); 380 if (!mShm.setProtect(OsConstants.PROT_READ | OsConstants.PROT_WRITE)) { 381 throw new SecurityException("Failed to set protection"); 382 } 383 mBuffer = mShm.mapReadWrite(); 384 return mBuffer; 385 } catch (ErrnoException e) { 386 throw new SecurityException("Failed to set protection", e); 387 } 388 } 389 releaseSharedMemory()390 public SharedMemory releaseSharedMemory() { 391 if (mBuffer != null) { 392 SharedMemory.unmap(mBuffer); 393 mBuffer = null; 394 } 395 SharedMemory tmp = mShm; 396 mShm = null; 397 return tmp; 398 } 399 getBufferLimit()400 public int getBufferLimit() { 401 return mBuffer == null ? -1 : mBuffer.limit(); 402 } 403 } 404 405 /** A {@code ByteBufferFactory} that tracks the {@code ByteBuffer} it creates. */ 406 private static class TrackedBufferFactory implements ByteBufferFactory { 407 private ByteBuffer mBuffer; 408 409 @Override create(int capacity)410 public ByteBuffer create(int capacity) { 411 if (mBuffer != null) { 412 throw new IllegalStateException("Multiple instantiation from this factory"); 413 } 414 mBuffer = ByteBuffer.allocate(capacity); 415 return mBuffer; 416 } 417 getBuffer()418 public ByteBuffer getBuffer() { 419 return mBuffer; 420 } 421 } 422 423 /** Round up the number to the next multiple of the divisor. */ roundUpToNextMultiple(long number, long divisor)424 private static long roundUpToNextMultiple(long number, long divisor) { 425 if (number > (Long.MAX_VALUE - divisor)) { 426 throw new IllegalArgumentException("arithmetic overflow"); 427 } 428 return ((number + (divisor - 1)) / divisor) * divisor; 429 } 430 } 431