Trace Redaction - Scrub Ftrace Events

Ftrace events fall into one of three categories:

  1. Contains no sensitive information and not needed
  2. Contains sensitive information but not needed
  3. May contain sensitive information, but needed

For case 1: the event is dropped.

    case 2: the event is dropped.

    case 3: the event is not dropped and other primitives will remove
            sensitive information.

Bug: 318576092
Bug: 318576499
Change-Id: I06616a65a2093832c3a58342ef8becd8f17e7996
diff --git a/Android.bp b/Android.bp
index bd2375b..ff0d4db 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12480,6 +12480,7 @@
         "src/trace_redaction/find_package_uid.cc",
         "src/trace_redaction/populate_allow_lists.cc",
         "src/trace_redaction/prune_package_list.cc",
+        "src/trace_redaction/scrub_ftrace_events.cc",
         "src/trace_redaction/scrub_trace_packet.cc",
         "src/trace_redaction/trace_redaction_framework.cc",
         "src/trace_redaction/trace_redactor.cc",
@@ -12492,6 +12493,7 @@
     srcs: [
         "src/trace_redaction/find_package_uid_unittest.cc",
         "src/trace_redaction/prune_package_list_unittest.cc",
+        "src/trace_redaction/scrub_ftrace_events_unittest.cc",
         "src/trace_redaction/scrub_trace_packet_unittest.cc",
     ],
 }
diff --git a/src/trace_redaction/BUILD.gn b/src/trace_redaction/BUILD.gn
index f65c588..937784f 100644
--- a/src/trace_redaction/BUILD.gn
+++ b/src/trace_redaction/BUILD.gn
@@ -34,6 +34,8 @@
     "populate_allow_lists.h",
     "prune_package_list.cc",
     "prune_package_list.h",
+    "scrub_ftrace_events.cc",
+    "scrub_ftrace_events.h",
     "scrub_trace_packet.cc",
     "scrub_trace_packet.h",
     "trace_redaction_framework.cc",
@@ -74,6 +76,7 @@
   sources = [
     "find_package_uid_unittest.cc",
     "prune_package_list_unittest.cc",
+    "scrub_ftrace_events_unittest.cc",
     "scrub_trace_packet_unittest.cc",
   ]
   deps = [
diff --git a/src/trace_redaction/main.cc b/src/trace_redaction/main.cc
index 24ad116..153e437 100644
--- a/src/trace_redaction/main.cc
+++ b/src/trace_redaction/main.cc
@@ -18,6 +18,8 @@
 #include "perfetto/base/status.h"
 #include "src/trace_redaction/find_package_uid.h"
 #include "src/trace_redaction/prune_package_list.h"
+#include "src/trace_redaction/scrub_ftrace_events.h"
+#include "src/trace_redaction/scrub_trace_packet.h"
 #include "src/trace_redaction/trace_redaction_framework.h"
 #include "src/trace_redaction/trace_redactor.h"
 
@@ -36,6 +38,8 @@
 
   // Add all transforms.
   redactor.transformers()->emplace_back(new PrunePackageList());
+  redactor.transformers()->emplace_back(new ScrubTracePacket());
+  redactor.transformers()->emplace_back(new ScrubFtraceEvents());
 
   Context context;
   context.package_name = package_name;
diff --git a/src/trace_redaction/populate_allow_lists.cc b/src/trace_redaction/populate_allow_lists.cc
index 1ccaa00..fbfe279 100644
--- a/src/trace_redaction/populate_allow_lists.cc
+++ b/src/trace_redaction/populate_allow_lists.cc
@@ -19,6 +19,7 @@
 #include "perfetto/base/status.h"
 #include "src/trace_redaction/trace_redaction_framework.h"
 
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 
 namespace perfetto::trace_redaction {
@@ -50,6 +51,41 @@
       protos::pbzero::TracePacket::kPackagesListFieldNumber,
   };
 
+  context->ftrace_packet_allow_list = {
+      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,
+      protos::pbzero::FtraceEvent::kRssStatFieldNumber,
+      protos::pbzero::FtraceEvent::kIonHeapShrinkFieldNumber,
+      protos::pbzero::FtraceEvent::kIonHeapGrowFieldNumber,
+      protos::pbzero::FtraceEvent::kIonStatFieldNumber,
+      protos::pbzero::FtraceEvent::kIonBufferCreateFieldNumber,
+      protos::pbzero::FtraceEvent::kIonBufferDestroyFieldNumber,
+      protos::pbzero::FtraceEvent::kDmaHeapStatFieldNumber,
+      protos::pbzero::FtraceEvent::kRssStatThrottledFieldNumber,
+  };
+
+  // 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.
+  //
+  // 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::kPrintFieldNumber,
+  // protos::pbzero::FtraceEvent::kBinderTransactionFieldNumber,
+  // protos::pbzero::FtraceEvent::kBinderTransactionReceivedFieldNumber,
+  // protos::pbzero::FtraceEvent::kBinderSetPriorityFieldNumber,
+  // protos::pbzero::FtraceEvent::kBinderLockedFieldNumber,
+  // protos::pbzero::FtraceEvent::kBinderUnlockFieldNumber,
+
   return base::OkStatus();
 }
 
diff --git a/src/trace_redaction/scrub_ftrace_events.cc b/src/trace_redaction/scrub_ftrace_events.cc
new file mode 100644
index 0000000..67585d1
--- /dev/null
+++ b/src/trace_redaction/scrub_ftrace_events.cc
@@ -0,0 +1,187 @@
+/*
+ * 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/scrub_ftrace_events.h"
+
+#include <string>
+
+#include "perfetto/protozero/message.h"
+#include "perfetto/protozero/message_arena.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "protos/perfetto/trace/trace.pbzero.h"
+
+namespace perfetto::trace_redaction {
+namespace {
+
+constexpr auto kFtraceEventsFieldNumber =
+    protos::pbzero::TracePacket::kFtraceEventsFieldNumber;
+
+constexpr auto kEventFieldNumber =
+    protos::pbzero::FtraceEventBundle::kEventFieldNumber;
+
+enum class Redact : uint8_t {
+  // Some resources in the target need to be redacted.
+  kSomething = 0,
+
+  // No resources in the target need to be redacted.
+  kNothing = 1,
+};
+
+// Return kSomething if an event will change after redaction . If a packet
+// will not change, then the packet should skip redaction and be appended
+// to the output.
+//
+// Event packets have few packets (e.g. timestamp, pid, the event payload).
+// because of this, it is relatively cheap to test a packet.
+//
+//  event {
+//    timestamp: 6702095044306682
+//    pid: 0
+//    sched_switch {
+//      prev_comm: "swapper/2"
+//      prev_pid: 0
+//      prev_prio: 120
+//      prev_state: 0
+//      next_comm: "surfaceflinger"
+//      next_pid: 819
+//      next_prio: 120
+//    }
+//  }
+Redact ProbeEvent(const Context& context, const protozero::Field& event) {
+  if (event.id() != kEventFieldNumber) {
+    PERFETTO_FATAL("Invalid proto field. Expected kEventFieldNumber.");
+  }
+
+  protozero::ProtoDecoder decoder(event.data(), event.size());
+
+  for (auto field = decoder.ReadField(); field.valid();
+       field = decoder.ReadField()) {
+    if (context.ftrace_packet_allow_list.count(field.id()) != 0) {
+      return Redact::kNothing;
+    }
+  }
+
+  return Redact::kSomething;
+}
+
+}  // namespace
+
+//  packet {
+//    ftrace_events {
+//      event {                   <-- This is where we test the allow-list
+//        timestamp: 6702095044299807
+//        pid: 0
+//        cpu_idle {              <-- This is the event data (allow-list)
+//          state: 4294967295
+//          cpu_id: 2
+//        }
+//      }
+//    }
+//  }
+base::Status ScrubFtraceEvents::Transform(const Context& context,
+                                          std::string* packet) const {
+  if (packet == nullptr || packet->empty()) {
+    return base::ErrStatus("Cannot scrub null or empty trace packet.");
+  }
+
+  if (context.ftrace_packet_allow_list.empty()) {
+    return base::ErrStatus("Cannot scrub ftrace packets, missing allow-list.");
+  }
+
+  // If the packet has no ftrace events, skip it, leaving it unmodified.
+  protos::pbzero::TracePacket::Decoder query(*packet);
+  if (!query.has_ftrace_events()) {
+    return base::OkStatus();
+  }
+
+  protozero::HeapBuffered<protos::pbzero::TracePacket> packet_msg;
+
+  // packet.foreach_child.foreach( ... )
+  protozero::ProtoDecoder d_packet(*packet);
+  for (auto packet_child_it = d_packet.ReadField(); packet_child_it.valid();
+       packet_child_it = d_packet.ReadField()) {
+    // packet.child_not<ftrace_events>( ).do ( ... )
+    if (packet_child_it.id() != kFtraceEventsFieldNumber) {
+      AppendField(packet_child_it, packet_msg.get());
+      continue;
+    }
+
+    // To clarify, "ftrace_events" is the field name and "FtraceEventBundle" is
+    // the field type. The terms are often used interchangeably.
+    auto* ftrace_events_msg = packet_msg->set_ftrace_events();
+
+    // packet.child<ftrace_events>( ).foreach_child( ... )
+    protozero::ProtoDecoder ftrace_events(packet_child_it.as_bytes());
+    for (auto ftrace_events_it = ftrace_events.ReadField();
+         ftrace_events_it.valid();
+         ftrace_events_it = ftrace_events.ReadField()) {
+      // packet.child<ftrace_events>( ).child_not<event>( ).do ( ... )
+      if (ftrace_events_it.id() != kEventFieldNumber) {
+        AppendField(ftrace_events_it, ftrace_events_msg);
+        continue;
+      }
+
+      // packet.child<ftrace_events>( ).child_is<event>( ).do ( ... )
+      if (ProbeEvent(context, ftrace_events_it) == Redact::kNothing) {
+        AppendField(ftrace_events_it, ftrace_events_msg);
+        continue;
+      }
+
+      // Dropping packet = "is event" and "is redacted"
+    }
+  }
+
+  packet->assign(packet_msg.SerializeAsString());
+  return base::OkStatus();
+}
+
+// This is copied from "src/protozero/field.cc", but was modified to use the
+// serialization methods provided in "perfetto/protozero/message.h".
+void ScrubFtraceEvents::AppendField(const protozero::Field& field,
+                                    protozero::Message* message) {
+  auto id = field.id();
+  auto type = field.type();
+
+  switch (type) {
+    case protozero::proto_utils::ProtoWireType::kVarInt: {
+      message->AppendVarInt(id, field.raw_int_value());
+      return;
+    }
+
+    case protozero::proto_utils::ProtoWireType::kFixed32: {
+      message->AppendFixed(id, field.as_uint32());
+      return;
+    }
+
+    case protozero::proto_utils::ProtoWireType::kFixed64: {
+      message->AppendFixed(id, field.as_uint64());
+      return;
+    }
+
+    case protozero::proto_utils::ProtoWireType::kLengthDelimited: {
+      message->AppendBytes(id, field.data(), field.size());
+      return;
+    }
+  }
+
+  // A switch-statement would be preferred, but when using a switch statement,
+  // it complains that about case coverage.
+  PERFETTO_FATAL("Unknown field type %u", static_cast<uint8_t>(type));
+}
+
+}  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/scrub_ftrace_events.h b/src/trace_redaction/scrub_ftrace_events.h
new file mode 100644
index 0000000..9ae6a98
--- /dev/null
+++ b/src/trace_redaction/scrub_ftrace_events.h
@@ -0,0 +1,62 @@
+/*
+ * 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_SCRUB_FTRACE_EVENTS_H_
+#define SRC_TRACE_REDACTION_SCRUB_FTRACE_EVENTS_H_
+
+#include "perfetto/protozero/field.h"
+#include "perfetto/protozero/message.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+namespace perfetto::trace_redaction {
+
+//  Assumptions:
+//    1. This is a hot path (a lot of ftrace packets)
+//    2. Allocations are slower than CPU cycles.
+//
+//  Overview:
+//    To limit allocations pbzero protos are used to build a new packet. These
+//    protos are append-only, so data is not removed from the packet. Instead,
+//    data is optionally added to a new packet.
+//
+//    To limit allocations, the goal is to add data as large chucks rather than
+//    small fragments. To do this, a reactive strategy is used. All operations
+//    follow a probe-than-act pattern. Before any action can be taken, the
+//    input data must be queries to determine the scope. For example:
+//
+//        [------A------][---B---][------C------]
+//                                [---][-D-][---]
+//
+//        Assume that A and B don't need any work, they can be appended to the
+//        output as two large blocks.
+//
+//        Block C is different, there is a block D that falls within block C.
+//        Block D contains sensitive information and should be dropped. When C
+//        is probed, it will come back saying that C needs additional redaction.
+
+class ScrubFtraceEvents final : public TransformPrimitive {
+ public:
+  base::Status Transform(const Context& context,
+                         std::string* packet) const override;
+
+  // Used by `Transform()`. Only exposed for conformance testing.
+  static void AppendField(const protozero::Field& field,
+                          protozero::Message* message);
+};
+
+}  // namespace perfetto::trace_redaction
+
+#endif  // SRC_TRACE_REDACTION_SCRUB_FTRACE_EVENTS_H_
diff --git a/src/trace_redaction/scrub_ftrace_events_unittest.cc b/src/trace_redaction/scrub_ftrace_events_unittest.cc
new file mode 100644
index 0000000..179c504
--- /dev/null
+++ b/src/trace_redaction/scrub_ftrace_events_unittest.cc
@@ -0,0 +1,329 @@
+/*
+ * 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/scrub_ftrace_events.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "protos/perfetto/trace/ftrace/power.pbzero.h"
+#include "src/base/test/status_matchers.h"
+#include "test/gtest_and_gmock.h"
+
+#include "protos/perfetto/trace/ftrace/ftrace_event.gen.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "protos/perfetto/trace/ftrace/power.gen.h"
+#include "protos/perfetto/trace/ftrace/task.gen.h"
+#include "protos/perfetto/trace/ps/process_tree.gen.h"
+#include "protos/perfetto/trace/trace_packet.gen.h"
+
+namespace perfetto::trace_redaction {
+
+namespace {
+// task_rename should be in the allow-list.
+void AddTaskRename(protos::gen::FtraceEventBundle* bundle,
+                   int32_t pid,
+                   const std::string& old_comm,
+                   const std::string& new_comm) {
+  auto* e = bundle->add_event();
+  e->mutable_task_rename()->set_pid(pid);
+  e->mutable_task_rename()->set_oldcomm(old_comm);
+  e->mutable_task_rename()->set_newcomm(new_comm);
+}
+
+void AddClockSetRate(protos::gen::FtraceEventBundle* bundle,
+                     uint64_t cpu,
+                     const std::string& name,
+                     uint64_t state) {
+  auto* e = bundle->add_event();
+  e->mutable_clock_set_rate()->set_cpu_id(cpu);
+  e->mutable_clock_set_rate()->set_name(name);
+  e->mutable_clock_set_rate()->set_state(state);
+}
+
+}  // namespace
+
+class ScrubFtraceEventsSerializationTest : public testing::Test {
+ public:
+  ScrubFtraceEventsSerializationTest() = default;
+  ~ScrubFtraceEventsSerializationTest() override = default;
+
+ protected:
+  void SetUp() override {
+    std::array bytes = {
+        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+        0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+    };
+
+    protozero::HeapBuffered<protozero::Message> msg;
+    msg->AppendFixed<uint32_t>(field_id_fixed_32, 7);
+    msg->AppendFixed<uint64_t>(field_id_fixed_64, 9);
+    msg->AppendVarInt(field_id_var_int, 723);
+    msg->AppendBytes(field_id_bytes, bytes.data(), bytes.size());
+
+    message_.assign(msg.SerializeAsString());
+  }
+
+  std::string message_;
+
+  static constexpr uint32_t field_id_fixed_32 = 0;
+  static constexpr uint32_t field_id_fixed_64 = 1;
+  static constexpr uint32_t field_id_var_int = 2;
+  static constexpr uint32_t field_id_bytes = 3;
+};
+
+TEST_F(ScrubFtraceEventsSerializationTest, AppendFixedUint32) {
+  protozero::ProtoDecoder decoder(message_);
+
+  const auto& field = decoder.FindField(field_id_fixed_32);
+
+  std::string expected = "";
+  field.SerializeAndAppendTo(&expected);
+
+  protozero::HeapBuffered<protozero::Message> actual;
+  ScrubFtraceEvents::AppendField(field, actual.get());
+
+  ASSERT_EQ(actual.SerializeAsString(), expected);
+}
+
+TEST_F(ScrubFtraceEventsSerializationTest, AppendFixedUint64) {
+  protozero::ProtoDecoder decoder(message_);
+
+  const auto& field = decoder.FindField(field_id_fixed_64);
+
+  std::string expected = "";
+  field.SerializeAndAppendTo(&expected);
+
+  protozero::HeapBuffered<protozero::Message> actual;
+  ScrubFtraceEvents::AppendField(field, actual.get());
+
+  ASSERT_EQ(actual.SerializeAsString(), expected);
+}
+
+TEST_F(ScrubFtraceEventsSerializationTest, AppendBytes) {
+  protozero::ProtoDecoder decoder(message_);
+
+  const auto& field = decoder.FindField(field_id_bytes);
+
+  std::string expected = "";
+  field.SerializeAndAppendTo(&expected);
+
+  protozero::HeapBuffered<protozero::Message> actual;
+  ScrubFtraceEvents::AppendField(field, actual.get());
+
+  ASSERT_EQ(actual.SerializeAsString(), expected);
+}
+
+TEST_F(ScrubFtraceEventsSerializationTest, AppendVarInt) {
+  protozero::ProtoDecoder decoder(message_);
+
+  const auto& field = decoder.FindField(field_id_var_int);
+
+  std::string expected = "";
+  field.SerializeAndAppendTo(&expected);
+
+  protozero::HeapBuffered<protozero::Message> actual;
+  ScrubFtraceEvents::AppendField(field, actual.get());
+
+  ASSERT_EQ(actual.SerializeAsString(), expected);
+}
+
+TEST(ScrubFtraceEventsTest, ReturnErrorForNullPacket) {
+  // Have something in the allow-list to avoid that error.
+  Context context;
+  context.ftrace_packet_allow_list = {
+      protos::pbzero::FtraceEvent::kTaskRenameFieldNumber};
+
+  ScrubFtraceEvents scrub;
+  ASSERT_FALSE(scrub.Transform(context, nullptr).ok());
+}
+
+TEST(ScrubFtraceEventsTest, ReturnErrorForEmptyPacket) {
+  // Have something in the allow-list to avoid that error.
+  Context context;
+  context.ftrace_packet_allow_list = {
+      protos::pbzero::FtraceEvent::kTaskRenameFieldNumber};
+
+  std::string packet_str = "";
+
+  ScrubFtraceEvents scrub;
+  ASSERT_FALSE(scrub.Transform(context, &packet_str).ok());
+}
+
+TEST(ScrubFtraceEventsTest, ReturnErrorForEmptyAllowList) {
+  // The context will have no allow-list entries. ScrubFtraceEvents should fail.
+  Context context;
+
+  protos::gen::TracePacket packet;
+  std::string packet_str = packet.SerializeAsString();
+
+  ScrubFtraceEvents scrub;
+  ASSERT_FALSE(scrub.Transform(context, &packet_str).ok());
+}
+
+TEST(ScrubFtraceEventsTest, IgnorePacketWithNoFtraceEvents) {
+  protos::gen::TracePacket trace_packet;
+  auto* tree = trace_packet.mutable_process_tree();
+
+  auto& process = tree->mutable_processes()->emplace_back();
+  process.set_pid(1);
+  process.set_ppid(2);
+  process.set_uid(3);
+
+  auto& thread = tree->mutable_threads()->emplace_back();
+  thread.set_name("hello world");
+  thread.set_tgid(1);
+  thread.set_tid(135);
+
+  auto original_packet = trace_packet.SerializeAsString();
+  auto packet = original_packet;
+
+  Context context;
+  context.ftrace_packet_allow_list = {
+      protos::pbzero::FtraceEvent::kTaskRenameFieldNumber};
+
+  ScrubFtraceEvents transform;
+  ASSERT_OK(transform.Transform(context, &packet));
+
+  // The packet doesn't have any ftrace events. It should not be affected by
+  // this transform.
+  ASSERT_EQ(original_packet, packet);
+}
+
+// There are some values in a ftrace event that sits behind the ftrace bundle.
+// These values should be retained.
+TEST(ScrubFtraceEventsTest, KeepsFtraceBundleSiblingValues) {
+  protos::gen::TracePacket trace_packet;
+  auto* ftrace_events = trace_packet.mutable_ftrace_events();
+
+  ftrace_events->set_cpu(7);
+  AddTaskRename(ftrace_events, 7, "old_comm", "new_comm_7");
+  AddClockSetRate(ftrace_events, 7, "cool cpu name", 1);
+
+  auto original_packet = trace_packet.SerializeAsString();
+  auto packet = original_packet;
+
+  Context context;
+  context.ftrace_packet_allow_list = {
+      protos::pbzero::FtraceEvent::kTaskRenameFieldNumber};
+
+  ScrubFtraceEvents transform;
+  ASSERT_OK(transform.Transform(context, &packet));
+
+  protos::gen::TracePacket gen_packet;
+  gen_packet.ParseFromString(packet);
+
+  ASSERT_TRUE(gen_packet.has_ftrace_events());
+  const auto& gen_events = gen_packet.ftrace_events();
+
+  // Because the CPU sits beside the event list, and not inside the event list,
+  // the CPU value should be retained.
+  ASSERT_TRUE(gen_events.has_cpu());
+  ASSERT_EQ(gen_events.cpu(), 7u);
+
+  // ClockSetRate should be dropped. Only TaskRename should remain.
+  ASSERT_EQ(gen_events.event_size(), 1);
+  ASSERT_FALSE(gen_events.event().front().has_clock_set_rate());
+  ASSERT_TRUE(gen_events.event().front().has_task_rename());
+}
+
+TEST(ScrubFtraceEventsTest, KeepsAllowedEvents) {
+  Context context;
+  context.ftrace_packet_allow_list = {
+      protos::pbzero::FtraceEvent::kTaskRenameFieldNumber,
+  };
+
+  protos::gen::TracePacket before;
+  AddTaskRename(before.mutable_ftrace_events(), 7, "old_comm", "new_comm_7");
+  AddTaskRename(before.mutable_ftrace_events(), 8, "old_comm", "new_comm_8");
+  AddTaskRename(before.mutable_ftrace_events(), 9, "old_comm", "new_comm_9");
+
+  auto before_str = before.SerializeAsString();
+  auto after_str = before_str;
+
+  ScrubFtraceEvents transform;
+  ASSERT_OK(transform.Transform(context, &after_str));
+
+  protos::gen::TracePacket after;
+  after.ParseFromString(after_str);
+
+  // Implementation detail: ScrubFtraceEvents may change entry order. The diff
+  // must be order independent. Sort the events by pid, this will make it easier
+  // to assert values.
+  auto events = after.ftrace_events().event();
+  std::sort(events.begin(), events.end(),
+            [](const auto& l, const auto& r) { return l.pid() < r.pid(); });
+
+  ASSERT_EQ(events.size(), 3u);
+
+  ASSERT_TRUE(events[0].has_task_rename());
+  ASSERT_EQ(events[0].task_rename().pid(), 7);
+  ASSERT_EQ(events[0].task_rename().oldcomm(), "old_comm");
+  ASSERT_EQ(events[0].task_rename().newcomm(), "new_comm_7");
+
+  ASSERT_TRUE(events[1].has_task_rename());
+  ASSERT_EQ(events[1].task_rename().pid(), 8);
+  ASSERT_EQ(events[1].task_rename().oldcomm(), "old_comm");
+  ASSERT_EQ(events[1].task_rename().newcomm(), "new_comm_8");
+
+  ASSERT_TRUE(events[2].has_task_rename());
+  ASSERT_EQ(events[2].task_rename().pid(), 9);
+  ASSERT_EQ(events[2].task_rename().oldcomm(), "old_comm");
+  ASSERT_EQ(events[2].task_rename().newcomm(), "new_comm_9");
+}
+
+// Only the specific non-allowed events should be removed from the event list.
+TEST(ScrubFtraceEventsTest, OnlyDropsNotAllowedEvents) {
+  // AddTaskRename >> Keep
+  // AddClockSetRate >> Drop
+  protos::gen::TracePacket original_packet;
+  AddTaskRename(original_packet.mutable_ftrace_events(), 7, "old_comm",
+                "new_comm_7");
+  AddClockSetRate(original_packet.mutable_ftrace_events(), 0, "cool cpu name",
+                  1);
+  AddTaskRename(original_packet.mutable_ftrace_events(), 8, "old_comm",
+                "new_comm_8");
+  AddTaskRename(original_packet.mutable_ftrace_events(), 9, "old_comm",
+                "new_comm_9");
+  auto packet = original_packet.SerializeAsString();
+
+  Context context;
+  context.ftrace_packet_allow_list = {
+      protos::pbzero::FtraceEvent::kTaskRenameFieldNumber};
+
+  ScrubFtraceEvents transform;
+  ASSERT_OK(transform.Transform(context, &packet));
+
+  protos::gen::TracePacket modified_packet;
+  ASSERT_TRUE(modified_packet.ParseFromString(packet));
+
+  // Only the clock set rate event should have been removed (drop 1 of the 4
+  // events).
+  ASSERT_TRUE(modified_packet.has_ftrace_events());
+  ASSERT_EQ(modified_packet.ftrace_events().event_size(), 3);
+
+  // All ftrace events should be rename events.
+  const auto& events = modified_packet.ftrace_events().event();
+
+  ASSERT_TRUE(events.at(0).has_task_rename());
+  ASSERT_EQ(events.at(0).task_rename().pid(), 7);
+
+  ASSERT_TRUE(events.at(1).has_task_rename());
+  ASSERT_EQ(events.at(1).task_rename().pid(), 8);
+
+  ASSERT_TRUE(events.at(2).has_task_rename());
+  ASSERT_EQ(events.at(2).task_rename().pid(), 9);
+}
+
+}  // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/scrub_trace_packet.h b/src/trace_redaction/scrub_trace_packet.h
index fbf89ca..75c5722 100644
--- a/src/trace_redaction/scrub_trace_packet.h
+++ b/src/trace_redaction/scrub_trace_packet.h
@@ -21,8 +21,17 @@
 
 namespace perfetto::trace_redaction {
 
-// Drops whole trace packets based on an allow-list (e.g. retain ProcessTree
-// packets).
+// TODO(vaage): This primitive DOES NOT handle redacting sched_switch and
+// sched_waking ftrace events. These events contain a large amount of "to be
+// redacted" information AND there are a high quantity of them AND they are
+// large packets. As such, this primitive is not enough and an ADDITIONAL
+// primitive is required.
+
+// Goes through individual ftrace packs and drops the ftrace packets from the
+// trace packet without modifying the surround fields.
+//
+// ScrubTracePacket does not respect field order - i.e. the field order going
+// may not match the field order going out.
 class ScrubTracePacket final : public TransformPrimitive {
  public:
   base::Status Transform(const Context& context,
diff --git a/src/trace_redaction/trace_redaction_framework.h b/src/trace_redaction/trace_redaction_framework.h
index 76b8594..d22ecd2 100644
--- a/src/trace_redaction/trace_redaction_framework.h
+++ b/src/trace_redaction/trace_redaction_framework.h
@@ -117,6 +117,45 @@
   // Because "data" is a "one of", if no field in "trace_packet_allow_list" can
   // be found, it packet should be removed.
   base::FlatSet<uint32_t> trace_packet_allow_list;
+
+  // Ftrace packets contain a "one of" entry called "event". Within the scope of
+  // a ftrace event, the event can be considered the payload and other other
+  // values can be considered metadata (e.g. timestamp and pid).
+  //
+  // A ftrace event should be removed if:
+  //
+  //  ... we know it contains too much sensitive information
+  //
+  //  ... we know it contains sensitive information and we have some ideas on
+  //      to remove it, but don't have the resources to do it right now (e.g.
+  //      print).
+  //
+  //  ... we don't see value in including it
+  //
+  // "ftrace_packet_allow_list" contains field ids of ftrace packets that we
+  // want to pass onto later transformations. An example would be:
+  //
+  //  ... kSchedWakingFieldNumber because it contains cpu activity information
+  //
+  // Compared against track days, the rules around removing ftrace packets are
+  // complicated because...
+  //
+  //  packet {
+  //    ftrace_packets {  <-- ONE-OF    (1)
+  //      event {         <-- REPEATED  (2)
+  //        cpu_idle { }  <-- ONE-OF    (3)
+  //      }
+  //      event { ... }
+  //    }
+  //  }
+  //
+  //  1.  A ftrace packet will populate the one-of slot in the trace packet.
+  //
+  //  2.  A ftrace packet can have multiple events
+  //
+  //  3.  In this example, a cpu_idle event populates the one-of slot in the
+  //      ftrace event
+  base::FlatSet<uint32_t> ftrace_packet_allow_list;
 };
 
 // Responsible for extracting low-level data from the trace and storing it in
diff --git a/src/trace_redaction/trace_redactor.cc b/src/trace_redaction/trace_redactor.cc
index 94168e6..49fcf8f 100644
--- a/src/trace_redaction/trace_redactor.cc
+++ b/src/trace_redaction/trace_redactor.cc
@@ -161,17 +161,12 @@
       continue;
     }
 
-    protozero::HeapBuffered<> serializer;
-    auto* packet_message =
-        serializer->BeginNestedMessage<TracePacket>(Trace::kPacketFieldNumber);
-    packet_message->AppendRawProtoBytes(packet.data(), packet.size());
-    packet_message->Finalize();
-    serializer->Finalize();
+    protozero::HeapBuffered<protos::pbzero::Trace> serializer;
+    serializer->add_packet()->AppendRawProtoBytes(packet.data(), packet.size());
+    packet.assign(serializer.SerializeAsString());
 
-    auto encoded_packet = serializer.SerializeAsString();
-
-    if (const auto exported_data = base::WriteAll(
-            dest_fd.get(), encoded_packet.data(), encoded_packet.size());
+    if (const auto exported_data =
+            base::WriteAll(dest_fd.get(), packet.data(), packet.size());
         exported_data <= 0) {
       return base::ErrStatus("Failed to write redacted trace to disk");
     }
diff --git a/src/trace_redaction/trace_redactor_integrationtest.cc b/src/trace_redaction/trace_redactor_integrationtest.cc
index 50074b1..e3fc4d1 100644
--- a/src/trace_redaction/trace_redactor_integrationtest.cc
+++ b/src/trace_redaction/trace_redactor_integrationtest.cc
@@ -15,28 +15,34 @@
  */
 
 #include <cstdint>
-#include <memory>
 #include <string>
 #include <string_view>
 #include <vector>
 
 #include "perfetto/base/status.h"
 #include "perfetto/ext/base/file_utils.h"
-#include "perfetto/ext/base/temp_file.h"
+#include "src/base/test/status_matchers.h"
 #include "src/base/test/tmp_dir_tree.h"
 #include "src/base/test/utils.h"
 #include "src/trace_redaction/find_package_uid.h"
+#include "src/trace_redaction/populate_allow_lists.h"
 #include "src/trace_redaction/prune_package_list.h"
+#include "src/trace_redaction/scrub_ftrace_events.h"
+#include "src/trace_redaction/scrub_trace_packet.h"
 #include "src/trace_redaction/trace_redaction_framework.h"
 #include "src/trace_redaction/trace_redactor.h"
 #include "test/gtest_and_gmock.h"
 
+#include "protos/perfetto/trace//ftrace/ftrace_event.pbzero.h"
 #include "protos/perfetto/trace/android/packages_list.pbzero.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
 #include "protos/perfetto/trace/trace.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
 
 namespace perfetto::trace_redaction {
 
 namespace {
+using FtraceEvent = protos::pbzero::FtraceEvent;
 using PackagesList = protos::pbzero::PackagesList;
 using PackageInfo = protos::pbzero::PackagesList::PackageInfo;
 using Trace = protos::pbzero::Trace;
@@ -55,6 +61,19 @@
  protected:
   void SetUp() override {
     src_trace_ = base::GetTestDataPath(std::string(kTracePath));
+
+    // Add every primitive to the redactor. This should mirror the production
+    // configuration. This configuration may differ to help with verifying the
+    // results.
+    redactor_.collectors()->emplace_back(new FindPackageUid());
+    redactor_.builders()->emplace_back(new PopulateAllowlists());
+    redactor_.transformers()->emplace_back(new PrunePackageList());
+    redactor_.transformers()->emplace_back(new ScrubTracePacket());
+    redactor_.transformers()->emplace_back(new ScrubFtraceEvents());
+
+    // Set the package name to "just some package name". If a specific package
+    // name is needed, it should overwrite this value.
+    context_.package_name = "com.google.omadm.trigger";
   }
 
   const std::string& src_trace() const { return src_trace_; }
@@ -77,33 +96,80 @@
     return infos;
   }
 
+  static base::StatusOr<std::string> ReadRawTrace(const std::string& path) {
+    std::string redacted_buffer;
+
+    if (base::ReadFile(path, &redacted_buffer)) {
+      return redacted_buffer;
+    }
+
+    return base::ErrStatus("Failed to read %s", path.c_str());
+  }
+
+  // NOTE - this will include fields like "timestamp" and "pid".
+  static void GetEventFields(const Trace::Decoder& trace,
+                             base::FlatSet<uint32_t>* set) {
+    for (auto packet_it = trace.packet(); packet_it; ++packet_it) {
+      TracePacket::Decoder packet(*packet_it);
+
+      if (!packet.has_ftrace_events()) {
+        continue;
+      }
+
+      protos::pbzero::FtraceEventBundle::Decoder bundle(packet.ftrace_events());
+
+      if (!bundle.has_event()) {
+        continue;
+      }
+
+      for (auto events_it = bundle.event(); events_it; ++events_it) {
+        protozero::ProtoDecoder event(*events_it);
+
+        for (auto event_it = event.ReadField(); event_it.valid();
+             event_it = event.ReadField()) {
+          set->insert(event_it.id());
+        }
+      }
+    }
+  }
+
+  static base::StatusOr<protozero::ConstBytes> FindFirstFtraceEvents(
+      const Trace::Decoder& trace) {
+    for (auto packet_it = trace.packet(); packet_it; ++packet_it) {
+      TracePacket::Decoder packet(*packet_it);
+
+      if (packet.has_ftrace_events()) {
+        return packet.ftrace_events();
+      }
+    }
+
+    return base::ErrStatus("Failed to find ftrace events");
+  }
+
   std::string src_trace_;
   base::TmpDirTree tmp_dir_;
+
+  Context context_;
+  TraceRedactor redactor_;
 };
 
 TEST_F(TraceRedactorIntegrationTest, FindsPackageAndFiltersPackageList) {
-  TraceRedactor redaction;
-  redaction.collectors()->emplace_back(new FindPackageUid());
-  redaction.transformers()->emplace_back(new PrunePackageList());
+  context_.package_name = "com.Unity.com.unity.multiplayer.samples.coop";
 
-  Context context;
-  context.package_name = "com.Unity.com.unity.multiplayer.samples.coop";
-
-  auto result = redaction.Redact(
-      src_trace(), tmp_dir_.AbsolutePath("dst.pftrace"), &context);
+  auto result = redactor_.Redact(
+      src_trace(), tmp_dir_.AbsolutePath("dst.pftrace"), &context_);
   tmp_dir_.TrackFile("dst.pftrace");
 
-  ASSERT_TRUE(result.ok()) << result.message();
+  ASSERT_OK(result);
 
-  std::string redacted_buffer;
-  ASSERT_TRUE(
-      base::ReadFile(tmp_dir_.AbsolutePath("dst.pftrace"), &redacted_buffer));
+  ASSERT_OK_AND_ASSIGN(auto redacted_buffer,
+                       ReadRawTrace(tmp_dir_.AbsolutePath("dst.pftrace")));
 
   Trace::Decoder redacted_trace(redacted_buffer);
   std::vector<protozero::ConstBytes> infos = GetPackageInfos(redacted_trace);
 
-  ASSERT_TRUE(context.package_uid.has_value());
-  ASSERT_EQ(NormalizeUid(context.package_uid.value()),
+  ASSERT_TRUE(context_.package_uid.has_value());
+  ASSERT_EQ(NormalizeUid(context_.package_uid.value()),
             NormalizeUid(kPackageUid));
 
   // It is possible for two packages_list to appear in the trace. The
@@ -132,21 +198,15 @@
 // the package list, so there is no way to differentiate these packages (only
 // the uid is used later), so each entry should remain.
 TEST_F(TraceRedactorIntegrationTest, RetainsAllInstancesOfUid) {
-  TraceRedactor redaction;
-  redaction.collectors()->emplace_back(new FindPackageUid());
-  redaction.transformers()->emplace_back(new PrunePackageList());
+  context_.package_name = "com.google.android.networkstack.tethering";
 
-  Context context;
-  context.package_name = "com.google.android.networkstack.tethering";
-
-  auto result = redaction.Redact(
-      src_trace(), tmp_dir_.AbsolutePath("dst.pftrace"), &context);
+  auto result = redactor_.Redact(
+      src_trace(), tmp_dir_.AbsolutePath("dst.pftrace"), &context_);
   tmp_dir_.TrackFile("dst.pftrace");
-  ASSERT_TRUE(result.ok()) << result.message();
+  ASSERT_OK(result);
 
-  std::string redacted_buffer;
-  ASSERT_TRUE(
-      base::ReadFile(tmp_dir_.AbsolutePath("dst.pftrace"), &redacted_buffer));
+  ASSERT_OK_AND_ASSIGN(auto redacted_buffer,
+                       ReadRawTrace(tmp_dir_.AbsolutePath("dst.pftrace")));
 
   Trace::Decoder redacted_trace(redacted_buffer);
   std::vector<protozero::ConstBytes> infos = GetPackageInfos(redacted_trace);
@@ -174,5 +234,110 @@
   ASSERT_EQ(package_names[7], "com.google.android.networkstack.tethering");
 }
 
+// Makes sure all not-allowed ftrace event is removed from a trace.
+TEST_F(TraceRedactorIntegrationTest, RemovesFtraceEvents) {
+  auto pre_redaction_file = src_trace();
+  auto post_redaction_file = tmp_dir_.AbsolutePath("dst.pftrace");
+
+  // We know that there are two oom score updates in the test trace. These
+  // events are not in the allowlist and should be dropped.
+  auto pre_redaction_buffer = ReadRawTrace(pre_redaction_file);
+  ASSERT_OK(pre_redaction_buffer) << pre_redaction_buffer.status().message();
+  Trace::Decoder pre_redaction_trace(*pre_redaction_buffer);
+
+  base::FlatSet<uint32_t> pre_redaction_event_types;
+  GetEventFields(pre_redaction_trace, &pre_redaction_event_types);
+  ASSERT_GT(pre_redaction_event_types.count(
+                FtraceEvent::kOomScoreAdjUpdateFieldNumber),
+            0u);
+
+  auto result =
+      redactor_.Redact(pre_redaction_file, post_redaction_file, &context_);
+  tmp_dir_.TrackFile("dst.pftrace");
+  ASSERT_OK(result) << result.message();
+
+  auto post_redaction_buffer = ReadRawTrace(post_redaction_file);
+  ASSERT_OK(post_redaction_buffer) << post_redaction_buffer.status().message();
+  Trace::Decoder post_redaction_trace(*post_redaction_buffer);
+
+  base::FlatSet<uint32_t> post_redaction_event_types;
+  GetEventFields(post_redaction_trace, &post_redaction_event_types);
+  ASSERT_EQ(post_redaction_event_types.count(
+                FtraceEvent::kOomScoreAdjUpdateFieldNumber),
+            0u);
+}
+
+// When a event is dropped from ftrace_events, only that event should be droped,
+// the other events in the ftrace_events should be retained.
+TEST_F(TraceRedactorIntegrationTest,
+       RetainsFtraceEventsWhenRemovingFtraceEvent) {
+  auto pre_redaction_file = src_trace();
+  auto post_redaction_file = tmp_dir_.AbsolutePath("dst.pftrace");
+
+  auto pre_redaction_buffer = ReadRawTrace(pre_redaction_file);
+  ASSERT_OK(pre_redaction_buffer) << pre_redaction_buffer.status().message();
+
+  Trace::Decoder pre_redaction_trace(*pre_redaction_buffer);
+
+  auto pre_redaction_first_events = FindFirstFtraceEvents(pre_redaction_trace);
+  ASSERT_OK(pre_redaction_first_events)
+      << pre_redaction_first_events.status().message();
+
+  auto result =
+      redactor_.Redact(pre_redaction_file, post_redaction_file, &context_);
+  tmp_dir_.TrackFile("dst.pftrace");
+  ASSERT_OK(result) << result.message();
+
+  auto post_redaction_buffer = ReadRawTrace(post_redaction_file);
+  ASSERT_OK(post_redaction_buffer) << post_redaction_buffer.status().message();
+
+  Trace::Decoder post_redaction_trace(*post_redaction_buffer);
+
+  auto post_redaction_ftrace_events =
+      FindFirstFtraceEvents(post_redaction_trace);
+  ASSERT_OK(post_redaction_ftrace_events)
+      << post_redaction_ftrace_events.status().message();
+
+  base::FlatSet<uint32_t> events_before;
+  GetEventFields(pre_redaction_trace, &events_before);
+  ASSERT_EQ(events_before.size(), 14u);
+  ASSERT_TRUE(events_before.count(FtraceEvent::kTimestampFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kPidFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kPrintFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kSchedSwitchFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kCpuFrequencyFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kCpuIdleFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kSchedWakeupFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kSchedWakingFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kSchedWakeupNewFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kTaskNewtaskFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kTaskRenameFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kSchedProcessExitFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kSchedProcessFreeFieldNumber));
+  ASSERT_TRUE(events_before.count(FtraceEvent::kOomScoreAdjUpdateFieldNumber));
+
+  base::FlatSet<uint32_t> events_after;
+  GetEventFields(post_redaction_trace, &events_after);
+  ASSERT_EQ(events_after.size(), 9u);
+
+  // Retained.
+  ASSERT_TRUE(events_after.count(FtraceEvent::kTimestampFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kPidFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kSchedSwitchFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kCpuFrequencyFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kCpuIdleFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kSchedWakingFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kTaskNewtaskFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kTaskRenameFieldNumber));
+  ASSERT_TRUE(events_after.count(FtraceEvent::kSchedProcessFreeFieldNumber));
+
+  // Dropped.
+  ASSERT_FALSE(events_after.count(FtraceEvent::kPrintFieldNumber));
+  ASSERT_FALSE(events_after.count(FtraceEvent::kSchedWakeupFieldNumber));
+  ASSERT_FALSE(events_after.count(FtraceEvent::kSchedWakeupNewFieldNumber));
+  ASSERT_FALSE(events_after.count(FtraceEvent::kSchedProcessExitFieldNumber));
+  ASSERT_FALSE(events_after.count(FtraceEvent::kOomScoreAdjUpdateFieldNumber));
+}
+
 }  // namespace
 }  // namespace perfetto::trace_redaction