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