blob: 2bac39903421b11eb2bee8bce130e4845e082f19 [file] [log] [blame] [edit]
// Copyright 2024 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "debugd/src/binary_log_tool.h"
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include <base/containers/span.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_file.h>
#include <base/memory/scoped_refptr.h>
#include <base/logging.h>
#include <brillo/errors/error.h>
#include <chromeos/dbus/fbpreprocessor/dbus-constants.h>
#include <cryptohome/proto_bindings/UserDataAuth.pb.h>
#include <dbus/debugd/dbus-constants.h>
#include <fbpreprocessor/proto_bindings/fbpreprocessor.pb.h>
#include <fbpreprocessor-client/fbpreprocessor/dbus-proxies.h>
#include <user_data_auth-client/user_data_auth/dbus-proxies.h>
#include "debugd/src/sandboxed_process.h"
namespace {
constexpr char kWiFiTarballName[] = "wifi_fw_dumps.tar.zst";
bool ValidateDirectoryNames(const std::set<base::FilePath>& files,
const base::FilePath& daemon_store_path) {
if (files.empty()) {
LOG(ERROR) << "No input files";
return false;
}
// The input files, i.e. the dumps processed by fbpreprocessord are stored
// in "/run/daemon-store/fbpreprocessord/<user_hash>/processed_dumps/".
base::FilePath processed_dir_path =
daemon_store_path.Append(fbpreprocessor::kProcessedDirectory);
for (auto file : files) {
if (file.DirName() != processed_dir_path) {
LOG(ERROR) << "Invalid input file path: " << file;
return false;
}
}
return true;
}
bool CompressFiles(const base::FilePath& outfile,
const std::set<base::FilePath>& files,
const base::FilePath& base_dir,
bool use_minijail) {
if (files.empty()) {
LOG(ERROR) << "No input files";
return false;
}
debugd::SandboxedProcess p;
p.InheritUsergroups();
p.AllowAccessRootMountNamespace();
if (use_minijail) {
std::string args(base_dir.value());
args.append(",");
args.append(base_dir.value());
args.append(",none,MS_BIND|MS_REC");
std::vector<std::string> minijail_args;
minijail_args.push_back("-k");
minijail_args.push_back(args);
p.Init(minijail_args);
}
p.AddArg("/bin/tar");
p.AddArg("-I zstd");
p.AddArg("-cf");
p.AddArg(outfile.value());
p.AddArg("-C");
p.AddArg(files.cbegin()->DirName().value());
for (auto file : files) {
p.AddArg(file.BaseName().value());
}
int ret = p.Run();
if (ret) {
PLOG(ERROR) << "Failed to run tar";
}
return ret == EXIT_SUCCESS;
}
// Compress the files in |files| to tarball with ZSTD compression and copy the
// contents of the tarball to the |out_fd|. The tarball is deleted once it is
// copied to the FD.
bool CompressAndSendFilesToFD(const base::FilePath& tarball_name,
const std::set<base::FilePath>& files,
const base::FilePath& daemon_store_path,
bool use_minijail,
const int out_fd) {
if (files.empty()) {
LOG(ERROR) << "No input files";
return false;
}
// The processed dumps are stored in the following directory:
// "/run/daemon-store/fbpreprocessord/<user_hash>/processed_dumps/",
// whereas, the intermediate compressed dumps should be stored under:
// "/run/daemon-store/fbpreprocessord/<user_hash>/scratch/" directory.
base::FilePath output_dir =
daemon_store_path.Append(fbpreprocessor::kScratchDirectory);
if (!base::DirectoryExists(output_dir)) {
LOG(ERROR) << "Output dir " << output_dir << " doesn't exist";
return false;
}
base::FilePath tarball_path = output_dir.Append(tarball_name);
if (!CompressFiles(tarball_path, files, daemon_store_path, use_minijail)) {
LOG(ERROR) << "Failed to compress binary logs";
return false;
}
VLOG(1) << "Attaching debug dumps at " << tarball_path;
base::File tarfile(tarball_path, base::File::FLAG_OPEN |
base::File::FLAG_READ |
base::File::FLAG_DELETE_ON_CLOSE);
if (!tarfile.IsValid()) {
LOG(ERROR) << "Error opening file " << tarball_path << ": "
<< base::File::ErrorToString(tarfile.error_details());
return false;
}
// The out_fd is closed by the caller function. So, use dup() to create a
// base::File object so that it can be closed independently after the copy
// operation without interfering with the out_fd.
int dup_fd = dup(out_fd);
if (dup_fd == -1) {
PLOG(ERROR) << "Failed to dup output fd";
return false;
}
base::File outfile(dup_fd);
if (!base::CopyFileContents(tarfile, outfile)) {
PLOG(ERROR) << "Failed to send binary logs";
return false;
}
return true;
}
std::optional<base::FilePath> GetDaemonStorePath(
org::chromium::CryptohomeMiscInterfaceProxyInterface* proxy,
base::FilePath& daemon_store_base_dir,
const std::string& username) {
user_data_auth::GetSanitizedUsernameRequest request;
user_data_auth::GetSanitizedUsernameReply reply;
request.set_username(username);
brillo::ErrorPtr error;
if (!proxy->GetSanitizedUsername(request, &reply, &error) || error.get()) {
LOG(ERROR) << "Failed to retrieve sanitized username: "
<< error->GetMessage();
return std::nullopt;
}
if (reply.sanitized_username().empty()) {
LOG(ERROR) << "Retrieved emtpy sanitized username.";
return std::nullopt;
}
return daemon_store_base_dir.Append(reply.sanitized_username());
}
} // namespace
namespace debugd {
BinaryLogTool::BinaryLogTool(scoped_refptr<dbus::Bus> bus)
: fbpreprocessor_proxy_(
std::make_unique<org::chromium::FbPreprocessorProxy>(bus)),
cryptohome_proxy_(
std::make_unique<org::chromium::CryptohomeMiscInterfaceProxy>(bus)),
daemon_store_base_dir_(
base::FilePath(fbpreprocessor::kDaemonStorageRoot)) {}
void BinaryLogTool::DisableMinijailForTesting() {
use_minijail_ = false;
}
void BinaryLogTool::SetFbPreprocessorProxyForTesting(
std::unique_ptr<org::chromium::FbPreprocessorProxyInterface> proxy) {
fbpreprocessor_proxy_ = std::move(proxy);
}
void BinaryLogTool::SetCryptohomeProxyForTesting(
std::unique_ptr<org::chromium::CryptohomeMiscInterfaceProxyInterface>
proxy) {
cryptohome_proxy_ = std::move(proxy);
}
void BinaryLogTool::GetBinaryLogs(
const std::string& username,
const std::map<FeedbackBinaryLogType, base::ScopedFD>& outfds) {
if (!outfds.contains(FeedbackBinaryLogType::WIFI_FIRMWARE_DUMP)) {
LOG(ERROR) << "Unsupported binary log type";
return;
}
fbpreprocessor::DebugDumps dumps;
brillo::ErrorPtr error;
if (!fbpreprocessor_proxy_->GetDebugDumps(&dumps, &error) || error.get()) {
LOG(ERROR) << "Failed to retrieve debug dumps: " << error->GetMessage();
return;
}
std::set<base::FilePath> files;
for (auto dump : dumps.dump()) {
if (dump.has_wifi_dump()) {
base::FilePath file(dump.wifi_dump().dmpfile());
if (base::PathExists(file)) {
files.insert(file);
}
}
}
if (files.empty()) {
return;
}
// GetDaemonStorePath() returns "/run/daemon-store/<daemon_name>/<user_hash>"
// path, which is the preferred place to store per-user data.
std::optional<base::FilePath> daemon_store_path = GetDaemonStorePath(
cryptohome_proxy_.get(), daemon_store_base_dir_, username);
if (!daemon_store_path) {
LOG(ERROR) << "Failed to get the daemon store path";
return;
}
if (!ValidateDirectoryNames(files, daemon_store_path.value())) {
LOG(ERROR) << "Failed to validate binary log files";
return;
}
int out_fd = outfds.at(FeedbackBinaryLogType::WIFI_FIRMWARE_DUMP).get();
base::FilePath tarball_name(kWiFiTarballName);
if (!CompressAndSendFilesToFD(tarball_name, files, daemon_store_path.value(),
use_minijail_, out_fd)) {
LOG(ERROR) << "Failed to send binary logs";
return;
}
}
} // namespace debugd