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