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