• 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.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         int retval = statxForFsverityNative(filePath);
85         if (retval < 0) {
86             Slog.e(TAG, "Failed to check whether fs-verity is enabled, errno " + -retval + ": "
87                     + filePath);
88             return false;
89         }
90         return (retval == 1);
91     }
92 
enableFsverityNative(@onNull String filePath, @NonNull byte[] pkcs7Signature)93     private static native int enableFsverityNative(@NonNull String filePath,
94             @NonNull byte[] pkcs7Signature);
statxForFsverityNative(@onNull String filePath)95     private static native int statxForFsverityNative(@NonNull String filePath);
96 
97     /**
98      * Generates legacy Merkle tree and fs-verity metadata with Signing Block skipped.
99      *
100      * @deprecated This is only used for previous fs-verity implementation, and should never be used
101      *             on new devices.
102      * @return {@code SetupResult} that contains the result code, and when success, the
103      *         {@code FileDescriptor} to read all the data from.
104      */
105     @Deprecated
generateApkVeritySetupData(@onNull String apkPath)106     public static SetupResult generateApkVeritySetupData(@NonNull String apkPath) {
107         if (DEBUG) {
108             Slog.d(TAG, "Trying to install legacy apk verity to " + apkPath);
109         }
110         SharedMemory shm = null;
111         try {
112             final byte[] signedVerityHash = ApkSignatureVerifier.getVerityRootHash(apkPath);
113             if (signedVerityHash == null) {
114                 if (DEBUG) {
115                     Slog.d(TAG, "Skip verity tree generation since there is no signed root hash");
116                 }
117                 return SetupResult.skipped();
118             }
119 
120             Pair<SharedMemory, Integer> result =
121                     generateFsVerityIntoSharedMemory(apkPath, signedVerityHash);
122             shm = result.first;
123             int contentSize = result.second;
124             FileDescriptor rfd = shm.getFileDescriptor();
125             if (rfd == null || !rfd.valid()) {
126                 return SetupResult.failed();
127             }
128             return SetupResult.ok(Os.dup(rfd), contentSize);
129         } catch (IOException | SecurityException | DigestException | NoSuchAlgorithmException |
130                 SignatureNotFoundException | ErrnoException e) {
131             Slog.e(TAG, "Failed to set up apk verity: ", e);
132             return SetupResult.failed();
133         } finally {
134             if (shm != null) {
135                 shm.close();
136             }
137         }
138     }
139 
140     /**
141      * {@see ApkSignatureVerifier#generateApkVerityRootHash(String)}.
142      * @deprecated This is only used for previous fs-verity implementation, and should never be used
143      *             on new devices.
144      */
145     @Deprecated
generateApkVerityRootHash(@onNull String apkPath)146     public static byte[] generateApkVerityRootHash(@NonNull String apkPath)
147             throws NoSuchAlgorithmException, DigestException, IOException {
148         return ApkSignatureVerifier.generateApkVerityRootHash(apkPath);
149     }
150 
151     /**
152      * {@see ApkSignatureVerifier#getVerityRootHash(String)}.
153      * @deprecated This is only used for previous fs-verity implementation, and should never be used
154      *             on new devices.
155      */
156     @Deprecated
getVerityRootHash(@onNull String apkPath)157     public static byte[] getVerityRootHash(@NonNull String apkPath)
158             throws IOException, SignatureNotFoundException {
159         return ApkSignatureVerifier.getVerityRootHash(apkPath);
160     }
161 
162     /**
163      * Returns a pair of {@code SharedMemory} and {@code Integer}. The {@code SharedMemory} contains
164      * Merkle tree and fsverity headers for the given apk, in the form that can immediately be used
165      * for fsverity setup. The data is aligned to the beginning of {@code SharedMemory}, and has
166      * length equals to the returned {@code Integer}.
167      */
generateFsVerityIntoSharedMemory(String apkPath, @NonNull byte[] expectedRootHash)168     private static Pair<SharedMemory, Integer> generateFsVerityIntoSharedMemory(String apkPath,
169             @NonNull byte[] expectedRootHash)
170             throws IOException, DigestException, NoSuchAlgorithmException,
171                    SignatureNotFoundException {
172         TrackedShmBufferFactory shmBufferFactory = new TrackedShmBufferFactory();
173         byte[] generatedRootHash =
174                 ApkSignatureVerifier.generateApkVerity(apkPath, shmBufferFactory);
175         // We only generate Merkle tree once here, so it's important to make sure the root hash
176         // matches the signed one in the apk.
177         if (!Arrays.equals(expectedRootHash, generatedRootHash)) {
178             throw new SecurityException("verity hash mismatch: "
179                     + bytesToString(generatedRootHash) + " != " + bytesToString(expectedRootHash));
180         }
181 
182         int contentSize = shmBufferFactory.getBufferLimit();
183         SharedMemory shm = shmBufferFactory.releaseSharedMemory();
184         if (shm == null) {
185             throw new IllegalStateException("Failed to generate verity tree into shared memory");
186         }
187         if (!shm.setProtect(OsConstants.PROT_READ)) {
188             throw new SecurityException("Failed to set up shared memory correctly");
189         }
190         return Pair.create(shm, contentSize);
191     }
192 
bytesToString(byte[] bytes)193     private static String bytesToString(byte[] bytes) {
194         return HexEncoding.encodeToString(bytes);
195     }
196 
197     /**
198      * @deprecated This is only used for previous fs-verity implementation, and should never be used
199      *             on new devices.
200      */
201     @Deprecated
202     public static class SetupResult {
203         /** Result code if verity is set up correctly. */
204         private static final int RESULT_OK = 1;
205 
206         /** Result code if signature is not provided. */
207         private static final int RESULT_SKIPPED = 2;
208 
209         /** Result code if the setup failed. */
210         private static final int RESULT_FAILED = 3;
211 
212         private final int mCode;
213         private final FileDescriptor mFileDescriptor;
214         private final int mContentSize;
215 
ok(@onNull FileDescriptor fileDescriptor, int contentSize)216         public static SetupResult ok(@NonNull FileDescriptor fileDescriptor, int contentSize) {
217             return new SetupResult(RESULT_OK, fileDescriptor, contentSize);
218         }
219 
skipped()220         public static SetupResult skipped() {
221             return new SetupResult(RESULT_SKIPPED, null, -1);
222         }
223 
failed()224         public static SetupResult failed() {
225             return new SetupResult(RESULT_FAILED, null, -1);
226         }
227 
SetupResult(int code, FileDescriptor fileDescriptor, int contentSize)228         private SetupResult(int code, FileDescriptor fileDescriptor, int contentSize) {
229             this.mCode = code;
230             this.mFileDescriptor = fileDescriptor;
231             this.mContentSize = contentSize;
232         }
233 
isFailed()234         public boolean isFailed() {
235             return mCode == RESULT_FAILED;
236         }
237 
isOk()238         public boolean isOk() {
239             return mCode == RESULT_OK;
240         }
241 
getUnownedFileDescriptor()242         public @NonNull FileDescriptor getUnownedFileDescriptor() {
243             return mFileDescriptor;
244         }
245 
getContentSize()246         public int getContentSize() {
247             return mContentSize;
248         }
249     }
250 
251     /** A {@code ByteBufferFactory} that creates a shared memory backed {@code ByteBuffer}. */
252     private static class TrackedShmBufferFactory implements ByteBufferFactory {
253         private SharedMemory mShm;
254         private ByteBuffer mBuffer;
255 
256         @Override
create(int capacity)257         public ByteBuffer create(int capacity) {
258             try {
259                 if (DEBUG) Slog.d(TAG, "Creating shared memory for apk verity");
260                 // NB: This method is supposed to be called once according to the contract with
261                 // ApkSignatureSchemeV2Verifier.
262                 if (mBuffer != null) {
263                     throw new IllegalStateException("Multiple instantiation from this factory");
264                 }
265                 mShm = SharedMemory.create("apkverity", capacity);
266                 if (!mShm.setProtect(OsConstants.PROT_READ | OsConstants.PROT_WRITE)) {
267                     throw new SecurityException("Failed to set protection");
268                 }
269                 mBuffer = mShm.mapReadWrite();
270                 return mBuffer;
271             } catch (ErrnoException e) {
272                 throw new SecurityException("Failed to set protection", e);
273             }
274         }
275 
releaseSharedMemory()276         public SharedMemory releaseSharedMemory() {
277             if (mBuffer != null) {
278                 SharedMemory.unmap(mBuffer);
279                 mBuffer = null;
280             }
281             SharedMemory tmp = mShm;
282             mShm = null;
283             return tmp;
284         }
285 
getBufferLimit()286         public int getBufferLimit() {
287             return mBuffer == null ? -1 : mBuffer.limit();
288         }
289     }
290 }
291