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