From 5814cc70a25dcfe1bd43a5d1b7aaf5f85c41036b Mon Sep 17 00:00:00 2001
From: Josh Gao <jmgao@google.com>
Date: Fri, 29 Jan 2021 13:41:27 -0800
Subject: [PATCH] Purge proto tombstones when apps are uninstalled.

In preparation for making tombstones available to apps, make sure we
clean up after ourselves, so that when we recycle UIDs, we don't
accidentally give out old tombstones from unrelated previous apps.

Bug: http://b/159164105
Test: manual
Change-Id: I76646b6e98bc9019bb8d978fa416303a617a8238
---
 .../server/os/NativeTombstoneManager.java     | 88 +++++++++++++++++++
 1 file changed, 88 insertions(+)

diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index a83edb75badb..3c5d45636e3e 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -22,11 +22,16 @@ import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
 import android.annotation.AppIdInt;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.FileObserver;
 import android.os.Handler;
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
+import android.system.ErrnoException;
+import android.system.Os;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.proto.ProtoInputStream;
@@ -75,6 +80,9 @@ public final class NativeTombstoneManager {
     }
 
     void onSystemReady() {
+        registerForUserRemoval();
+        registerForPackageRemoval();
+
         // Scan existing tombstones.
         mHandler.post(() -> {
             final File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
@@ -145,6 +153,67 @@ public final class NativeTombstoneManager {
         }
     }
 
+    private void purge(Optional<Integer> userId, Optional<Integer> appId) {
+        mHandler.post(() -> {
+            synchronized (mLock) {
+                for (int i = mTombstones.size() - 1; i >= 0; --i) {
+                    TombstoneFile tombstone = mTombstones.valueAt(i);
+                    if (tombstone.matches(userId, appId)) {
+                        tombstone.purge();
+                        mTombstones.removeAt(i);
+                    }
+                }
+            }
+        });
+    }
+
+    private void purgePackage(int uid, boolean allUsers) {
+        final int appId = UserHandle.getAppId(uid);
+        Optional<Integer> userId;
+        if (allUsers) {
+            userId = Optional.empty();
+        } else {
+            userId = Optional.of(UserHandle.getUserId(uid));
+        }
+        purge(userId, Optional.of(appId));
+    }
+
+    private void purgeUser(int uid) {
+        purge(Optional.of(uid), Optional.empty());
+    }
+
+    private void registerForPackageRemoval() {
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
+        filter.addDataScheme("package");
+        mContext.registerReceiverForAllUsers(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final int uid = intent.getIntExtra(Intent.EXTRA_UID, UserHandle.USER_NULL);
+                if (uid == UserHandle.USER_NULL) return;
+
+                final boolean allUsers = intent.getBooleanExtra(
+                        Intent.EXTRA_REMOVED_FOR_ALL_USERS, false);
+
+                purgePackage(uid, allUsers);
+            }
+        }, filter, null, mHandler);
+    }
+
+    private void registerForUserRemoval() {
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_REMOVED);
+        mContext.registerReceiverForAllUsers(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                if (userId < 1) return;
+
+                purgeUser(userId);
+            }
+        }, filter, null, mHandler);
+    }
+
     static class TombstoneFile {
         final ParcelFileDescriptor mPfd;
 
@@ -179,6 +248,25 @@ public final class NativeTombstoneManager {
             IoUtils.closeQuietly(mPfd);
         }
 
+        public void purge() {
+            if (!mPurged) {
+                // There's no way to atomically unlink a specific file for which we have an fd from
+                // a path, which means that we can't safely delete a tombstone without coordination
+                // with tombstoned (which has a risk of deadlock if for example, system_server hangs
+                // with a flock). Do the next best thing, and just truncate the file.
+                //
+                // We don't have to worry about inflicting a SIGBUS on a process that has the
+                // tombstone mmaped, because we only clear if the package has been removed, which
+                // means no one with access to the tombstone should be left.
+                try {
+                    Os.ftruncate(mPfd.getFileDescriptor(), 0);
+                } catch (ErrnoException ex) {
+                    Slog.e(TAG, "Failed to truncate tombstone", ex);
+                }
+                mPurged = true;
+            }
+        }
+
         static Optional<TombstoneFile> parse(ParcelFileDescriptor pfd) {
             final FileInputStream is = new FileInputStream(pfd.getFileDescriptor());
             final ProtoInputStream stream = new ProtoInputStream(is);
-- 
GitLab