Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f76196eb authored by Ömer Yaveroğlu's avatar Ömer Yaveroğlu Committed by Automerger Merge Worker
Browse files

Merge "Remove the IntegrityFileManager class that is unused after cleaning up...

Merge "Remove the IntegrityFileManager class that is unused after cleaning up references from AppIntegrityManagerServiceImpl." into main am: 454a2140 am: 364e9eed

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/3315773



Change-Id: I1e66e5a7b4517929361e5ad04dd9acdabdb18513
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 0826db1a 364e9eed
Loading
Loading
Loading
Loading
+0 −218
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.integrity;

import android.annotation.Nullable;
import android.content.integrity.AppInstallMetadata;
import android.content.integrity.Rule;
import android.os.Environment;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.integrity.model.RuleMetadata;
import com.android.server.integrity.parser.RandomAccessObject;
import com.android.server.integrity.parser.RuleBinaryParser;
import com.android.server.integrity.parser.RuleIndexRange;
import com.android.server.integrity.parser.RuleIndexingController;
import com.android.server.integrity.parser.RuleMetadataParser;
import com.android.server.integrity.parser.RuleParseException;
import com.android.server.integrity.parser.RuleParser;
import com.android.server.integrity.serializer.RuleBinarySerializer;
import com.android.server.integrity.serializer.RuleMetadataSerializer;
import com.android.server.integrity.serializer.RuleSerializeException;
import com.android.server.integrity.serializer.RuleSerializer;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

/** Abstraction over the underlying storage of rules and other metadata. */
public class IntegrityFileManager {
    private static final String TAG = "IntegrityFileManager";

    private static final String METADATA_FILE = "metadata";
    private static final String RULES_FILE = "rules";
    private static final String INDEXING_FILE = "indexing";
    private static final Object RULES_LOCK = new Object();

    private static IntegrityFileManager sInstance = null;

    private final RuleParser mRuleParser;
    private final RuleSerializer mRuleSerializer;

    private final File mDataDir;
    // mRulesDir contains data of the actual rules currently stored.
    private final File mRulesDir;
    // mStagingDir is used to store the temporary rules / metadata during updating, since we want to
    // update rules atomically.
    private final File mStagingDir;

    @Nullable private RuleMetadata mRuleMetadataCache;
    @Nullable private RuleIndexingController mRuleIndexingController;

    /** Get the singleton instance of this class. */
    public static synchronized IntegrityFileManager getInstance() {
        if (sInstance == null) {
            sInstance = new IntegrityFileManager();
        }
        return sInstance;
    }

    private IntegrityFileManager() {
        this(
                new RuleBinaryParser(),
                new RuleBinarySerializer(),
                Environment.getDataSystemDirectory());
    }

    @VisibleForTesting
    IntegrityFileManager(RuleParser ruleParser, RuleSerializer ruleSerializer, File dataDir) {
        mRuleParser = ruleParser;
        mRuleSerializer = ruleSerializer;
        mDataDir = dataDir;

        mRulesDir = new File(dataDir, "integrity_rules");
        mStagingDir = new File(dataDir, "integrity_staging");

        if (!mStagingDir.mkdirs() || !mRulesDir.mkdirs()) {
            Slog.e(TAG, "Error creating staging and rules directory");
            // TODO: maybe throw an exception?
        }

        File metadataFile = new File(mRulesDir, METADATA_FILE);
        if (metadataFile.exists()) {
            try (FileInputStream inputStream = new FileInputStream(metadataFile)) {
                mRuleMetadataCache = RuleMetadataParser.parse(inputStream);
            } catch (Exception e) {
                Slog.e(TAG, "Error reading metadata file.", e);
            }
        }

        updateRuleIndexingController();
    }

    /**
     * Returns if the rules have been initialized.
     *
     * <p>Used to fail early if there are no rules (so we don't need to parse the apk at all).
     */
    public boolean initialized() {
        return new File(mRulesDir, RULES_FILE).exists()
                && new File(mRulesDir, METADATA_FILE).exists()
                && new File(mRulesDir, INDEXING_FILE).exists();
    }

    /** Write rules to persistent storage. */
    public void writeRules(String version, String ruleProvider, List<Rule> rules)
            throws IOException, RuleSerializeException {
        try {
            writeMetadata(mStagingDir, ruleProvider, version);
        } catch (IOException e) {
            Slog.e(TAG, "Error writing metadata.", e);
            // We don't consider this fatal so we continue execution.
        }

        try (FileOutputStream ruleFileOutputStream =
                        new FileOutputStream(new File(mStagingDir, RULES_FILE));
                FileOutputStream indexingFileOutputStream =
                        new FileOutputStream(new File(mStagingDir, INDEXING_FILE))) {
            mRuleSerializer.serialize(
                    rules, Optional.empty(), ruleFileOutputStream, indexingFileOutputStream);
        }

        switchStagingRulesDir();

        // Update object holding the indexing information.
        updateRuleIndexingController();
    }

    /**
     * Read rules from persistent storage.
     *
     * @param appInstallMetadata information about the install used to select rules to read. If
     *     null, all rules will be read.
     */
    public List<Rule> readRules(@Nullable AppInstallMetadata appInstallMetadata)
            throws IOException, RuleParseException {
        synchronized (RULES_LOCK) {
            // Try to identify indexes from the index file.
            List<RuleIndexRange> ruleReadingIndexes = Collections.emptyList();
            if (appInstallMetadata != null) {
                try {
                    ruleReadingIndexes =
                            mRuleIndexingController.identifyRulesToEvaluate(appInstallMetadata);
                } catch (Exception e) {
                    Slog.w(TAG, "Error identifying the rule indexes. Trying unindexed.", e);
                }
            }

            // Read the rules based on the index information when available.
            File ruleFile = new File(mRulesDir, RULES_FILE);
            List<Rule> rules =
                    mRuleParser.parse(RandomAccessObject.ofFile(ruleFile), ruleReadingIndexes);
            return rules;
        }
    }

    /** Read the metadata of the current rules in storage. */
    @Nullable
    public RuleMetadata readMetadata() {
        return mRuleMetadataCache;
    }

    private void switchStagingRulesDir() throws IOException {
        synchronized (RULES_LOCK) {
            File tmpDir = new File(mDataDir, "temp");

            if (!(mRulesDir.renameTo(tmpDir)
                    && mStagingDir.renameTo(mRulesDir)
                    && tmpDir.renameTo(mStagingDir))) {
                throw new IOException("Error switching staging/rules directory");
            }

            for (File file : mStagingDir.listFiles()) {
                file.delete();
            }
        }
    }

    private void updateRuleIndexingController() {
        File ruleIndexingFile = new File(mRulesDir, INDEXING_FILE);
        if (ruleIndexingFile.exists()) {
            try (FileInputStream inputStream = new FileInputStream(ruleIndexingFile)) {
                mRuleIndexingController = new RuleIndexingController(inputStream);
            } catch (Exception e) {
                Slog.e(TAG, "Error parsing the rule indexing file.", e);
            }
        }
    }

    private void writeMetadata(File directory, String ruleProvider, String version)
            throws IOException {
        mRuleMetadataCache = new RuleMetadata(ruleProvider, version);

        File metadataFile = new File(directory, METADATA_FILE);

        try (FileOutputStream outputStream = new FileOutputStream(metadataFile)) {
            RuleMetadataSerializer.serialize(mRuleMetadataCache, outputStream);
        }
    }
}
+0 −243
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.integrity;

import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE;

import static com.google.common.truth.Truth.assertThat;

import android.content.integrity.AppInstallMetadata;
import android.content.integrity.AtomicFormula;
import android.content.integrity.AtomicFormula.LongAtomicFormula;
import android.content.integrity.AtomicFormula.StringAtomicFormula;
import android.content.integrity.CompoundFormula;
import android.content.integrity.Rule;
import android.util.Slog;

import com.android.server.integrity.parser.RuleBinaryParser;
import com.android.server.integrity.serializer.RuleBinarySerializer;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/** Unit test for {@link IntegrityFileManager} */
@RunWith(JUnit4.class)
public class IntegrityFileManagerTest {
    private static final String TAG = "IntegrityFileManagerTest";

    private static final String VERSION = "version";
    private static final String RULE_PROVIDER = "rule_provider";

    private File mTmpDir;

    // under test
    private IntegrityFileManager mIntegrityFileManager;

    @Before
    public void setUp() throws Exception {
        mTmpDir = Files.createTempDirectory("IntegrityFileManagerTest").toFile();
        Slog.i(TAG, "Using temp directory " + mTmpDir);

        // Use Xml Parser/Serializer to help with debugging since we can just print the file.
        mIntegrityFileManager =
                new IntegrityFileManager(
                        new RuleBinaryParser(), new RuleBinarySerializer(), mTmpDir);
        Files.walk(mTmpDir.toPath())
                .forEach(
                        path -> {
                            Slog.i(TAG, "before " + path);
                        });
    }

    @After
    public void tearDown() throws Exception {
        Files.walk(mTmpDir.toPath())
                .forEach(
                        path -> {
                            Slog.i(TAG, "after " + path);
                        });
        // Sorting paths in reverse order guarantees that we delete inside files before deleting
        // directory.
        Files.walk(mTmpDir.toPath())
                .sorted(Comparator.reverseOrder())
                .map(Path::toFile)
                .forEach(File::delete);
    }

    @Test
    public void testGetMetadata() throws Exception {
        assertThat(mIntegrityFileManager.readMetadata()).isNull();
        mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);

        assertThat(mIntegrityFileManager.readMetadata()).isNotNull();
        assertThat(mIntegrityFileManager.readMetadata().getVersion()).isEqualTo(VERSION);
        assertThat(mIntegrityFileManager.readMetadata().getRuleProvider()).isEqualTo(RULE_PROVIDER);
    }

    @Test
    public void testIsInitialized() throws Exception {
        assertThat(mIntegrityFileManager.initialized()).isFalse();
        mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
        assertThat(mIntegrityFileManager.initialized()).isTrue();
    }

    @Test
    public void testGetRules() throws Exception {
        String packageName = "package";
        String packageCert = "cert";
        int version = 123;
        Rule packageNameRule = getPackageNameIndexedRule(packageName);
        Rule packageCertRule = getAppCertificateIndexedRule(packageCert);
        Rule versionCodeRule =
                new Rule(
                        new LongAtomicFormula(
                                AtomicFormula.VERSION_CODE, AtomicFormula.EQ, version),
                        Rule.DENY);
        Rule randomRule =
                new Rule(
                        new CompoundFormula(
                                CompoundFormula.OR,
                                Arrays.asList(
                                        new StringAtomicFormula(
                                                AtomicFormula.PACKAGE_NAME,
                                                "abc",
                                                /* isHashedValue= */ false),
                                        new LongAtomicFormula(
                                                AtomicFormula.VERSION_CODE,
                                                AtomicFormula.EQ,
                                                version))),
                        Rule.DENY);

        List<Rule> rules =
                Arrays.asList(packageNameRule, packageCertRule, versionCodeRule, randomRule);
        mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, rules);

        AppInstallMetadata appInstallMetadata =
                new AppInstallMetadata.Builder()
                        .setPackageName(packageName)
                        .setAppCertificates(Collections.singletonList(packageCert))
                        .setAppCertificateLineage(Collections.singletonList(packageCert))
                        .setVersionCode(version)
                        .setInstallerName("abc")
                        .setInstallerCertificates(Collections.singletonList("abc"))
                        .setIsPreInstalled(true)
                        .build();
        List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata);

        assertThat(rulesFetched)
                .containsExactly(packageNameRule, packageCertRule, versionCodeRule, randomRule);
    }

    @Test
    public void testGetRules_indexedForManyRules() throws Exception {
        String packageName = "package";
        String installerName = "installer";
        String appCertificate = "cert";

        // Create a rule set with 2500 package name indexed, 2500 app certificate indexed and
        // 500 unindexed rules.
        List<Rule> rules = new ArrayList<>();
        int unindexedRuleCount = 70;

        for (int i = 0; i < 2500; i++) {
            rules.add(getPackageNameIndexedRule(String.format("%s%04d", packageName, i)));
            rules.add(getAppCertificateIndexedRule(String.format("%s%04d", appCertificate, i)));
        }

        for (int i = 0; i < unindexedRuleCount; i++) {
            rules.add(getInstallerCertificateRule(String.format("%s%04d", installerName, i)));
        }

        // Write the rules and get them indexed.
        mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, rules);

        // Read the rules for a specific rule.
        String installedPackageName = String.format("%s%04d", packageName, 264);
        String installedAppCertificate = String.format("%s%04d", appCertificate, 1264);
        AppInstallMetadata appInstallMetadata =
                new AppInstallMetadata.Builder()
                        .setPackageName(installedPackageName)
                        .setAppCertificates(Collections.singletonList(installedAppCertificate))
                        .setAppCertificateLineage(
                                Collections.singletonList(installedAppCertificate))
                        .setVersionCode(250)
                        .setInstallerName("abc")
                        .setInstallerCertificates(Collections.singletonList("abc"))
                        .setIsPreInstalled(true)
                        .build();
        List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata);

        // Verify that we do not load all the rules and we have the necessary rules to evaluate.
        assertThat(rulesFetched.size())
                .isEqualTo(INDEXING_BLOCK_SIZE * 2 + unindexedRuleCount);
        assertThat(rulesFetched)
                .containsAtLeast(
                        getPackageNameIndexedRule(installedPackageName),
                        getAppCertificateIndexedRule(installedAppCertificate));
    }

    private Rule getPackageNameIndexedRule(String packageName) {
        return new Rule(
                new StringAtomicFormula(
                        AtomicFormula.PACKAGE_NAME, packageName, /* isHashedValue= */false),
                Rule.DENY);
    }

    private Rule getAppCertificateIndexedRule(String appCertificate) {
        return new Rule(
                new StringAtomicFormula(
                        AtomicFormula.APP_CERTIFICATE,
                        appCertificate, /* isHashedValue= */ false),
                Rule.DENY);
    }

    private Rule getInstallerCertificateRule(String installerCert) {
        return new Rule(
                new StringAtomicFormula(
                        AtomicFormula.INSTALLER_NAME, installerCert, /* isHashedValue= */false),
                Rule.DENY);
    }

    @Test
    public void testStagingDirectoryCleared() throws Exception {
        // We must push rules two times to ensure that staging directory is empty because we cleared
        // it, rather than because original rules directory is empty.
        mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
        mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);

        assertStagingDirectoryCleared();
    }

    private void assertStagingDirectoryCleared() {
        File stagingDir = new File(mTmpDir, "integrity_staging");
        assertThat(stagingDir.exists()).isTrue();
        assertThat(stagingDir.isDirectory()).isTrue();
        assertThat(stagingDir.listFiles()).isEmpty();
    }
}