Install CT log list on successful download

Add the CertificateTransparencyInstaller, that is in charge of
atomically update the log list file that is read by SCT verification in
Conscrypt.

Flag: com.android.net.ct.flags.certificate_transparency_service
Bug: 319829948
Test: atest NetworkSecurityUnitTests
Change-Id: I7c82e36820256bc0df11e44fd973b25a64d69f54
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
index 6e787d1..f35b163 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyDownloader.java
@@ -25,6 +25,9 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import java.io.IOException;
+import java.io.InputStream;
+
 /** Helper class to download certificate transparency log files. */
 class CertificateTransparencyDownloader extends BroadcastReceiver {
 
@@ -33,17 +36,26 @@
     private final Context mContext;
     private final DataStore mDataStore;
     private final DownloadHelper mDownloadHelper;
+    private final CertificateTransparencyInstaller mInstaller;
 
     @VisibleForTesting
     CertificateTransparencyDownloader(
-            Context context, DataStore dataStore, DownloadHelper downloadHelper) {
+            Context context,
+            DataStore dataStore,
+            DownloadHelper downloadHelper,
+            CertificateTransparencyInstaller installer) {
         mContext = context;
         mDataStore = dataStore;
         mDownloadHelper = downloadHelper;
+        mInstaller = installer;
     }
 
     CertificateTransparencyDownloader(Context context, DataStore dataStore) {
-        this(context, dataStore, new DownloadHelper(context));
+        this(
+                context,
+                dataStore,
+                new DownloadHelper(context),
+                new CertificateTransparencyInstaller());
     }
 
     void registerReceiver() {
@@ -110,7 +122,7 @@
             return;
         }
 
-        startContentDownload(mDataStore.getProperty(Config.CONTENT_URL));
+        startContentDownload(mDataStore.getProperty(Config.CONTENT_URL_PENDING));
     }
 
     private void handleContentDownloadCompleted(long downloadId) {
@@ -127,7 +139,26 @@
             return;
         }
 
-        // TODO: 1. verify file signature, 2. validate file content, 3. install log file.
+        // TODO: 1. verify file signature, 2. validate file content.
+
+        String version = mDataStore.getProperty(Config.VERSION_PENDING);
+        String contentUrl = mDataStore.getProperty(Config.CONTENT_URL_PENDING);
+        String metadataUrl = mDataStore.getProperty(Config.METADATA_URL_PENDING);
+        boolean success = false;
+        try (InputStream inputStream = mContext.getContentResolver().openInputStream(contentUri)) {
+            success = mInstaller.install(inputStream, version);
+        } catch (IOException e) {
+            Log.e(TAG, "Could not install new content", e);
+            return;
+        }
+
+        if (success) {
+            // Update information about the stored version on successful install.
+            mDataStore.setProperty(Config.VERSION, version);
+            mDataStore.setProperty(Config.CONTENT_URL, contentUrl);
+            mDataStore.setProperty(Config.METADATA_URL, metadataUrl);
+            mDataStore.store();
+        }
     }
 
     private long download(String url) {
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java
index ecf94d5..fdac434 100644
--- a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyFlagsListener.java
@@ -85,9 +85,9 @@
             return;
         }
 
-        mDataStore.setProperty(Config.VERSION, newVersion);
-        mDataStore.setProperty(Config.CONTENT_URL, newContentUrl);
-        mDataStore.setProperty(Config.METADATA_URL, newMetadataUrl);
+        mDataStore.setProperty(Config.VERSION_PENDING, newVersion);
+        mDataStore.setProperty(Config.CONTENT_URL_PENDING, newContentUrl);
+        mDataStore.setProperty(Config.METADATA_URL_PENDING, newMetadataUrl);
         mDataStore.store();
 
         mCertificateTransparencyDownloader.startMetadataDownload(newMetadataUrl);
diff --git a/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyInstaller.java b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyInstaller.java
new file mode 100644
index 0000000..82dcadf
--- /dev/null
+++ b/networksecurity/service/src/com/android/server/net/ct/CertificateTransparencyInstaller.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2024 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.net.ct;
+
+import android.annotation.SuppressLint;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+
+/** Installer of CT log lists. */
+public class CertificateTransparencyInstaller {
+
+    private static final String TAG = "CertificateTransparencyInstaller";
+    private static final String CT_DIR_NAME = "/data/misc/keychain/ct/";
+
+    static final String LOGS_DIR_PREFIX = "logs-";
+    static final String LOGS_LIST_FILE_NAME = "log_list.json";
+    static final String CURRENT_DIR_SYMLINK_NAME = "current";
+
+    private final File mCertificateTransparencyDir;
+    private final File mCurrentDirSymlink;
+
+    CertificateTransparencyInstaller(File certificateTransparencyDir) {
+        mCertificateTransparencyDir = certificateTransparencyDir;
+        mCurrentDirSymlink = new File(certificateTransparencyDir, CURRENT_DIR_SYMLINK_NAME);
+    }
+
+    CertificateTransparencyInstaller() {
+        this(new File(CT_DIR_NAME));
+    }
+
+    /**
+     * Install a new log list to use during SCT verification.
+     *
+     * @param newContent an input stream providing the log list
+     * @param version the version of the new log list
+     * @return true if the log list was installed successfully, false otherwise.
+     * @throws IOException if the list cannot be saved in the CT directory.
+     */
+    public boolean install(InputStream newContent, String version) throws IOException {
+        // To support atomically replacing the old configuration directory with the new there's a
+        // bunch of steps. We create a new directory with the logs and then do an atomic update of
+        // the current symlink to point to the new directory.
+        // 1. Ensure that the update dir exists and is readable.
+        makeDir(mCertificateTransparencyDir);
+
+        File newLogsDir = new File(mCertificateTransparencyDir, LOGS_DIR_PREFIX + version);
+        // 2. Handle the corner case where the new directory already exists.
+        if (newLogsDir.exists()) {
+            // If the symlink has already been updated then the update died between steps 6 and 7
+            // and so we cannot delete the directory since it is in use.
+            if (newLogsDir.getCanonicalPath().equals(mCurrentDirSymlink.getCanonicalPath())) {
+                deleteOldLogDirectories();
+                return false;
+            }
+            // If the symlink has not been updated then the previous installation failed and this is
+            // a re-attempt. Clean-up leftover files and try again.
+            deleteContentsAndDir(newLogsDir);
+        }
+        try {
+            // 3. Create /data/misc/keychain/ct/logs-<new_version>/ .
+            makeDir(newLogsDir);
+
+            // 4. Move the log list json file in logs-<new_version>/ .
+            File logListFile = new File(newLogsDir, LOGS_LIST_FILE_NAME);
+            if (Files.copy(newContent, logListFile.toPath()) == 0) {
+                throw new IOException("The log list appears empty");
+            }
+            setWorldReadable(logListFile);
+
+            // 5. Create temp symlink. We rename this to the target symlink to get an atomic update.
+            File tempSymlink = new File(mCertificateTransparencyDir, "new_symlink");
+            try {
+                Os.symlink(newLogsDir.getCanonicalPath(), tempSymlink.getCanonicalPath());
+            } catch (ErrnoException e) {
+                throw new IOException("Failed to create symlink", e);
+            }
+
+            // 6. Update the symlink target, this is the actual update step.
+            tempSymlink.renameTo(mCurrentDirSymlink.getAbsoluteFile());
+        } catch (IOException | RuntimeException e) {
+            deleteContentsAndDir(newLogsDir);
+            throw e;
+        }
+        Log.i(TAG, "CT log directory updated to " + newLogsDir.getAbsolutePath());
+        // 7. Cleanup
+        deleteOldLogDirectories();
+        return true;
+    }
+
+    private void makeDir(File dir) throws IOException {
+        dir.mkdir();
+        if (!dir.isDirectory()) {
+            throw new IOException("Unable to make directory " + dir.getCanonicalPath());
+        }
+        setWorldReadable(dir);
+    }
+
+    // CT files and directories are readable by all apps.
+    @SuppressLint("SetWorldReadable")
+    private void setWorldReadable(File file) throws IOException {
+        if (!file.setReadable(true, false)) {
+            throw new IOException("Failed to set " + file.getCanonicalPath() + " readable");
+        }
+    }
+
+    private void deleteOldLogDirectories() throws IOException {
+        if (!mCertificateTransparencyDir.exists()) {
+            return;
+        }
+        File currentTarget = mCurrentDirSymlink.getCanonicalFile();
+        for (File file : mCertificateTransparencyDir.listFiles()) {
+            if (!currentTarget.equals(file.getCanonicalFile())
+                    && file.getName().startsWith(LOGS_DIR_PREFIX)) {
+                deleteContentsAndDir(file);
+            }
+        }
+    }
+
+    static boolean deleteContentsAndDir(File dir) {
+        if (deleteContents(dir)) {
+            return dir.delete();
+        } else {
+            return false;
+        }
+    }
+
+    private static boolean deleteContents(File dir) {
+        File[] files = dir.listFiles();
+        boolean success = true;
+        if (files != null) {
+            for (File file : files) {
+                if (file.isDirectory()) {
+                    success &= deleteContents(file);
+                }
+                if (!file.delete()) {
+                    Log.w(TAG, "Failed to delete " + file);
+                    success = false;
+                }
+            }
+        }
+        return success;
+    }
+}
diff --git a/networksecurity/service/src/com/android/server/net/ct/Config.java b/networksecurity/service/src/com/android/server/net/ct/Config.java
index e184359..04b7dac 100644
--- a/networksecurity/service/src/com/android/server/net/ct/Config.java
+++ b/networksecurity/service/src/com/android/server/net/ct/Config.java
@@ -34,9 +34,12 @@
     static final File PREFERENCES_FILE = new File(DEVICE_PROTECTED_DATA_DIR, PREFERENCES_FILE_NAME);
 
     // flags and properties names
+    static final String VERSION_PENDING = "version_pending";
     static final String VERSION = "version";
+    static final String CONTENT_URL_PENDING = "content_url_pending";
     static final String CONTENT_URL = "content_url";
     static final String CONTENT_URL_KEY = "content_url_key";
+    static final String METADATA_URL_PENDING = "metadata_url_pending";
     static final String METADATA_URL = "metadata_url";
     static final String METADATA_URL_KEY = "metadata_url_key";
 }
diff --git a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
index acd0d36..5131a71 100644
--- a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
+++ b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyDownloaderTest.java
@@ -17,6 +17,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -25,6 +27,7 @@
 import android.app.DownloadManager;
 import android.content.Context;
 import android.content.Intent;
+import android.net.Uri;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
@@ -44,6 +47,7 @@
 public class CertificateTransparencyDownloaderTest {
 
     @Mock private DownloadHelper mDownloadHelper;
+    @Mock private CertificateTransparencyInstaller mCertificateTransparencyInstaller;
 
     private Context mContext;
     private File mTempFile;
@@ -60,7 +64,8 @@
         mDataStore.load();
 
         mCertificateTransparencyDownloader =
-                new CertificateTransparencyDownloader(mContext, mDataStore, mDownloadHelper);
+                new CertificateTransparencyDownloader(
+                        mContext, mDataStore, mDownloadHelper, mCertificateTransparencyInstaller);
     }
 
     @After
@@ -98,7 +103,7 @@
 
         long contentId = 666;
         String contentUrl = "http://test-content.org";
-        mDataStore.setProperty(Config.CONTENT_URL, contentUrl);
+        mDataStore.setProperty(Config.CONTENT_URL_PENDING, contentUrl);
         when(mDownloadHelper.startDownload(contentUrl)).thenReturn(contentId);
 
         mCertificateTransparencyDownloader.onReceive(
@@ -114,7 +119,7 @@
         when(mDownloadHelper.isSuccessful(metadataId)).thenReturn(false);
 
         String contentUrl = "http://test-content.org";
-        mDataStore.setProperty(Config.CONTENT_URL, contentUrl);
+        mDataStore.setProperty(Config.CONTENT_URL_PENDING, contentUrl);
 
         mCertificateTransparencyDownloader.onReceive(
                 mContext, makeDownloadCompleteIntent(metadataId));
@@ -123,19 +128,64 @@
     }
 
     @Test
-    public void testDownloader_handleContentCompleteSuccessful() {
+    public void testDownloader_handleContentCompleteInstallSuccessful() throws IOException {
+        String version = "666";
+        mDataStore.setProperty(Config.VERSION_PENDING, version);
+
         long metadataId = 123;
         mDataStore.setPropertyLong(Config.METADATA_URL_KEY, metadataId);
+        Uri metadataUri = Uri.fromFile(File.createTempFile("log_list-metadata", "txt"));
+        mDataStore.setProperty(Config.METADATA_URL_PENDING, metadataUri.toString());
+        when(mDownloadHelper.getUri(metadataId)).thenReturn(metadataUri);
 
         long contentId = 666;
         mDataStore.setPropertyLong(Config.CONTENT_URL_KEY, contentId);
         when(mDownloadHelper.isSuccessful(contentId)).thenReturn(true);
+        Uri contentUri = Uri.fromFile(File.createTempFile("log_list", "json"));
+        mDataStore.setProperty(Config.CONTENT_URL_PENDING, contentUri.toString());
+        when(mDownloadHelper.getUri(contentId)).thenReturn(contentUri);
+
+        when(mCertificateTransparencyInstaller.install(any(), eq(version))).thenReturn(true);
+
+        assertThat(mDataStore.getProperty(Config.VERSION)).isNull();
+        assertThat(mDataStore.getProperty(Config.CONTENT_URL)).isNull();
+        assertThat(mDataStore.getProperty(Config.METADATA_URL)).isNull();
 
         mCertificateTransparencyDownloader.onReceive(
                 mContext, makeDownloadCompleteIntent(contentId));
 
-        verify(mDownloadHelper, times(1)).getUri(metadataId);
-        verify(mDownloadHelper, times(1)).getUri(contentId);
+        verify(mCertificateTransparencyInstaller, times(1)).install(any(), eq(version));
+        assertThat(mDataStore.getProperty(Config.VERSION)).isEqualTo(version);
+        assertThat(mDataStore.getProperty(Config.CONTENT_URL)).isEqualTo(contentUri.toString());
+        assertThat(mDataStore.getProperty(Config.METADATA_URL)).isEqualTo(metadataUri.toString());
+    }
+
+    @Test
+    public void testDownloader_handleContentCompleteInstallFails() throws IOException {
+        String version = "666";
+        mDataStore.setProperty(Config.VERSION_PENDING, version);
+
+        long metadataId = 123;
+        mDataStore.setPropertyLong(Config.METADATA_URL_KEY, metadataId);
+        Uri metadataUri = Uri.fromFile(File.createTempFile("log_list-metadata", "txt"));
+        mDataStore.setProperty(Config.METADATA_URL_PENDING, metadataUri.toString());
+        when(mDownloadHelper.getUri(metadataId)).thenReturn(metadataUri);
+
+        long contentId = 666;
+        mDataStore.setPropertyLong(Config.CONTENT_URL_KEY, contentId);
+        when(mDownloadHelper.isSuccessful(contentId)).thenReturn(true);
+        Uri contentUri = Uri.fromFile(File.createTempFile("log_list", "json"));
+        mDataStore.setProperty(Config.CONTENT_URL_PENDING, contentUri.toString());
+        when(mDownloadHelper.getUri(contentId)).thenReturn(contentUri);
+
+        when(mCertificateTransparencyInstaller.install(any(), eq(version))).thenReturn(false);
+
+        mCertificateTransparencyDownloader.onReceive(
+                mContext, makeDownloadCompleteIntent(contentId));
+
+        assertThat(mDataStore.getProperty(Config.VERSION)).isNull();
+        assertThat(mDataStore.getProperty(Config.CONTENT_URL)).isNull();
+        assertThat(mDataStore.getProperty(Config.METADATA_URL)).isNull();
     }
 
     private Intent makeDownloadCompleteIntent(long downloadId) {
diff --git a/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyInstallerTest.java b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyInstallerTest.java
new file mode 100644
index 0000000..bfb8bdf
--- /dev/null
+++ b/networksecurity/tests/unit/src/com/android/server/net/ct/CertificateTransparencyInstallerTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2024 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.net.ct;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.system.ErrnoException;
+import android.system.Os;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/** Tests for the {@link CertificateTransparencyInstaller}. */
+@RunWith(JUnit4.class)
+public class CertificateTransparencyInstallerTest {
+
+    private File mTestDir =
+            new File(
+                    InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(),
+                    "test-dir");
+    private File mTestSymlink =
+            new File(mTestDir, CertificateTransparencyInstaller.CURRENT_DIR_SYMLINK_NAME);
+    private CertificateTransparencyInstaller mCertificateTransparencyInstaller =
+            new CertificateTransparencyInstaller(mTestDir);
+
+    @Before
+    public void setUp() {
+        CertificateTransparencyInstaller.deleteContentsAndDir(mTestDir);
+    }
+
+    @Test
+    public void testCertificateTransparencyInstaller_installSuccessfully() throws IOException {
+        String content = "i_am_a_certificate_and_i_am_transparent";
+        String version = "666";
+        boolean success = false;
+
+        try (InputStream inputStream = asStream(content)) {
+            success = mCertificateTransparencyInstaller.install(inputStream, version);
+        }
+
+        assertThat(success).isTrue();
+        assertThat(mTestDir.exists()).isTrue();
+        assertThat(mTestDir.isDirectory()).isTrue();
+        assertThat(mTestSymlink.exists()).isTrue();
+        assertThat(mTestSymlink.isDirectory()).isTrue();
+
+        File logsDir =
+                new File(mTestDir, CertificateTransparencyInstaller.LOGS_DIR_PREFIX + version);
+        assertThat(logsDir.exists()).isTrue();
+        assertThat(logsDir.isDirectory()).isTrue();
+        assertThat(mTestSymlink.getCanonicalPath()).isEqualTo(logsDir.getCanonicalPath());
+
+        File logsListFile = new File(logsDir, CertificateTransparencyInstaller.LOGS_LIST_FILE_NAME);
+        assertThat(logsListFile.exists()).isTrue();
+        assertThat(readAsString(logsListFile)).isEqualTo(content);
+    }
+
+    @Test
+    public void testCertificateTransparencyInstaller_versionIsAlreadyInstalled()
+            throws IOException, ErrnoException {
+        String existingVersion = "666";
+        String existingContent = "i_was_already_installed_successfully";
+        File existingLogDir =
+                new File(
+                        mTestDir,
+                        CertificateTransparencyInstaller.LOGS_DIR_PREFIX + existingVersion);
+        assertThat(mTestDir.mkdir()).isTrue();
+        assertThat(existingLogDir.mkdir()).isTrue();
+        Os.symlink(existingLogDir.getCanonicalPath(), mTestSymlink.getCanonicalPath());
+        File logsListFile =
+                new File(existingLogDir, CertificateTransparencyInstaller.LOGS_LIST_FILE_NAME);
+        logsListFile.createNewFile();
+        writeToFile(logsListFile, existingContent);
+        boolean success = false;
+
+        try (InputStream inputStream = asStream("i_will_be_ignored")) {
+            success = mCertificateTransparencyInstaller.install(inputStream, existingVersion);
+        }
+
+        assertThat(success).isFalse();
+        assertThat(readAsString(logsListFile)).isEqualTo(existingContent);
+    }
+
+    @Test
+    public void testCertificateTransparencyInstaller_versionInstalledFailed()
+            throws IOException, ErrnoException {
+        String existingVersion = "666";
+        String existingContent = "somebody_tried_to_install_me_but_failed_halfway_through";
+        String newContent = "i_am_the_real_certificate";
+        File existingLogDir =
+                new File(
+                        mTestDir,
+                        CertificateTransparencyInstaller.LOGS_DIR_PREFIX + existingVersion);
+        assertThat(mTestDir.mkdir()).isTrue();
+        assertThat(existingLogDir.mkdir()).isTrue();
+        File logsListFile =
+                new File(existingLogDir, CertificateTransparencyInstaller.LOGS_LIST_FILE_NAME);
+        logsListFile.createNewFile();
+        writeToFile(logsListFile, existingContent);
+        boolean success = false;
+
+        try (InputStream inputStream = asStream(newContent)) {
+            success = mCertificateTransparencyInstaller.install(inputStream, existingVersion);
+        }
+
+        assertThat(success).isTrue();
+        assertThat(mTestSymlink.getCanonicalPath()).isEqualTo(existingLogDir.getCanonicalPath());
+        assertThat(readAsString(logsListFile)).isEqualTo(newContent);
+    }
+
+    private static InputStream asStream(String string) throws IOException {
+        return new ByteArrayInputStream(string.getBytes());
+    }
+
+    private static String readAsString(File file) throws IOException {
+        return new String(new FileInputStream(file).readAllBytes());
+    }
+
+    private static void writeToFile(File file, String string) throws IOException {
+        try (OutputStream out = new FileOutputStream(file)) {
+            out.write(string.getBytes());
+        }
+    }
+}
diff --git a/networksecurity/tests/unit/src/com/android/server/net/ct/DataStoreTest.java b/networksecurity/tests/unit/src/com/android/server/net/ct/DataStoreTest.java
index d16f138..3e670d4 100644
--- a/networksecurity/tests/unit/src/com/android/server/net/ct/DataStoreTest.java
+++ b/networksecurity/tests/unit/src/com/android/server/net/ct/DataStoreTest.java
@@ -37,7 +37,6 @@
     public void setUp() throws IOException {
         mTempFile = File.createTempFile("datastore-test", ".properties");
         mDataStore = new DataStore(mTempFile);
-        mDataStore.load();
     }
 
     @After