• 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 import android.util.apk.VerityBuilder;
30 
31 import libcore.util.HexEncoding;
32 
33 import java.io.File;
34 import java.io.FileDescriptor;
35 import java.io.IOException;
36 import java.io.RandomAccessFile;
37 import java.nio.ByteBuffer;
38 import java.nio.ByteOrder;
39 import java.nio.channels.FileChannel;
40 import java.nio.file.Files;
41 import java.nio.file.Path;
42 import java.nio.file.Paths;
43 import java.security.DigestException;
44 import java.security.MessageDigest;
45 import java.security.NoSuchAlgorithmException;
46 import java.util.Arrays;
47 
48 import sun.security.pkcs.PKCS7;
49 
50 /** Provides fsverity related operations. */
51 abstract public class VerityUtils {
52     private static final String TAG = "VerityUtils";
53 
54     /**
55      * File extension of the signature file. For example, foo.apk.fsv_sig is the signature file of
56      * foo.apk.
57      */
58     public static final String FSVERITY_SIGNATURE_FILE_EXTENSION = ".fsv_sig";
59 
60     /** The maximum size of signature file.  This is just to avoid potential abuse. */
61     private static final int MAX_SIGNATURE_FILE_SIZE_BYTES = 8192;
62 
63     private static final int COMMON_LINUX_PAGE_SIZE_IN_BYTES = 4096;
64 
65     private static final boolean DEBUG = false;
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     /** Generates Merkle tree and fs-verity metadata then enables fs-verity. */
setUpFsverity(@onNull String filePath, String signaturePath)78     public static void setUpFsverity(@NonNull String filePath, String signaturePath)
79             throws IOException, DigestException, NoSuchAlgorithmException {
80         final PKCS7 pkcs7 = new PKCS7(Files.readAllBytes(Paths.get(signaturePath)));
81         final byte[] expectedMeasurement = pkcs7.getContentInfo().getContentBytes();
82         if (DEBUG) {
83             Slog.d(TAG, "Enabling fs-verity with signed fs-verity measurement "
84                     + bytesToString(expectedMeasurement));
85             Slog.d(TAG, "PKCS#7 info: " + pkcs7);
86         }
87 
88         final TrackedBufferFactory bufferFactory = new TrackedBufferFactory();
89         final byte[] actualMeasurement = generateFsverityMetadata(filePath, signaturePath,
90                 bufferFactory);
91         try (RandomAccessFile raf = new RandomAccessFile(filePath, "rw")) {
92             FileChannel ch = raf.getChannel();
93             ch.position(roundUpToNextMultiple(ch.size(), COMMON_LINUX_PAGE_SIZE_IN_BYTES));
94             ByteBuffer buffer = bufferFactory.getBuffer();
95 
96             long offset = buffer.position();
97             long size = buffer.limit();
98             while (offset < size) {
99                 long s = ch.write(buffer);
100                 offset += s;
101                 size -= s;
102             }
103         }
104 
105         if (!Arrays.equals(expectedMeasurement, actualMeasurement)) {
106             throw new SecurityException("fs-verity measurement mismatch: "
107                     + bytesToString(actualMeasurement) + " != "
108                     + bytesToString(expectedMeasurement));
109         }
110 
111         // This can fail if the public key is not already in .fs-verity kernel keyring.
112         int errno = enableFsverityNative(filePath);
113         if (errno != 0) {
114             throw new IOException("Failed to enable fs-verity on " + filePath + ": "
115                     + Os.strerror(errno));
116         }
117     }
118 
119     /** Returns whether the file has fs-verity enabled. */
hasFsverity(@onNull String filePath)120     public static boolean hasFsverity(@NonNull String filePath) {
121         // NB: only measure but not check the actual measurement here. As long as this succeeds,
122         // the file is on readable if the measurement can be verified against a trusted key, and
123         // this is good enough for installed apps.
124         int errno = measureFsverityNative(filePath);
125         if (errno != 0) {
126             if (errno != OsConstants.ENODATA) {
127                 Slog.e(TAG, "Failed to measure fs-verity, errno " + errno + ": " + filePath);
128             }
129             return false;
130         }
131         return true;
132     }
133 
134     /**
135      * Generates legacy Merkle tree and fs-verity metadata with Signing Block skipped.
136      *
137      * @return {@code SetupResult} that contains the result code, and when success, the
138      *         {@code FileDescriptor} to read all the data from.
139      */
generateApkVeritySetupData(@onNull String apkPath)140     public static SetupResult generateApkVeritySetupData(@NonNull String apkPath) {
141         if (DEBUG) {
142             Slog.d(TAG, "Trying to install legacy apk verity to " + apkPath);
143         }
144         SharedMemory shm = null;
145         try {
146             final byte[] signedVerityHash = ApkSignatureVerifier.getVerityRootHash(apkPath);
147             if (signedVerityHash == null) {
148                 if (DEBUG) {
149                     Slog.d(TAG, "Skip verity tree generation since there is no signed root hash");
150                 }
151                 return SetupResult.skipped();
152             }
153 
154             Pair<SharedMemory, Integer> result =
155                     generateFsVerityIntoSharedMemory(apkPath, signedVerityHash);
156             shm = result.first;
157             int contentSize = result.second;
158             FileDescriptor rfd = shm.getFileDescriptor();
159             if (rfd == null || !rfd.valid()) {
160                 return SetupResult.failed();
161             }
162             return SetupResult.ok(Os.dup(rfd), contentSize);
163         } catch (IOException | SecurityException | DigestException | NoSuchAlgorithmException |
164                 SignatureNotFoundException | ErrnoException e) {
165             Slog.e(TAG, "Failed to set up apk verity: ", e);
166             return SetupResult.failed();
167         } finally {
168             if (shm != null) {
169                 shm.close();
170             }
171         }
172     }
173 
174     /**
175      * {@see ApkSignatureVerifier#generateApkVerityRootHash(String)}.
176      */
generateApkVerityRootHash(@onNull String apkPath)177     public static byte[] generateApkVerityRootHash(@NonNull String apkPath)
178             throws NoSuchAlgorithmException, DigestException, IOException {
179         return ApkSignatureVerifier.generateApkVerityRootHash(apkPath);
180     }
181 
182     /**
183      * {@see ApkSignatureVerifier#getVerityRootHash(String)}.
184      */
getVerityRootHash(@onNull String apkPath)185     public static byte[] getVerityRootHash(@NonNull String apkPath)
186             throws IOException, SignatureNotFoundException {
187         return ApkSignatureVerifier.getVerityRootHash(apkPath);
188     }
189 
190     /**
191      * Generates fs-verity metadata for {@code filePath} in the buffer created by {@code
192      * trackedBufferFactory}. The metadata contains the Merkle tree, fs-verity descriptor and
193      * extensions, including a PKCS#7 signature provided in {@code signaturePath}.
194      *
195      * <p>It is worthy to note that {@code trackedBufferFactory} generates a "tracked" {@code
196      * ByteBuffer}. The data will be used outside this method via the factory itself.
197      *
198      * @return fs-verity signed data (struct fsverity_digest_disk) of {@code filePath}, which
199      *         includes SHA-256 of fs-verity descriptor and authenticated extensions.
200      */
generateFsverityMetadata(String filePath, String signaturePath, @NonNull ByteBufferFactory trackedBufferFactory)201     private static byte[] generateFsverityMetadata(String filePath, String signaturePath,
202             @NonNull ByteBufferFactory trackedBufferFactory)
203             throws IOException, DigestException, NoSuchAlgorithmException {
204         try (RandomAccessFile file = new RandomAccessFile(filePath, "r")) {
205             VerityBuilder.VerityResult result = VerityBuilder.generateFsVerityTree(
206                     file, trackedBufferFactory);
207 
208             ByteBuffer buffer = result.verityData;
209             buffer.position(result.merkleTreeSize);
210 
211             final byte[] measurement = generateFsverityDescriptorAndMeasurement(file,
212                     result.rootHash, signaturePath, buffer);
213             buffer.flip();
214             return constructFsveritySignedDataNative(measurement);
215         }
216     }
217 
218     /**
219      * Generates fs-verity descriptor including the extensions to the {@code output} and returns the
220      * fs-verity measurement.
221      *
222      * @return fs-verity measurement, which is a SHA-256 of fs-verity descriptor and authenticated
223      *         extensions.
224      */
generateFsverityDescriptorAndMeasurement( @onNull RandomAccessFile file, @NonNull byte[] rootHash, @NonNull String pkcs7SignaturePath, @NonNull ByteBuffer output)225     private static byte[] generateFsverityDescriptorAndMeasurement(
226             @NonNull RandomAccessFile file, @NonNull byte[] rootHash,
227             @NonNull String pkcs7SignaturePath, @NonNull ByteBuffer output)
228             throws IOException, NoSuchAlgorithmException, DigestException {
229         final short kRootHashExtensionId = 1;
230         final short kPkcs7SignatureExtensionId = 3;
231         final int origPosition = output.position();
232 
233         // For generating fs-verity file measurement, which consists of the descriptor and
234         // authenticated extensions (but not unauthenticated extensions and the footer).
235         MessageDigest md = MessageDigest.getInstance("SHA-256");
236 
237         // 1. Generate fs-verity descriptor.
238         final byte[] desc = constructFsverityDescriptorNative(file.length());
239         output.put(desc);
240         md.update(desc);
241 
242         // 2. Generate authenticated extensions.
243         final byte[] authExt =
244                 constructFsverityExtensionNative(kRootHashExtensionId, rootHash.length);
245         output.put(authExt);
246         output.put(rootHash);
247         md.update(authExt);
248         md.update(rootHash);
249 
250         // 3. Generate unauthenticated extensions.
251         ByteBuffer header = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN);
252         output.putShort((short) 1);  // number of unauthenticated extensions below
253         output.position(output.position() + 6);
254 
255         // Generate PKCS#7 extension. NB: We do not verify agaist trusted certificate (should be
256         // done by the caller if needed).
257         Path path = Paths.get(pkcs7SignaturePath);
258         if (Files.size(path) > MAX_SIGNATURE_FILE_SIZE_BYTES) {
259             throw new IllegalArgumentException("Signature size is unexpectedly large: "
260                     + pkcs7SignaturePath);
261         }
262         final byte[] pkcs7Signature = Files.readAllBytes(path);
263         output.put(constructFsverityExtensionNative(kPkcs7SignatureExtensionId,
264                     pkcs7Signature.length));
265         output.put(pkcs7Signature);
266 
267         // 4. Generate the footer.
268         output.put(constructFsverityFooterNative(output.position() - origPosition));
269 
270         return md.digest();
271     }
272 
enableFsverityNative(@onNull String filePath)273     private static native int enableFsverityNative(@NonNull String filePath);
measureFsverityNative(@onNull String filePath)274     private static native int measureFsverityNative(@NonNull String filePath);
constructFsveritySignedDataNative(@onNull byte[] measurement)275     private static native byte[] constructFsveritySignedDataNative(@NonNull byte[] measurement);
constructFsverityDescriptorNative(long fileSize)276     private static native byte[] constructFsverityDescriptorNative(long fileSize);
constructFsverityExtensionNative(short extensionId, int extensionDataSize)277     private static native byte[] constructFsverityExtensionNative(short extensionId,
278             int extensionDataSize);
constructFsverityFooterNative(int offsetToDescriptorHead)279     private static native byte[] constructFsverityFooterNative(int offsetToDescriptorHead);
280 
281     /**
282      * Returns a pair of {@code SharedMemory} and {@code Integer}. The {@code SharedMemory} contains
283      * Merkle tree and fsverity headers for the given apk, in the form that can immediately be used
284      * for fsverity setup. The data is aligned to the beginning of {@code SharedMemory}, and has
285      * length equals to the returned {@code Integer}.
286      */
generateFsVerityIntoSharedMemory(String apkPath, @NonNull byte[] expectedRootHash)287     private static Pair<SharedMemory, Integer> generateFsVerityIntoSharedMemory(String apkPath,
288             @NonNull byte[] expectedRootHash)
289             throws IOException, DigestException, NoSuchAlgorithmException,
290                    SignatureNotFoundException {
291         TrackedShmBufferFactory shmBufferFactory = new TrackedShmBufferFactory();
292         byte[] generatedRootHash =
293                 ApkSignatureVerifier.generateApkVerity(apkPath, shmBufferFactory);
294         // We only generate Merkle tree once here, so it's important to make sure the root hash
295         // matches the signed one in the apk.
296         if (!Arrays.equals(expectedRootHash, generatedRootHash)) {
297             throw new SecurityException("verity hash mismatch: "
298                     + bytesToString(generatedRootHash) + " != " + bytesToString(expectedRootHash));
299         }
300 
301         int contentSize = shmBufferFactory.getBufferLimit();
302         SharedMemory shm = shmBufferFactory.releaseSharedMemory();
303         if (shm == null) {
304             throw new IllegalStateException("Failed to generate verity tree into shared memory");
305         }
306         if (!shm.setProtect(OsConstants.PROT_READ)) {
307             throw new SecurityException("Failed to set up shared memory correctly");
308         }
309         return Pair.create(shm, contentSize);
310     }
311 
bytesToString(byte[] bytes)312     private static String bytesToString(byte[] bytes) {
313         return HexEncoding.encodeToString(bytes);
314     }
315 
316     public static class SetupResult {
317         /** Result code if verity is set up correctly. */
318         private static final int RESULT_OK = 1;
319 
320         /** Result code if signature is not provided. */
321         private static final int RESULT_SKIPPED = 2;
322 
323         /** Result code if the setup failed. */
324         private static final int RESULT_FAILED = 3;
325 
326         private final int mCode;
327         private final FileDescriptor mFileDescriptor;
328         private final int mContentSize;
329 
ok(@onNull FileDescriptor fileDescriptor, int contentSize)330         public static SetupResult ok(@NonNull FileDescriptor fileDescriptor, int contentSize) {
331             return new SetupResult(RESULT_OK, fileDescriptor, contentSize);
332         }
333 
skipped()334         public static SetupResult skipped() {
335             return new SetupResult(RESULT_SKIPPED, null, -1);
336         }
337 
failed()338         public static SetupResult failed() {
339             return new SetupResult(RESULT_FAILED, null, -1);
340         }
341 
SetupResult(int code, FileDescriptor fileDescriptor, int contentSize)342         private SetupResult(int code, FileDescriptor fileDescriptor, int contentSize) {
343             this.mCode = code;
344             this.mFileDescriptor = fileDescriptor;
345             this.mContentSize = contentSize;
346         }
347 
isFailed()348         public boolean isFailed() {
349             return mCode == RESULT_FAILED;
350         }
351 
isOk()352         public boolean isOk() {
353             return mCode == RESULT_OK;
354         }
355 
getUnownedFileDescriptor()356         public @NonNull FileDescriptor getUnownedFileDescriptor() {
357             return mFileDescriptor;
358         }
359 
getContentSize()360         public int getContentSize() {
361             return mContentSize;
362         }
363     }
364 
365     /** A {@code ByteBufferFactory} that creates a shared memory backed {@code ByteBuffer}. */
366     private static class TrackedShmBufferFactory implements ByteBufferFactory {
367         private SharedMemory mShm;
368         private ByteBuffer mBuffer;
369 
370         @Override
create(int capacity)371         public ByteBuffer create(int capacity) {
372             try {
373                 if (DEBUG) Slog.d(TAG, "Creating shared memory for apk verity");
374                 // NB: This method is supposed to be called once according to the contract with
375                 // ApkSignatureSchemeV2Verifier.
376                 if (mBuffer != null) {
377                     throw new IllegalStateException("Multiple instantiation from this factory");
378                 }
379                 mShm = SharedMemory.create("apkverity", capacity);
380                 if (!mShm.setProtect(OsConstants.PROT_READ | OsConstants.PROT_WRITE)) {
381                     throw new SecurityException("Failed to set protection");
382                 }
383                 mBuffer = mShm.mapReadWrite();
384                 return mBuffer;
385             } catch (ErrnoException e) {
386                 throw new SecurityException("Failed to set protection", e);
387             }
388         }
389 
releaseSharedMemory()390         public SharedMemory releaseSharedMemory() {
391             if (mBuffer != null) {
392                 SharedMemory.unmap(mBuffer);
393                 mBuffer = null;
394             }
395             SharedMemory tmp = mShm;
396             mShm = null;
397             return tmp;
398         }
399 
getBufferLimit()400         public int getBufferLimit() {
401             return mBuffer == null ? -1 : mBuffer.limit();
402         }
403     }
404 
405     /** A {@code ByteBufferFactory} that tracks the {@code ByteBuffer} it creates. */
406     private static class TrackedBufferFactory implements ByteBufferFactory {
407         private ByteBuffer mBuffer;
408 
409         @Override
create(int capacity)410         public ByteBuffer create(int capacity) {
411             if (mBuffer != null) {
412                 throw new IllegalStateException("Multiple instantiation from this factory");
413             }
414             mBuffer = ByteBuffer.allocate(capacity);
415             return mBuffer;
416         }
417 
getBuffer()418         public ByteBuffer getBuffer() {
419             return mBuffer;
420         }
421     }
422 
423     /** Round up the number to the next multiple of the divisor. */
roundUpToNextMultiple(long number, long divisor)424     private static long roundUpToNextMultiple(long number, long divisor) {
425         if (number > (Long.MAX_VALUE - divisor)) {
426             throw new IllegalArgumentException("arithmetic overflow");
427         }
428         return ((number + (divisor - 1)) / divisor) * divisor;
429     }
430 }
431