blob: bb9a34b293f16f11caa823eb4482e0063b399133 [file] [log] [blame] [edit]
// Copyright 2016 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 "debugd/src/oom_adj_tool.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <memory>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_file.h>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <brillo/userdb_utils.h>
#include "debugd/src/process_with_output.h"
namespace debugd {
namespace {
constexpr char kProcfsDirFormat[] = "/proc/%d";
constexpr char kOomScoreAdjFileFormat[] = "/proc/%d/oom_score_adj";
constexpr char kUidMapFileFormat[] = "/proc/%d/uid_map";
// Though uid might be an unsigned int, some system call like setreuid() still
// accepts uid_t as -1 as a special invalid value. I followed the tradition in
// this file.
constexpr uid_t kInvalidUid = static_cast<uid_t>(-1);
uid_t GetUidForUsername(const std::string& user_name) {
uid_t uid;
return brillo::userdb::GetUserInfo(user_name, &uid, nullptr) ? uid
: kInvalidUid;
}
bool IsValidUid(const uid_t uid) {
return uid != kInvalidUid;
}
// Print and collect errors.
void PrintAndAppendError(std::string* errors, const std::string& new_error) {
LOG(WARNING) << new_error;
*errors += new_error + "\n";
}
// A helper class to get process attributes like process owner, etc.
class ProcessHandler {
public:
explicit ProcessHandler(const pid_t pid);
~ProcessHandler() = default;
// Get UID of the process |pid_|.
uid_t GetProcessOwnerUid(std::string* errors);
// Get UID of root inside the user namespace |pid_| is in.
uid_t GetUserNamespaceRootUid(std::string* errors);
private:
const pid_t pid_;
};
ProcessHandler::ProcessHandler(const pid_t pid) : pid_(pid) {}
uid_t ProcessHandler::GetProcessOwnerUid(std::string* errors) {
std::string procfs_entry = base::StringPrintf(kProcfsDirFormat, pid_);
base::ScopedFD procfs_fd(open(procfs_entry.c_str(), O_RDONLY | O_DIRECTORY));
if (!procfs_fd.is_valid()) {
PrintAndAppendError(
errors,
base::StringPrintf("Failed to open procfs entry of process %d", pid_));
return kInvalidUid;
}
struct stat statbuf;
if (fstat(procfs_fd.get(), &statbuf) < 0) {
PrintAndAppendError(
errors, base::StringPrintf("Failed to get uid of process %d", pid_));
return kInvalidUid;
}
return statbuf.st_uid;
}
uid_t ProcessHandler::GetUserNamespaceRootUid(std::string* errors) {
base::FilePath uid_map_file(base::StringPrintf(kUidMapFileFormat, pid_));
base::ScopedFILE uid_file(base::OpenFile(uid_map_file, "r"));
if (!uid_file) {
PrintAndAppendError(
errors,
base::StringPrintf("Failed to open uid map file %s for process %d",
uid_map_file.value().c_str(), pid_));
return kInvalidUid;
}
uid_t start, map;
// Each line in the uid_map file specifies a 1-to-1 mapping of a range
// of contiguous user IDs between two user namespaces. The first two numbers
// specify the starting user ID in each of the two user namespaces. The
// third number specifies the length of the mapped range.
// Assume root user in the user namespace of |pid_| is mapped to the calling
// user namespace. If not it returns kInvalidUid.
while (fscanf(uid_file.get(), "%d%d%*d", &start, &map) == 2) {
// Returns mapping of UID 0 = root.
if (start == 0)
return map;
}
return kInvalidUid;
}
// Batch set oom_score_adj for a list of processes. Only Chrome tabs and
// Android apps are valid target.
class OomScoreSetter {
public:
OomScoreSetter()
: chronos_uid_(GetUidForUsername("chronos")),
android_root_uid_(GetUidForUsername("android-root")) {}
~OomScoreSetter() = default;
// Entry point.
std::string Set(const std::map<pid_t, int32_t>& scores);
private:
// Sets oom_score_adj for one process.
void SetOne(const pid_t pid, const int32_t score, std::string* errors);
// Whether it's valid to alter OOM score of the given process |pid_|.
bool IsValidOwner(const pid_t pid, std::string* errors);
const uid_t chronos_uid_;
const uid_t android_root_uid_;
};
std::string OomScoreSetter::Set(const std::map<pid_t, int32_t>& scores) {
VLOG(2) << "UID of chronos: " << chronos_uid_;
VLOG(2) << "UID of android-root: " << android_root_uid_;
std::string errors;
for (const auto& entry : scores) {
const pid_t& pid = entry.first;
const int32_t& score = entry.second;
VLOG(2) << "Setting OOM score " << score << " for process " << pid;
SetOne(pid, score, &errors);
}
return errors;
}
void OomScoreSetter::SetOne(const pid_t pid,
const int32_t score,
std::string* errors) {
if (!IsValidOwner(pid, errors)) {
PrintAndAppendError(
errors,
base::StringPrintf("Invalid pid %d, operation not allowed", pid));
return;
}
std::string score_str = std::to_string(score);
const size_t len = score_str.length();
base::FilePath oom_file(base::StringPrintf(kOomScoreAdjFileFormat, pid));
ssize_t bytes_written = base::WriteFile(oom_file, score_str.c_str(), len);
std::string write_error;
if (bytes_written < 0) {
write_error = strerror(errno);
} else if ((size_t)bytes_written != len) {
write_error = base::StringPrintf("%zd instead of %zu bytes written",
bytes_written, len);
}
if (!write_error.empty()) {
PrintAndAppendError(
errors,
base::StringPrintf("Write %d to %s failed: %s", score,
oom_file.value().c_str(), write_error.c_str()));
}
}
// Returns true if
// 1. The process is owned by "chronos", or
// 2. The process is created in a user namespace where root is "android-root".
bool OomScoreSetter::IsValidOwner(const pid_t pid, std::string* errors) {
ProcessHandler handler(pid);
uid_t process_owner_uid = handler.GetProcessOwnerUid(errors);
VLOG(2) << "Owner of " << pid << ": " << process_owner_uid;
if (IsValidUid(process_owner_uid) && process_owner_uid == chronos_uid_)
return true;
uid_t namespace_root_uid = handler.GetUserNamespaceRootUid(errors);
VLOG(2) << "Root of the user namespace " << pid
<< " is in: " << namespace_root_uid;
if (IsValidUid(namespace_root_uid) && namespace_root_uid == android_root_uid_)
return true;
return false;
}
} // namespace
std::string OomAdjTool::Set(const std::map<pid_t, int32_t>& scores) {
OomScoreSetter setter;
return setter.Set(scores);
}
} // namespace debugd