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