Home | History | Annotate | Download | only in security
      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      */
     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      */
     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      */
     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      */
    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 
    143         public static SetupResult ok(@NonNull FileDescriptor fileDescriptor, int contentSize) {
    144             return new SetupResult(RESULT_OK, fileDescriptor, contentSize);
    145         }
    146 
    147         public static SetupResult skipped() {
    148             return new SetupResult(RESULT_SKIPPED, null, -1);
    149         }
    150 
    151         public static SetupResult failed() {
    152             return new SetupResult(RESULT_FAILED, null, -1);
    153         }
    154 
    155         private SetupResult(int code, FileDescriptor fileDescriptor, int contentSize) {
    156             this.mCode = code;
    157             this.mFileDescriptor = fileDescriptor;
    158             this.mContentSize = contentSize;
    159         }
    160 
    161         public boolean isFailed() {
    162             return mCode == RESULT_FAILED;
    163         }
    164 
    165         public boolean isOk() {
    166             return mCode == RESULT_OK;
    167         }
    168 
    169         public @NonNull FileDescriptor getUnownedFileDescriptor() {
    170             return mFileDescriptor;
    171         }
    172 
    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
    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 
    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 
    213         public int getBufferLimit() {
    214             return mBuffer == null ? -1 : mBuffer.limit();
    215         }
    216     }
    217 }
    218