Trace Redaction - Reject invalid traces
In order to ensure proper redaction, trace redaction can and should
be very strict about its input. Small errors (e.g. missing a proc
free packet) can lead to major issues (e.g. two threads get merged
into a single thread because the proc free message was missing).
Bug: 338425440
Change-Id: I0b8abcd5fc2f196c8bf94f9edfbcf223319d0770
diff --git a/Android.bp b/Android.bp
index 7e67314..a770cb5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -13609,6 +13609,7 @@
"src/trace_redaction/suspend_resume.cc",
"src/trace_redaction/trace_redaction_framework.cc",
"src/trace_redaction/trace_redactor.cc",
+ "src/trace_redaction/verify_integrity.cc",
],
}
@@ -13632,6 +13633,7 @@
"src/trace_redaction/remap_scheduling_events_unittest.cc",
"src/trace_redaction/remove_process_free_comm_unittest.cc",
"src/trace_redaction/suspend_resume_unittest.cc",
+ "src/trace_redaction/verify_integrity_unittest.cc",
],
}
diff --git a/src/trace_redaction/BUILD.gn b/src/trace_redaction/BUILD.gn
index b4aecb6..c9ab86e 100644
--- a/src/trace_redaction/BUILD.gn
+++ b/src/trace_redaction/BUILD.gn
@@ -81,6 +81,8 @@
"trace_redaction_framework.h",
"trace_redactor.cc",
"trace_redactor.h",
+ "verify_integrity.cc",
+ "verify_integrity.h",
]
deps = [
"../../gn:default_deps",
@@ -111,6 +113,7 @@
"scrub_process_trees_integrationtest.cc",
"trace_redaction_integration_fixture.cc",
"trace_redaction_integration_fixture.h",
+ "verify_integrity_integrationtest.cc",
]
deps = [
":trace_redaction",
@@ -146,6 +149,7 @@
"remap_scheduling_events_unittest.cc",
"remove_process_free_comm_unittest.cc",
"suspend_resume_unittest.cc",
+ "verify_integrity_unittest.cc",
]
deps = [
":trace_redaction",
diff --git a/src/trace_redaction/main.cc b/src/trace_redaction/main.cc
index 78bf3d0..798d1a0 100644
--- a/src/trace_redaction/main.cc
+++ b/src/trace_redaction/main.cc
@@ -39,6 +39,7 @@
#include "src/trace_redaction/suspend_resume.h"
#include "src/trace_redaction/trace_redaction_framework.h"
#include "src/trace_redaction/trace_redactor.h"
+#include "src/trace_redaction/verify_integrity.h"
namespace perfetto::trace_redaction {
@@ -48,6 +49,12 @@
std::string_view package_name) {
TraceRedactor redactor;
+ // VerifyIntegrity breaks the CollectPrimitive pattern. Instead of writing to
+ // the context, its job is to read trace packets and return errors if any
+ // packet does not look "correct". This primitive is added first in an effort
+ // to detect and react to bad input before other collectors run.
+ redactor.emplace_collect<VerifyIntegrity>();
+
// Add all collectors.
redactor.emplace_collect<FindPackageUid>();
redactor.emplace_collect<CollectTimelineEvents>();
diff --git a/src/trace_redaction/verify_integrity.cc b/src/trace_redaction/verify_integrity.cc
new file mode 100644
index 0000000..53fdc0b
--- /dev/null
+++ b/src/trace_redaction/verify_integrity.cc
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Projectf
+ *
+ * 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/verify_integrity.h"
+
+#include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
+#include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto::trace_redaction {
+namespace {
+// All constants are from
+// "system/core/libcutils/include/private/android_filesystem_config.h"
+//
+// AID 1000 == system (those are probably frame_timeline packets you will see
+// those on) AID 9999 is nobody == traced/traced_probes
+constexpr int32_t kAidSystem = 1000;
+constexpr int32_t kAidNobody = 9999;
+} // namespace
+
+base::Status VerifyIntegrity::Collect(
+ const protos::pbzero::TracePacket::Decoder& packet,
+ Context*) const {
+ if (!packet.has_trusted_uid()) {
+ return base::ErrStatus(
+ "VerifyIntegrity: missing field (TracePacket::kTrustedUid).");
+ }
+
+ if (packet.trusted_uid() != kAidSystem &&
+ packet.trusted_uid() != kAidNobody) {
+ return base::ErrStatus(
+ "VerifyIntegrity: invalid field (TracePacket::kTrustedUid).");
+ }
+
+ if (packet.has_ftrace_events()) {
+ protos::pbzero::FtraceEventBundle::Decoder ftrace_events(
+ packet.ftrace_events());
+
+ // The other clocks in ftrace are only used on very old kernel versions. No
+ // device with V should have such an old version. As a failsafe though,
+ // check that the ftrace_clock field is unset to ensure no invalid
+ // timestamps get by.
+ if (ftrace_events.has_ftrace_clock()) {
+ return base::ErrStatus(
+ "VerifyIntegrity: unexpected field "
+ "(FtraceEventBundle::kFtraceClock).");
+ }
+
+ // Every ftrace event bundle should have a CPU field. This is necessary for
+ // switch/waking redaction to work.
+ if (!ftrace_events.has_cpu()) {
+ return base::ErrStatus(
+ "VerifyIntegrity: missing field (FtraceEventBundle::kCpu).");
+ }
+
+ for (auto event_buffer = ftrace_events.event(); event_buffer;
+ ++event_buffer) {
+ protos::pbzero::FtraceEvent::Decoder event(*event_buffer);
+
+ if (!event.has_timestamp()) {
+ return base::ErrStatus(
+ "VerifyIntegrity: missing field (FtraceEvent::kTimestamp).");
+ }
+
+ if (!event.has_pid()) {
+ return base::ErrStatus(
+ "VerifyIntegrity: missing field (FtraceEvent::kPid).");
+ }
+ }
+ }
+
+ // If there is a process tree, there should be a timestamp on the packet. This
+ // is the only way to know when the process tree was collected.
+ if (packet.has_process_tree() && !packet.has_timestamp()) {
+ return base::ErrStatus(
+ "VerifyIntegrity: missing fields (TracePacket::kProcessTree + "
+ "TracePacket::kTimestamp).");
+ }
+
+ // If there are a process stats, there should be a timestamp on the packet.
+ // This is the only way to know when the stats were collected.
+ if (packet.has_process_stats() && !packet.has_timestamp()) {
+ return base::ErrStatus(
+ "VerifyIntegrity: missing fields (TracePacket::kProcessStats + "
+ "TracePacket::kTimestamp).");
+ }
+
+ return base::OkStatus();
+}
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/verify_integrity.h b/src/trace_redaction/verify_integrity.h
new file mode 100644
index 0000000..2ac5232
--- /dev/null
+++ b/src/trace_redaction/verify_integrity.h
@@ -0,0 +1,38 @@
+/*
+ * 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 "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
+#include "src/trace_redaction/trace_redaction_framework.h"
+
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+#ifndef SRC_TRACE_REDACTION_VERIFY_INTEGRITY_H_
+#define SRC_TRACE_REDACTION_VERIFY_INTEGRITY_H_
+
+namespace perfetto::trace_redaction {
+
+// This breaks the normal collect primitive pattern. Rather than collecting
+// information, it looks at packets and returns an error if the packet violates
+// any requirements.
+class VerifyIntegrity : public CollectPrimitive {
+ public:
+ base::Status Collect(const protos::pbzero::TracePacket::Decoder& packet,
+ Context* context) const override;
+};
+
+} // namespace perfetto::trace_redaction
+
+#endif // SRC_TRACE_REDACTION_VERIFY_INTEGRITY_H_
diff --git a/src/trace_redaction/verify_integrity_integrationtest.cc b/src/trace_redaction/verify_integrity_integrationtest.cc
new file mode 100644
index 0000000..d97f3c9
--- /dev/null
+++ b/src/trace_redaction/verify_integrity_integrationtest.cc
@@ -0,0 +1,35 @@
+/*
+ * 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/base/test/status_matchers.h"
+#include "src/trace_redaction/trace_redaction_integration_fixture.h"
+#include "src/trace_redaction/trace_redactor.h"
+#include "src/trace_redaction/verify_integrity.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto::trace_redaction {
+
+class VerifyIntegrityIntegrationTest : public testing::Test,
+ public TraceRedactionIntegrationFixure {
+};
+
+// The trace used in the integration tests should pass the verify primitive.
+TEST_F(VerifyIntegrityIntegrationTest, VerifiesValidTrace) {
+ trace_redactor()->emplace_collect<VerifyIntegrity>();
+ ASSERT_OK(Redact());
+}
+
+} // namespace perfetto::trace_redaction
diff --git a/src/trace_redaction/verify_integrity_unittest.cc b/src/trace_redaction/verify_integrity_unittest.cc
new file mode 100644
index 0000000..638f056
--- /dev/null
+++ b/src/trace_redaction/verify_integrity_unittest.cc
@@ -0,0 +1,196 @@
+/*
+ * 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/verify_integrity.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_bundle.gen.h"
+#include "protos/perfetto/trace/trace_packet.gen.h"
+
+namespace perfetto::trace_redaction {
+
+namespace {
+// The trace packet uid must be 1000 (system) or 9999 (nobody). If it is
+// anything else, the packet is invalid.
+int32_t kNobodyUid = 9999;
+int32_t kSystemUid = 1000;
+int32_t kInvalidUid = 9;
+
+uint64_t kSomeTime = 1234;
+uint32_t kSomePid = 7;
+
+uint32_t kSomeCpu = 3;
+} // namespace
+
+class VerifyIntegrityUnitTest : public testing::Test {
+ protected:
+ base::Status Verify(const protos::gen::TracePacket& packet) {
+ auto packet_buffer = packet.SerializeAsString();
+ protos::pbzero::TracePacket::Decoder packer_decoder(packet_buffer);
+
+ VerifyIntegrity verify;
+ Context context;
+ return verify.Collect(packer_decoder, &context);
+ }
+};
+
+TEST_F(VerifyIntegrityUnitTest, InvalidPacketNoUid) {
+ protos::gen::TracePacket packet;
+ ASSERT_FALSE(Verify(packet).ok());
+}
+
+TEST_F(VerifyIntegrityUnitTest, InvalidPacketInvalidUid) {
+ protos::gen::TracePacket packet;
+
+ packet.set_trusted_uid(kInvalidUid);
+
+ ASSERT_FALSE(Verify(packet).ok());
+}
+
+TEST_F(VerifyIntegrityUnitTest, ValidPacketSystemUid) {
+ protos::gen::TracePacket packet;
+
+ packet.set_trusted_uid(kSystemUid);
+
+ ASSERT_OK(Verify(packet));
+}
+
+TEST_F(VerifyIntegrityUnitTest, ValidPacketNobodyUid) {
+ protos::gen::TracePacket packet;
+
+ packet.set_trusted_uid(kNobodyUid);
+
+ ASSERT_OK(Verify(packet));
+}
+
+TEST_F(VerifyIntegrityUnitTest, InvalidPacketFtraceBundleMissingCpu) {
+ protos::gen::TracePacket packet;
+
+ packet.set_trusted_uid(kSystemUid);
+
+ packet.mutable_ftrace_events();
+
+ ASSERT_FALSE(Verify(packet).ok());
+}
+
+TEST_F(VerifyIntegrityUnitTest, ValidPacketFtraceBundle) {
+ protos::gen::TracePacket packet;
+
+ packet.set_trusted_uid(kSystemUid);
+
+ // A bundle doesn't need to have anything in it (other than cpu).
+ auto* ftrace_events = packet.mutable_ftrace_events();
+ ftrace_events->set_cpu(kSomeCpu);
+
+ ASSERT_OK(Verify(packet));
+}
+
+TEST_F(VerifyIntegrityUnitTest, InvalidPacketFtraceEventMissingPid) {
+ protos::gen::TracePacket packet;
+
+ packet.set_trusted_uid(kSystemUid);
+
+ auto* ftrace_events = packet.mutable_ftrace_events();
+ ftrace_events->set_cpu(kSomeCpu);
+
+ // A valid event has a pid and timestamp. Add the time (but not the pid) to
+ // ensure the pid caused the error.
+ auto* event = ftrace_events->add_event();
+ event->set_timestamp(kSomeTime);
+
+ ASSERT_FALSE(Verify(packet).ok());
+}
+
+TEST_F(VerifyIntegrityUnitTest, InvalidPacketFtraceEventMissingTime) {
+ protos::gen::TracePacket packet;
+
+ packet.set_trusted_uid(kSystemUid);
+
+ auto* ftrace_events = packet.mutable_ftrace_events();
+ ftrace_events->set_cpu(kSomeCpu);
+
+ // A valid event has a pid and timestamp. Add the pid (but not the time) to
+ // ensure the time caused the error.
+ auto* event = ftrace_events->add_event();
+ event->set_pid(kSomePid);
+
+ ASSERT_FALSE(Verify(packet).ok());
+}
+
+TEST_F(VerifyIntegrityUnitTest, ValidPacketFtraceEvent) {
+ protos::gen::TracePacket packet;
+
+ packet.set_trusted_uid(kSystemUid);
+
+ auto* ftrace_events = packet.mutable_ftrace_events();
+ ftrace_events->set_cpu(kSomeCpu);
+
+ auto* event = ftrace_events->add_event();
+ event->set_pid(kSomePid);
+ event->set_timestamp(kSomeTime);
+
+ ASSERT_OK(Verify(packet));
+}
+
+TEST_F(VerifyIntegrityUnitTest, InvalidPacketProcessTreeMissingTime) {
+ protos::gen::TracePacket packet;
+
+ packet.set_trusted_uid(kSystemUid);
+
+ // When the packet has a process tree, the packet must have a timestamp.
+ packet.mutable_process_tree();
+
+ ASSERT_FALSE(Verify(packet).ok());
+}
+
+TEST_F(VerifyIntegrityUnitTest, ValidPacketProcessTree) {
+ protos::gen::TracePacket packet;
+
+ packet.set_trusted_uid(kSystemUid);
+
+ // When the packet has a process tree, the packet must have a timestamp.
+ packet.mutable_process_tree();
+ packet.set_timestamp(kSomeTime);
+
+ ASSERT_OK(Verify(packet));
+}
+
+TEST_F(VerifyIntegrityUnitTest, InvalidPacketProcessStatsMissingTime) {
+ protos::gen::TracePacket packet;
+
+ packet.set_trusted_uid(kSystemUid);
+
+ // When the packet has process stats, the packet must have a timestamp.
+ packet.mutable_process_stats();
+
+ ASSERT_FALSE(Verify(packet).ok());
+}
+
+TEST_F(VerifyIntegrityUnitTest, ValidPacketProcessStats) {
+ protos::gen::TracePacket packet;
+
+ packet.set_trusted_uid(kSystemUid);
+
+ // When the packet has a process tree, the packet must have a timestamp.
+ packet.mutable_process_stats();
+ packet.set_timestamp(kSomeTime);
+
+ ASSERT_OK(Verify(packet));
+}
+
+} // namespace perfetto::trace_redaction