| // 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 |