blob: 2dece33529791ab9360c30c05c66b5d1d6314358 [file] [log] [blame]
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fcntl.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <base/command_line.h>
#include <base/files/file.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/posix/eintr_wrapper.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/time/time.h>
#include <brillo/process.h>
#include <brillo/syslog_logging.h>
#include <chromeos/libminijail.h>
namespace iwlwifi {
namespace {
constexpr char kDumpName[] = "iwl-fw-error.dump";
constexpr char kFwParserPath[] = "/opt/intel/fw_parser";
constexpr char kFwParserSeccompFile[] =
"/usr/share/policy/fw_parser-seccomp.policy";
constexpr char kUser[] = "iwlwifi-dump";
const char* const kAllowedDumpFiles[] = {
"csr.lst",
"fh_regs.lst",
"monitor.lst.sysmon",
"radio_reg.lst",
};
} // namespace
std::vector<base::FilePath> GetBindMounts() {
std::vector<base::FilePath> mounts;
const std::string mount_list(BIND_MOUNTS);
std::vector<std::string> parsed_mount_list = base::SplitString(
mount_list, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
for (const std::string& mount : parsed_mount_list) {
base::FilePath path(mount);
if (path.IsAbsolute() && !path.ReferencesParent())
mounts.push_back(path);
}
return mounts;
}
bool RunFwParser(const base::FilePath& work_dir) {
struct passwd* pw = getpwnam(kUser); // NOLINT(runtime/threadsafe_fn)
CHECK(pw);
if (chmod(work_dir.value().c_str(), 0755) < 0 ||
chown(work_dir.value().c_str(), pw->pw_uid, pw->pw_gid) < 0) {
LOG(ERROR) << "Could not change permissions on work directory "
<< work_dir.value();
return false;
}
base::FilePath dump_file = work_dir.Append(kDumpName);
if (!base::PathExists(dump_file))
return false;
// Create the target directories for bind-mounting an extremely minimal
// rootfs. We want to lock down this binary as much as possible.
const base::FilePath kRoot("/");
const std::vector<base::FilePath> bind_mounts = GetBindMounts();
for (const base::FilePath& mount : bind_mounts) {
base::FilePath work_dir_bind_target(work_dir);
CHECK(kRoot.AppendRelativePath(mount, &work_dir_bind_target));
base::File::Error error;
if (!base::CreateDirectoryAndGetError(work_dir_bind_target, &error)) {
LOG(ERROR) << "Failed to create directory \""
<< work_dir_bind_target.value() << "\": "
<< base::File::ErrorToString(error);
return false;
}
}
struct minijail* j = minijail_new();
minijail_no_new_privs(j);
minijail_change_user(j, kUser);
minijail_change_group(j, kUser);
minijail_use_seccomp_filter(j);
minijail_parse_seccomp_filters(j, kFwParserSeccompFile);
minijail_namespace_ipc(j);
minijail_namespace_uts(j);
minijail_namespace_net(j);
minijail_namespace_pids(j);
for (const base::FilePath& mount : bind_mounts) {
const char* bind_target = mount.value().c_str();
minijail_bind(j, bind_target, bind_target, false);
}
minijail_enter_chroot(j, work_dir.value().c_str());
std::string argv0(kFwParserPath);
std::string argv1(dump_file.BaseName().value());
std::vector<char*> argv{&argv0[0], &argv1[0], nullptr};
minijail_run(j, argv[0], argv.data());
int exit_status = minijail_wait(j);
minijail_destroy(j);
return (exit_status == 0);
}
bool ProcessDump(const base::FilePath& dump_file) {
base::ScopedTempDir temp_dir;
if (!temp_dir.CreateUniqueTempDir()) {
DLOG(ERROR) << "Could not create temporary directory";
return false;
}
base::FilePath dump_file_target = temp_dir.GetPath().Append(kDumpName);
if (!base::Move(dump_file, dump_file_target)) {
DLOG(ERROR) << "Failed to move " << dump_file.value()
<< " into the temp sandbox directory";
return false;
}
if (!RunFwParser(temp_dir.GetPath()))
return false;
dprintf(STDOUT_FILENO, "\n");
// Only print the relevant output files. Other files may contain PII
// that we don't know how to filter in the general case, so we leave
// them out.
base::FileEnumerator fe(
temp_dir.GetPath(), false, base::FileEnumerator::FILES);
for (base::FilePath current = fe.Next();
!current.empty();
current = fe.Next()) {
bool allowed = false;
for (const char* allowed_file : kAllowedDumpFiles) {
if (base::EndsWith(current.value(),
allowed_file,
base::CompareCase::SENSITIVE)) {
allowed = true;
break;
}
}
if (!allowed)
continue;
dprintf(STDOUT_FILENO, "{{{ %s }}}\n", current.value().c_str());
base::ScopedFD file(open(current.value().c_str(),
O_RDONLY | O_EXCL | O_NONBLOCK));
if (!file.is_valid()) {
DPLOG(INFO) << "Couldn't open file " << current.value();
return false;
}
struct stat stat_buf;
if (fstat(file.get(), &stat_buf) < 0) {
DPLOG(INFO) << "Couldn't stat file " << current.value();
return false;
}
if (!S_ISREG(stat_buf.st_mode)) {
DLOG(INFO) << current.value() << " is not a regular file";
return false;
}
if (sendfile(STDOUT_FILENO, file.get(), nullptr, stat_buf.st_size) < 0) {
DPLOG(ERROR) << "Failed to write " << current.value() << " out";
return false;
}
dprintf(STDOUT_FILENO, "\n");
}
return true;
}
} // namespace iwlwifi
int main(int argc, char** argv) {
brillo::InitLog(brillo::kLogToStderrIfTty | brillo::kLogToSyslog);
base::CommandLine command_line(argc, argv);
if (command_line.GetArgs().size() != 1) {
printf("Usage: %s dump_file\n"
" Parse Intel WiFi debug log dumps for feedback reporting.\n",
command_line.GetProgram().value().c_str());
return EXIT_FAILURE;
}
base::FilePath dump_file(command_line.GetArgs()[0]);
LOG(INFO) << "Processing dump file " << dump_file.value();
return iwlwifi::ProcessDump(dump_file) ? EXIT_SUCCESS : EXIT_FAILURE;
}