Trace Redaction - Filter suspend resume events

This starts a new pattern for some primitives. Rather than having
a single primitive build the allowlist, this allows multiple
primitives to contribute to the allowlist.

For this change, there is:

  1. a collect filter that adds the ftrace event id
  2. a ftrace event filter than filters suspend-resume events

As long as item 2 is registered, item 1 can be optionally added to
enable/disable suspend-resume events.

Change-Id: I41d3e10a381e4497d0aa5ba9756c0c3d941a653b
diff --git a/Android.bp b/Android.bp
index 1683b53..3c0b263 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12879,6 +12879,7 @@
         "src/trace_redaction/scrub_process_stats.cc",
         "src/trace_redaction/scrub_process_trees.cc",
         "src/trace_redaction/scrub_trace_packet.cc",
+        "src/trace_redaction/suspend_resume.cc",
         "src/trace_redaction/trace_redaction_framework.cc",
         "src/trace_redaction/trace_redactor.cc",
     ],
@@ -12900,6 +12901,7 @@
         "src/trace_redaction/redact_process_free_unittest.cc",
         "src/trace_redaction/redact_sched_switch_unittest.cc",
         "src/trace_redaction/redact_task_newtask_unittest.cc",
+        "src/trace_redaction/suspend_resume_unittest.cc",
     ],
 }
 
diff --git a/src/trace_redaction/BUILD.gn b/src/trace_redaction/BUILD.gn
index 3dc9cdf..8bfffdd 100644
--- a/src/trace_redaction/BUILD.gn
+++ b/src/trace_redaction/BUILD.gn
@@ -68,6 +68,8 @@
     "scrub_process_trees.h",
     "scrub_trace_packet.cc",
     "scrub_trace_packet.h",
+    "suspend_resume.cc",
+    "suspend_resume.h",
     "trace_redaction_framework.cc",
     "trace_redaction_framework.h",
     "trace_redactor.cc",
@@ -130,6 +132,7 @@
     "redact_process_free_unittest.cc",
     "redact_sched_switch_unittest.cc",
     "redact_task_newtask_unittest.cc",
+    "suspend_resume_unittest.cc",
   ]
   deps = [
     ":trace_redaction",
diff --git a/src/trace_redaction/main.cc b/src/trace_redaction/main.cc
index 3fa0080..656fdef 100644
--- a/src/trace_redaction/main.cc
+++ b/src/trace_redaction/main.cc
@@ -34,6 +34,7 @@
 #include "src/trace_redaction/scrub_process_stats.h"
 #include "src/trace_redaction/scrub_process_trees.h"
 #include "src/trace_redaction/scrub_trace_packet.h"
+#include "src/trace_redaction/suspend_resume.h"
 #include "src/trace_redaction/trace_redaction_framework.h"
 #include "src/trace_redaction/trace_redactor.h"
 
@@ -51,6 +52,7 @@
 
   // Add all builders.
   redactor.emplace_build<PopulateAllowlists>();
+  redactor.emplace_build<AllowSuspendResume>();
   redactor.emplace_build<OptimizeTimeline>();
 
   // Add all transforms.
@@ -62,6 +64,7 @@
   scrub_ftrace_events->emplace_back<FilterPrintEvents>();
   scrub_ftrace_events->emplace_back<FilterSchedWakingEvents>();
   scrub_ftrace_events->emplace_back<FilterTaskRename>();
+  scrub_ftrace_events->emplace_back<FilterSuspendResume>();
 
   // Scrub packets and ftrace events first as they will remove the largest
   // chucks of data from the trace. This will reduce the amount of data that the
diff --git a/src/trace_redaction/populate_allow_lists.cc b/src/trace_redaction/populate_allow_lists.cc
index 01da4a0..380a152 100644
--- a/src/trace_redaction/populate_allow_lists.cc
+++ b/src/trace_redaction/populate_allow_lists.cc
@@ -25,10 +25,6 @@
 namespace perfetto::trace_redaction {
 
 base::Status PopulateAllowlists::Build(Context* context) const {
-  if (!context->trace_packet_allow_list.empty()) {
-    return base::ErrStatus("PopulateAllowlists: allow-list should be empty.");
-  }
-
   // TRACE PACKET NOTES
   //
   //    protos::pbzero::TracePacket::kAndroidSystemPropertyFieldNumber
@@ -38,7 +34,7 @@
   //      difficult. Because this packet's value has no measurable, the safest
   //      option to drop the whole packet.
 
-  context->trace_packet_allow_list = {
+  std::initializer_list<uint> trace_packets = {
       protos::pbzero::TracePacket::kProcessTreeFieldNumber,
       protos::pbzero::TracePacket::kProcessStatsFieldNumber,
       protos::pbzero::TracePacket::kClockSnapshotFieldNumber,
@@ -59,13 +55,16 @@
       protos::pbzero::TracePacket::kPackagesListFieldNumber,
   };
 
-  context->ftrace_packet_allow_list = {
+  for (auto item : trace_packets) {
+    context->trace_packet_allow_list.insert(item);
+  }
+
+  std::initializer_list<uint> ftrace_events = {
       protos::pbzero::FtraceEvent::kSchedSwitchFieldNumber,
       protos::pbzero::FtraceEvent::kCpuFrequencyFieldNumber,
       protos::pbzero::FtraceEvent::kCpuIdleFieldNumber,
       protos::pbzero::FtraceEvent::kSchedBlockedReasonFieldNumber,
       protos::pbzero::FtraceEvent::kSchedWakingFieldNumber,
-      protos::pbzero::FtraceEvent::kSuspendResumeFieldNumber,
       protos::pbzero::FtraceEvent::kTaskNewtaskFieldNumber,
       protos::pbzero::FtraceEvent::kTaskRenameFieldNumber,
       protos::pbzero::FtraceEvent::kSchedProcessFreeFieldNumber,
@@ -80,21 +79,9 @@
       protos::pbzero::FtraceEvent::kPrintFieldNumber,
   };
 
-  // TODO: Some ftrace fields should be retained, but they carry too much risk
-  // without additional redaction. This list should be configured in a build
-  // primitive so that they can be optionally included.
-  //
-  // protos::pbzero::FtraceEvent::kPrintFieldNumber,
-  //
-  // TODO: Some fields will create new packets (e.g. binder calls may create
-  // new spans. This is currently not supported (generated packets still
-  // need to be redacted).
-  //
-  // protos::pbzero::FtraceEvent::kBinderTransactionFieldNumber,
-  // protos::pbzero::FtraceEvent::kBinderTransactionReceivedFieldNumber,
-  // protos::pbzero::FtraceEvent::kBinderSetPriorityFieldNumber,
-  // protos::pbzero::FtraceEvent::kBinderLockedFieldNumber,
-  // protos::pbzero::FtraceEvent::kBinderUnlockFieldNumber,
+  for (auto item : ftrace_events) {
+    context->ftrace_packet_allow_list.insert(item);
+  }
 
   return base::OkStatus();
 }
diff --git a/src/trace_redaction/suspend_resume.cc b/src/trace_redaction/suspend_resume.cc
new file mode 100644
index 0000000..243446f
--- /dev/null
+++ b/src/trace_redaction/suspend_resume.cc
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_redaction/suspend_resume.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/power.pbzero.h"
+
+namespace perfetto::trace_redaction {
+
+base::Status AllowSuspendResume::Build(Context* context) const {
+  context->ftrace_packet_allow_list.insert(
+      protos::pbzero::FtraceEvent::kSuspendResumeFieldNumber);
+
+  // Values are taken from "suspend_period.textproto".
+  context->suspend_result_allow_list.insert("syscore_suspend");
+  context->suspend_result_allow_list.insert("syscore_resume");
+  context->suspend_result_allow_list.insert("timekeeping_freeze");
+
+  return base::OkStatus();
+}
+
+base::Status FilterSuspendResume::VerifyContext(const Context&) const {
+  // FilterSuspendResume could check if kSuspendResumeFieldNumber is present in
+  // ftrace_packet_allow_list and there are values in the
+  // suspend_result_allow_list, but would make it hard to enable/disable
+  // suspend-resume redaction.
+  return base::OkStatus();
+}
+
+// The ftrace event is passed in.
+bool FilterSuspendResume::KeepEvent(const Context& context,
+                                    protozero::ConstBytes bytes) const {
+  protozero::ProtoDecoder event_decoder(bytes);
+
+  auto suspend_resume = event_decoder.FindField(
+      protos::pbzero::FtraceEvent::kSuspendResumeFieldNumber);
+
+  // It's not a suspend-resume event, defer the decision to another filter.
+  if (!suspend_resume.valid()) {
+    return true;
+  }
+
+  protozero::ProtoDecoder suspend_resume_decoder(suspend_resume.as_bytes());
+
+  auto action = suspend_resume_decoder.FindField(
+      protos::pbzero::SuspendResumeFtraceEvent::kActionFieldNumber);
+
+  return !action.valid() ||
+         context.suspend_result_allow_list.count(action.as_std_string());
+}
+
+}  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/suspend_resume.h b/src/trace_redaction/suspend_resume.h
new file mode 100644
index 0000000..09f7bd8
--- /dev/null
+++ b/src/trace_redaction/suspend_resume.h
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_TRACE_REDACTION_SUSPEND_RESUME_H_
+#define SRC_TRACE_REDACTION_SUSPEND_RESUME_H_
+
+#include "src/trace_redaction/scrub_ftrace_events.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+// Updates allowlists to include suspend-resume events and which events to allow
+// through.
+class AllowSuspendResume : public BuildPrimitive {
+ public:
+  base::Status Build(Context* context) const override;
+};
+
+// Filters ftrace events based on the suspend-resume event.
+class FilterSuspendResume : public FtraceEventFilter {
+ public:
+  base::Status VerifyContext(const Context& context) const override;
+
+  bool KeepEvent(const Context& context,
+                 protozero::ConstBytes bytes) const override;
+};
+
+}  // namespace perfetto::trace_redaction
+
+#endif  // SRC_TRACE_REDACTION_SUSPEND_RESUME_H_
diff --git a/src/trace_redaction/suspend_resume_unittest.cc b/src/trace_redaction/suspend_resume_unittest.cc
new file mode 100644
index 0000000..7cf498a
--- /dev/null
+++ b/src/trace_redaction/suspend_resume_unittest.cc
@@ -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.
+ */
+
+#include "src/trace_redaction/suspend_resume.h"
+#include "src/base/test/status_matchers.h"
+#include "test/gtest_and_gmock.h"
+
+#include "protos/perfetto/trace/ftrace/ftrace.gen.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event.gen.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/power.gen.h"
+
+namespace perfetto::trace_redaction {
+
+TEST(AllowSuspendResumeTest, UpdatesTracePacketAllowlist) {
+  Context context;
+
+  // Start with a non-empty allow-list item.
+  context.ftrace_packet_allow_list.insert(
+      protos::pbzero::FtraceEvent::kPrintFieldNumber);
+
+  ASSERT_EQ(context.ftrace_packet_allow_list.size(), 1u);
+
+  AllowSuspendResume allow;
+  auto status = allow.Build(&context);
+  ASSERT_OK(status) << status.message();
+
+  // Print should still be present. The allowlist should have been updated, not
+  // replaced.
+  ASSERT_EQ(context.ftrace_packet_allow_list.count(
+                protos::pbzero::FtraceEvent::kPrintFieldNumber),
+            1u);
+
+  ASSERT_EQ(context.ftrace_packet_allow_list.count(
+                protos::pbzero::FtraceEvent::kSuspendResumeFieldNumber),
+            1u);
+}
+
+TEST(AllowSuspendResumeTest, UpdatesSuspendResumeAllowlist) {
+  Context context;
+
+  ASSERT_TRUE(context.suspend_result_allow_list.empty());
+
+  AllowSuspendResume allow;
+  auto status = allow.Build(&context);
+  ASSERT_OK(status) << status.message();
+
+  ASSERT_FALSE(context.suspend_result_allow_list.empty());
+}
+
+class SuspendResumeTest : public testing::Test {
+ protected:
+  void SetUp() {
+    AllowSuspendResume allow;
+    ASSERT_OK(allow.Build(&context_));
+  }
+
+  protos::gen::FtraceEvent CreateSuspendResumeEvent(
+      const std::string* action) const {
+    protos::gen::FtraceEvent event;
+    event.set_timestamp(1234);
+    event.set_pid(0);
+
+    auto* suspend_resume = event.mutable_suspend_resume();
+
+    if (action) {
+      suspend_resume->set_action(*action);
+    }
+
+    return event;
+  }
+
+  protos::gen::FtraceEvent CreateOtherEvent() const {
+    protos::gen::FtraceEvent event;
+    event.set_timestamp(1234);
+    event.set_pid(0);
+
+    auto* print = event.mutable_print();
+    print->set_buf("This is a message");
+
+    return event;
+  }
+
+  const Context& context() const { return context_; }
+
+ private:
+  Context context_;
+};
+
+// The suspend-resume filter is not responsible for non-suspend-resume events.
+// It should assume that another filter will handle it and it should just allow
+// those events through
+TEST_F(SuspendResumeTest, AcceptsOtherEvents) {
+  auto event = CreateOtherEvent();
+  auto event_array = event.SerializeAsArray();
+  protozero::ConstBytes event_bytes{event_array.data(), event_array.size()};
+
+  FilterSuspendResume filter;
+  ASSERT_TRUE(filter.KeepEvent(context(), event_bytes));
+}
+
+TEST_F(SuspendResumeTest, AcceptsEventsWithNoName) {
+  auto event = CreateSuspendResumeEvent(nullptr);
+  auto event_array = event.SerializeAsArray();
+  protozero::ConstBytes event_bytes{event_array.data(), event_array.size()};
+
+  Context context;
+
+  FilterSuspendResume filter;
+  ASSERT_TRUE(filter.KeepEvent(context, event_bytes));
+}
+
+TEST_F(SuspendResumeTest, AcceptsEventsWithValidName) {
+  // This value is from "src/trace_redaction/suspend_resume.cc".
+  std::string name = "syscore_suspend";
+
+  auto event = CreateSuspendResumeEvent(&name);
+  auto event_array = event.SerializeAsArray();
+  protozero::ConstBytes event_bytes{event_array.data(), event_array.size()};
+
+  FilterSuspendResume filter;
+  ASSERT_TRUE(filter.KeepEvent(context(), event_bytes));
+}
+
+TEST_F(SuspendResumeTest, RejectsEventsWithInvalidName) {
+  std::string name = "hello world";
+
+  auto event = CreateSuspendResumeEvent(&name);
+  auto event_array = event.SerializeAsArray();
+  protozero::ConstBytes event_bytes{event_array.data(), event_array.size()};
+
+  FilterSuspendResume filter;
+  ASSERT_FALSE(filter.KeepEvent(context(), event_bytes));
+}
+
+}  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/trace_redaction_framework.h b/src/trace_redaction/trace_redaction_framework.h
index c1c7e45..c76a651 100644
--- a/src/trace_redaction/trace_redaction_framework.h
+++ b/src/trace_redaction/trace_redaction_framework.h
@@ -159,6 +159,17 @@
   //      ftrace event
   base::FlatSet<uint32_t> ftrace_packet_allow_list;
 
+  //  message SuspendResumeFtraceEvent {
+  //    optional string action = 1 [(datapol.semantic_type) = ST_NOT_REQUIRED];
+  //    optional int32 val = 2;
+  //    optional uint32 start = 3 [(datapol.semantic_type) = ST_NOT_REQUIRED];
+  //  }
+  //
+  // The "action" in SuspendResumeFtraceEvent is a free-form string. There are
+  // some know and expected values. Those values are stored here and all events
+  // who's action value is not found here, the ftrace event will be dropped.
+  base::FlatSet<std::string> suspend_result_allow_list;
+
   // The timeline is a query-focused data structure that connects a pid to a
   // uid at specific point in time.
   //