blob: 2342967e924c453e332ce811f9903aa0e136a804 [file] [log] [blame]
/*
* 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 "src/trace_processor/util/status_macros.h"
#include "protos/perfetto/common/trace_stats.pbzero.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 {
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() > Context::kMaxTrustedUid) {
return base::ErrStatus("VerifyIntegrity: untrusted uid found (uid = %d).",
packet.trusted_uid());
}
if (packet.has_ftrace_events()) {
RETURN_IF_ERROR(OnFtraceEvents(packet.ftrace_events()));
}
// 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).");
}
if (packet.has_trace_stats()) {
RETURN_IF_ERROR(OnTraceStats(packet.trace_stats()));
}
return base::OkStatus();
}
base::Status VerifyIntegrity::OnFtraceEvents(
const protozero::ConstBytes bytes) const {
protos::pbzero::FtraceEventBundle::Decoder events(bytes);
// Any ftrace lost events should cause the trace to be dropped:
// protos/perfetto/trace/ftrace/ftrace_event_bundle.proto
if (events.has_lost_events() && events.has_lost_events()) {
return base::ErrStatus(
"VerifyIntegrity: detected FtraceEventBundle error.");
}
// 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 (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 (!events.has_cpu()) {
return base::ErrStatus(
"VerifyIntegrity: missing field (FtraceEventBundle::kCpu).");
}
// Any ftrace errors should cause the trace to be dropped:
// protos/perfetto/trace/ftrace/ftrace_event_bundle.proto
if (events.has_error()) {
return base::ErrStatus("VerifyIntegrity: detected FtraceEvent errors.");
}
for (auto it = events.event(); it; ++it) {
RETURN_IF_ERROR(OnFtraceEvent(*it));
}
return base::OkStatus();
}
base::Status VerifyIntegrity::OnFtraceEvent(
const protozero::ConstBytes bytes) const {
protos::pbzero::FtraceEvent::Decoder event(bytes);
if (!event.has_timestamp()) {
return base::ErrStatus(
"VerifyIntegrity: missing field (FtraceEvent::kTimestamp).");
}
if (!event.has_pid()) {
return base::ErrStatus(
"VerifyIntegrity: missing field (FtraceEvent::kPid).");
}
return base::OkStatus();
}
base::Status VerifyIntegrity::OnTraceStats(
const protozero::ConstBytes bytes) const {
protos::pbzero::TraceStats::Decoder trace_stats(bytes);
if (trace_stats.has_flushes_failed() && trace_stats.flushes_failed()) {
return base::ErrStatus("VerifyIntegrity: detected TraceStats flush fails.");
}
if (trace_stats.has_final_flush_outcome() &&
trace_stats.final_flush_outcome() ==
protos::pbzero::TraceStats::FINAL_FLUSH_FAILED) {
return base::ErrStatus(
"VerifyIntegrity: TraceStats final_flush_outcome is "
"FINAL_FLUSH_FAILED.");
}
for (auto it = trace_stats.buffer_stats(); it; ++it) {
RETURN_IF_ERROR(OnBufferStats(*it));
}
return base::OkStatus();
}
base::Status VerifyIntegrity::OnBufferStats(
const protozero::ConstBytes bytes) const {
protos::pbzero::TraceStats::BufferStats::Decoder stats(bytes);
if (stats.has_patches_failed() && stats.patches_failed()) {
return base::ErrStatus(
"VerifyIntegrity: detected BufferStats patch fails.");
}
if (stats.has_abi_violations() && stats.abi_violations()) {
return base::ErrStatus(
"VerifyIntegrity: detected BufferStats abi violations.");
}
auto has_loss = stats.has_trace_writer_packet_loss();
auto value = stats.trace_writer_packet_loss();
if (has_loss && value) {
return base::ErrStatus(
"VerifyIntegrity: detected BufferStats writer packet loss.");
}
return base::OkStatus();
}
} // namespace perfetto::trace_redaction