• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.integrity;
18 
19 import android.annotation.Nullable;
20 import android.content.integrity.AppInstallMetadata;
21 import android.content.integrity.Rule;
22 import android.os.Environment;
23 import android.util.Slog;
24 
25 import com.android.internal.annotations.VisibleForTesting;
26 import com.android.server.integrity.model.RuleMetadata;
27 import com.android.server.integrity.parser.RandomAccessObject;
28 import com.android.server.integrity.parser.RuleBinaryParser;
29 import com.android.server.integrity.parser.RuleIndexRange;
30 import com.android.server.integrity.parser.RuleIndexingController;
31 import com.android.server.integrity.parser.RuleMetadataParser;
32 import com.android.server.integrity.parser.RuleParseException;
33 import com.android.server.integrity.parser.RuleParser;
34 import com.android.server.integrity.serializer.RuleBinarySerializer;
35 import com.android.server.integrity.serializer.RuleMetadataSerializer;
36 import com.android.server.integrity.serializer.RuleSerializeException;
37 import com.android.server.integrity.serializer.RuleSerializer;
38 
39 import java.io.File;
40 import java.io.FileInputStream;
41 import java.io.FileOutputStream;
42 import java.io.IOException;
43 import java.util.Collections;
44 import java.util.List;
45 import java.util.Optional;
46 
47 /** Abstraction over the underlying storage of rules and other metadata. */
48 public class IntegrityFileManager {
49     private static final String TAG = "IntegrityFileManager";
50 
51     private static final String METADATA_FILE = "metadata";
52     private static final String RULES_FILE = "rules";
53     private static final String INDEXING_FILE = "indexing";
54     private static final Object RULES_LOCK = new Object();
55 
56     private static IntegrityFileManager sInstance = null;
57 
58     private final RuleParser mRuleParser;
59     private final RuleSerializer mRuleSerializer;
60 
61     private final File mDataDir;
62     // mRulesDir contains data of the actual rules currently stored.
63     private final File mRulesDir;
64     // mStagingDir is used to store the temporary rules / metadata during updating, since we want to
65     // update rules atomically.
66     private final File mStagingDir;
67 
68     @Nullable private RuleMetadata mRuleMetadataCache;
69     @Nullable private RuleIndexingController mRuleIndexingController;
70 
71     /** Get the singleton instance of this class. */
getInstance()72     public static synchronized IntegrityFileManager getInstance() {
73         if (sInstance == null) {
74             sInstance = new IntegrityFileManager();
75         }
76         return sInstance;
77     }
78 
IntegrityFileManager()79     private IntegrityFileManager() {
80         this(
81                 new RuleBinaryParser(),
82                 new RuleBinarySerializer(),
83                 Environment.getDataSystemDirectory());
84     }
85 
86     @VisibleForTesting
IntegrityFileManager(RuleParser ruleParser, RuleSerializer ruleSerializer, File dataDir)87     IntegrityFileManager(RuleParser ruleParser, RuleSerializer ruleSerializer, File dataDir) {
88         mRuleParser = ruleParser;
89         mRuleSerializer = ruleSerializer;
90         mDataDir = dataDir;
91 
92         mRulesDir = new File(dataDir, "integrity_rules");
93         mStagingDir = new File(dataDir, "integrity_staging");
94 
95         if (!mStagingDir.mkdirs() || !mRulesDir.mkdirs()) {
96             Slog.e(TAG, "Error creating staging and rules directory");
97             // TODO: maybe throw an exception?
98         }
99 
100         File metadataFile = new File(mRulesDir, METADATA_FILE);
101         if (metadataFile.exists()) {
102             try (FileInputStream inputStream = new FileInputStream(metadataFile)) {
103                 mRuleMetadataCache = RuleMetadataParser.parse(inputStream);
104             } catch (Exception e) {
105                 Slog.e(TAG, "Error reading metadata file.", e);
106             }
107         }
108 
109         updateRuleIndexingController();
110     }
111 
112     /**
113      * Returns if the rules have been initialized.
114      *
115      * <p>Used to fail early if there are no rules (so we don't need to parse the apk at all).
116      */
initialized()117     public boolean initialized() {
118         return new File(mRulesDir, RULES_FILE).exists()
119                 && new File(mRulesDir, METADATA_FILE).exists()
120                 && new File(mRulesDir, INDEXING_FILE).exists();
121     }
122 
123     /** Write rules to persistent storage. */
writeRules(String version, String ruleProvider, List<Rule> rules)124     public void writeRules(String version, String ruleProvider, List<Rule> rules)
125             throws IOException, RuleSerializeException {
126         try {
127             writeMetadata(mStagingDir, ruleProvider, version);
128         } catch (IOException e) {
129             Slog.e(TAG, "Error writing metadata.", e);
130             // We don't consider this fatal so we continue execution.
131         }
132 
133         try (FileOutputStream ruleFileOutputStream =
134                         new FileOutputStream(new File(mStagingDir, RULES_FILE));
135                 FileOutputStream indexingFileOutputStream =
136                         new FileOutputStream(new File(mStagingDir, INDEXING_FILE))) {
137             mRuleSerializer.serialize(
138                     rules, Optional.empty(), ruleFileOutputStream, indexingFileOutputStream);
139         }
140 
141         switchStagingRulesDir();
142 
143         // Update object holding the indexing information.
144         updateRuleIndexingController();
145     }
146 
147     /**
148      * Read rules from persistent storage.
149      *
150      * @param appInstallMetadata information about the install used to select rules to read. If
151      *     null, all rules will be read.
152      */
readRules(@ullable AppInstallMetadata appInstallMetadata)153     public List<Rule> readRules(@Nullable AppInstallMetadata appInstallMetadata)
154             throws IOException, RuleParseException {
155         synchronized (RULES_LOCK) {
156             // Try to identify indexes from the index file.
157             List<RuleIndexRange> ruleReadingIndexes = Collections.emptyList();
158             if (appInstallMetadata != null) {
159                 try {
160                     ruleReadingIndexes =
161                             mRuleIndexingController.identifyRulesToEvaluate(appInstallMetadata);
162                 } catch (Exception e) {
163                     Slog.w(TAG, "Error identifying the rule indexes. Trying unindexed.", e);
164                 }
165             }
166 
167             // Read the rules based on the index information when available.
168             File ruleFile = new File(mRulesDir, RULES_FILE);
169             List<Rule> rules =
170                     mRuleParser.parse(RandomAccessObject.ofFile(ruleFile), ruleReadingIndexes);
171             return rules;
172         }
173     }
174 
175     /** Read the metadata of the current rules in storage. */
176     @Nullable
readMetadata()177     public RuleMetadata readMetadata() {
178         return mRuleMetadataCache;
179     }
180 
switchStagingRulesDir()181     private void switchStagingRulesDir() throws IOException {
182         synchronized (RULES_LOCK) {
183             File tmpDir = new File(mDataDir, "temp");
184 
185             if (!(mRulesDir.renameTo(tmpDir)
186                     && mStagingDir.renameTo(mRulesDir)
187                     && tmpDir.renameTo(mStagingDir))) {
188                 throw new IOException("Error switching staging/rules directory");
189             }
190 
191             for (File file : mStagingDir.listFiles()) {
192                 file.delete();
193             }
194         }
195     }
196 
updateRuleIndexingController()197     private void updateRuleIndexingController() {
198         File ruleIndexingFile = new File(mRulesDir, INDEXING_FILE);
199         if (ruleIndexingFile.exists()) {
200             try (FileInputStream inputStream = new FileInputStream(ruleIndexingFile)) {
201                 mRuleIndexingController = new RuleIndexingController(inputStream);
202             } catch (Exception e) {
203                 Slog.e(TAG, "Error parsing the rule indexing file.", e);
204             }
205         }
206     }
207 
writeMetadata(File directory, String ruleProvider, String version)208     private void writeMetadata(File directory, String ruleProvider, String version)
209             throws IOException {
210         mRuleMetadataCache = new RuleMetadata(ruleProvider, version);
211 
212         File metadataFile = new File(directory, METADATA_FILE);
213 
214         try (FileOutputStream outputStream = new FileOutputStream(metadataFile)) {
215             RuleMetadataSerializer.serialize(mRuleMetadataCache, outputStream);
216         }
217     }
218 }
219