1 /*
2  * Copyright (C) 2023 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.fsverity;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assert.assertThrows;
22 
23 import android.content.Context;
24 import android.security.FileIntegrityManager;
25 import android.util.Log;
26 
27 import androidx.test.core.app.ApplicationProvider;
28 import androidx.test.platform.app.InstrumentationRegistry;
29 
30 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
31 
32 import org.junit.Rule;
33 import org.junit.Test;
34 
35 import java.io.FileOutputStream;
36 import java.io.IOException;
37 import java.io.RandomAccessFile;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 
41 /**
42  * Test helper that works with the host-side test to set up a test file, and to verify fs-verity
43  * verification is done expectedly.
44  */
45 public class Helper {
46     private static final String TAG = "FsVerityTest";
47 
48     private static final String FILENAME = "test.file";
49 
50     private static final long BLOCK_SIZE = 4096;
51 
52     @Rule
53     public final AdoptShellPermissionsRule mAdoptShellPermissionsRule =
54             new AdoptShellPermissionsRule(
55                     InstrumentationRegistry.getInstrumentation().getUiAutomation(),
56                     android.Manifest.permission.SETUP_FSVERITY);
57 
58     @Test
prepareTest()59     public void prepareTest() throws Exception {
60         Context context = ApplicationProvider.getApplicationContext();
61         android.os.Bundle testArgs = InstrumentationRegistry.getArguments();
62 
63         String basename = testArgs.getString("basename");
64         context.deleteFile(basename);
65 
66         assertThat(testArgs).isNotNull();
67         int fileSize = Integer.parseInt(testArgs.getString("fileSize"));
68         Log.d(TAG, "Preparing test file with size " + fileSize);
69 
70         byte[] bytes = new byte[8192];
71         Arrays.fill(bytes, (byte) '1');
72         try (FileOutputStream os = context.openFileOutput(basename, Context.MODE_PRIVATE)) {
73             for (int i = 0; i < fileSize; i += bytes.length) {
74                 if (i + bytes.length > fileSize) {
75                     os.write(bytes, 0, fileSize % bytes.length);
76                 } else {
77                     os.write(bytes);
78                 }
79             }
80         }
81 
82         // Enable fs-verity
83         FileIntegrityManager fim = context.getSystemService(FileIntegrityManager.class);
84         fim.setupFsVerity(context.getFileStreamPath(basename));
85     }
86 
87     @Test
verifyFileRead()88     public void verifyFileRead() throws Exception {
89         Context context = ApplicationProvider.getApplicationContext();
90 
91         // Collect indices that the backing blocks are supposed to be corrupted.
92         android.os.Bundle testArgs = InstrumentationRegistry.getArguments();
93         assertThat(testArgs).isNotNull();
94         String filePath = testArgs.getString("filePath");
95         String csv = testArgs.getString("brokenBlockIndicesCsv");
96         Log.d(TAG, "brokenBlockIndicesCsv: " + csv);
97         String[] strings = csv.split(",");
98         var corrupted = new ArrayList(strings.length);
99         for (int i = 0; i < strings.length; i++) {
100             corrupted.add(Integer.parseInt(strings[i]));
101         }
102 
103         // Expect the read to succeed or fail per the prior.
104         try (var file = new RandomAccessFile(filePath, "r")) {
105             long total_blocks = (file.length() + BLOCK_SIZE - 1) / BLOCK_SIZE;
106             for (int i = 0; i < (int) total_blocks; i++) {
107                 file.seek(i * BLOCK_SIZE);
108                 if (corrupted.contains(i)) {
109                     Log.d(TAG, "Expecting read at block #" + i + " to fail");
110                     assertThrows(IOException.class, () -> file.read());
111                 } else {
112                     assertThat(file.readByte()).isEqualTo('1');
113                 }
114             }
115         }
116     }
117 }
118