1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "common/libs/utils/proc_file_utils.h" 18 19 #include <sys/stat.h> 20 #include <unistd.h> 21 22 #include <regex> 23 #include <sstream> 24 #include <string> 25 #include <string_view> 26 #include <unordered_map> 27 #include <vector> 28 29 #include <android-base/file.h> 30 #include <android-base/parseint.h> 31 #include <android-base/strings.h> 32 #include <fmt/core.h> 33 34 #include "common/libs/fs/shared_buf.h" 35 #include "common/libs/fs/shared_fd.h" 36 #include "common/libs/utils/files.h" 37 38 namespace cuttlefish { 39 40 // sometimes, files under /proc/<pid> owned by a different user 41 // e.g. /proc/<pid>/exe FileOwnerUid(const std::string & file_path)42 static Result<uid_t> FileOwnerUid(const std::string& file_path) { 43 struct stat buf; 44 CF_EXPECT_EQ(::stat(file_path.data(), &buf), 0); 45 return buf.st_uid; 46 } 47 48 struct ProcStatusUids { 49 uid_t real_; 50 uid_t effective_; 51 uid_t saved_set_; 52 uid_t filesystem_; 53 }; 54 55 // /proc/<pid>/status has Uid: <uid> <uid> <uid> <uid> line 56 // It normally is separated by a tab or more but that's not guaranteed forever OwnerUids(const pid_t pid)57 static Result<ProcStatusUids> OwnerUids(const pid_t pid) { 58 // parse from /proc/<pid>/status 59 std::regex uid_pattern(R"(Uid:\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+))"); 60 std::string status_path = fmt::format("/proc/{}/status", pid); 61 std::string status_content; 62 CF_EXPECT(android::base::ReadFileToString(status_path, &status_content)); 63 std::vector<uid_t> uids; 64 for (const std::string& line : 65 android::base::Tokenize(status_content, "\n")) { 66 std::smatch matches; 67 if (!std::regex_match(line, matches, uid_pattern)) { 68 continue; 69 } 70 // the line, then 4 uids 71 CF_EXPECT_EQ(matches.size(), 5, 72 fmt::format("Error in the Uid line: \"{}\"", line)); 73 uids.reserve(4); 74 for (int i = 1; i < 5; i++) { 75 unsigned uid = 0; 76 CF_EXPECT(android::base::ParseUint(matches[i], &uid)); 77 uids.push_back(uid); 78 } 79 break; 80 } 81 CF_EXPECT(!uids.empty(), "The \"Uid:\" line was not found"); 82 return ProcStatusUids{ 83 .real_ = uids.at(0), 84 .effective_ = uids.at(1), 85 .saved_set_ = uids.at(2), 86 .filesystem_ = uids.at(3), 87 }; 88 } 89 PidDirPath(const pid_t pid)90 static std::string PidDirPath(const pid_t pid) { 91 return fmt::format("{}/{}", kProcDir, pid); 92 } 93 94 /* ReadFile does not work for /proc/<pid>/<some files> 95 * ReadFile requires the file size to be known in advance, 96 * which is not the case here. 97 */ ReadAll(const std::string & file_path)98 static Result<std::string> ReadAll(const std::string& file_path) { 99 SharedFD fd = SharedFD::Open(file_path, O_RDONLY); 100 CF_EXPECT(fd->IsOpen()); 101 // should be good size to read all Envs or Args, 102 // whichever bigger 103 const int buf_size = 1024; 104 std::string output; 105 ssize_t nread = 0; 106 do { 107 std::vector<char> buf(buf_size); 108 nread = ReadExact(fd, buf.data(), buf_size); 109 CF_EXPECT(nread >= 0, "ReadExact returns " << nread); 110 output.append(buf.begin(), buf.end()); 111 } while (nread > 0); 112 return output; 113 } 114 115 /** 116 * Tokenizes the given string, using '\0' as a delimiter 117 * 118 * android::base::Tokenize works mostly except the delimiter can't be '\0'. 119 * The /proc/<pid>/environ file has the list of environment variables, delimited 120 * by '\0'. Needs a dedicated tokenizer. 121 * 122 */ TokenizeByNullChar(const std::string & input)123 static std::vector<std::string> TokenizeByNullChar(const std::string& input) { 124 if (input.empty()) { 125 return {}; 126 } 127 std::vector<std::string> tokens; 128 std::string token; 129 for (int i = 0; i < input.size(); i++) { 130 if (input.at(i) != '\0') { 131 token.append(1, input.at(i)); 132 } else { 133 if (token.empty()) { 134 break; 135 } 136 tokens.push_back(token); 137 token.clear(); 138 } 139 } 140 if (!token.empty()) { 141 tokens.push_back(token); 142 } 143 return tokens; 144 } 145 CollectPids(const uid_t uid)146 Result<std::vector<pid_t>> CollectPids(const uid_t uid) { 147 CF_EXPECT(DirectoryExists(kProcDir)); 148 auto subdirs = CF_EXPECT(DirectoryContents(kProcDir)); 149 std::regex pid_dir_pattern("[0-9]+"); 150 std::vector<pid_t> pids; 151 for (const auto& subdir : subdirs) { 152 if (!std::regex_match(subdir, pid_dir_pattern)) { 153 continue; 154 } 155 int pid; 156 // Shouldn't failed here. If failed, either regex or 157 // android::base::ParseInt needs serious fixes 158 CF_EXPECT(android::base::ParseInt(subdir, &pid)); 159 auto owner_uid_result = OwnerUids(pid); 160 if (owner_uid_result.ok() && owner_uid_result->real_ == uid) { 161 pids.push_back(pid); 162 } 163 } 164 return pids; 165 } 166 GetCmdArgs(const pid_t pid)167 Result<std::vector<std::string>> GetCmdArgs(const pid_t pid) { 168 std::string cmdline_file_path = PidDirPath(pid) + "/cmdline"; 169 auto owner = CF_EXPECT(FileOwnerUid(cmdline_file_path)); 170 CF_EXPECT(getuid() == owner); 171 std::string contents = CF_EXPECT(ReadAll(cmdline_file_path)); 172 return TokenizeByNullChar(contents); 173 } 174 GetExecutablePath(const pid_t pid)175 Result<std::string> GetExecutablePath(const pid_t pid) { 176 std::string exec_target_path; 177 std::string proc_exe_path = fmt::format("/proc/{}/exe", pid); 178 CF_EXPECT( 179 android::base::Readlink(proc_exe_path, std::addressof(exec_target_path)), 180 proc_exe_path << " Should be a symbolic link but it is not."); 181 std::string suffix(" (deleted)"); 182 if (android::base::EndsWith(exec_target_path, suffix)) { 183 return exec_target_path.substr(0, exec_target_path.size() - suffix.size()); 184 } 185 return exec_target_path; 186 } 187 CheckExecNameFromStatus(const std::string & exec_name,const pid_t pid)188 static Result<void> CheckExecNameFromStatus(const std::string& exec_name, 189 const pid_t pid) { 190 std::string status_path = fmt::format("/proc/{}/status", pid); 191 std::string status_content; 192 CF_EXPECT(android::base::ReadFileToString(status_path, &status_content)); 193 bool found = false; 194 for (const std::string& line : 195 android::base::Tokenize(status_content, "\n")) { 196 std::string_view line_view(line); 197 if (!android::base::ConsumePrefix(&line_view, "Name:")) { 198 continue; 199 } 200 auto trimmed_line = android::base::Trim(line_view); 201 if (trimmed_line == exec_name) { 202 found = true; 203 break; 204 } 205 } 206 CF_EXPECTF(found == true, 207 "\"Name: [name]\" line is not found in the status file: \"{}\"", 208 status_path); 209 return {}; 210 } 211 CollectPidsByExecName(const std::string & exec_name,const uid_t uid)212 Result<std::vector<pid_t>> CollectPidsByExecName(const std::string& exec_name, 213 const uid_t uid) { 214 CF_EXPECT_EQ(android::base::Basename(exec_name), exec_name); 215 auto input_pids = CF_EXPECT(CollectPids(uid)); 216 std::vector<pid_t> output_pids; 217 for (const auto pid : input_pids) { 218 auto owner_uids_result = OwnerUids(pid); 219 if (!owner_uids_result.ok() || owner_uids_result->real_ != uid) { 220 LOG(VERBOSE) << "Process #" << pid << " does not belong to " << uid; 221 continue; 222 } 223 if (CheckExecNameFromStatus(exec_name, pid).ok()) { 224 output_pids.push_back(pid); 225 } 226 } 227 return output_pids; 228 } 229 CollectPidsByExecPath(const std::string & exec_path,const uid_t uid)230 Result<std::vector<pid_t>> CollectPidsByExecPath(const std::string& exec_path, 231 const uid_t uid) { 232 auto input_pids = CF_EXPECT(CollectPids(uid)); 233 std::vector<pid_t> output_pids; 234 for (const auto pid : input_pids) { 235 auto pid_exec_path = GetExecutablePath(pid); 236 if (!pid_exec_path.ok()) { 237 continue; 238 } 239 if (*pid_exec_path == exec_path) { 240 output_pids.push_back(pid); 241 } 242 } 243 return output_pids; 244 } 245 CollectPidsByArgv0(const std::string & expected_argv0,const uid_t uid)246 Result<std::vector<pid_t>> CollectPidsByArgv0(const std::string& expected_argv0, 247 const uid_t uid) { 248 auto input_pids = CF_EXPECT(CollectPids(uid)); 249 std::vector<pid_t> output_pids; 250 for (const auto pid : input_pids) { 251 auto argv_result = GetCmdArgs(pid); 252 if (!argv_result.ok()) { 253 continue; 254 } 255 if (argv_result->empty()) { 256 continue; 257 } 258 if (argv_result->front() == expected_argv0) { 259 output_pids.push_back(pid); 260 } 261 } 262 return output_pids; 263 } 264 OwnerUid(const pid_t pid)265 Result<uid_t> OwnerUid(const pid_t pid) { 266 // parse from /proc/<pid>/status 267 auto uids_result = OwnerUids(pid); 268 if (!uids_result.ok()) { 269 LOG(DEBUG) << uids_result.error().Trace(); 270 LOG(DEBUG) << "Falling back to the old OwnerUid logic"; 271 return CF_EXPECT(FileOwnerUid(PidDirPath(pid))); 272 } 273 return uids_result->real_; 274 } 275 GetEnvs(const pid_t pid)276 Result<std::unordered_map<std::string, std::string>> GetEnvs(const pid_t pid) { 277 std::string environ_file_path = PidDirPath(pid) + "/environ"; 278 auto owner = CF_EXPECT(FileOwnerUid(environ_file_path)); 279 CF_EXPECT(getuid() == owner, "Owned by another user of uid" << owner); 280 std::string environ = CF_EXPECT(ReadAll(environ_file_path)); 281 std::vector<std::string> lines = TokenizeByNullChar(environ); 282 // now, each line looks like: HOME=/home/user 283 std::unordered_map<std::string, std::string> envs; 284 for (const auto& line : lines) { 285 auto pos = line.find_first_of('='); 286 if (pos == std::string::npos) { 287 LOG(ERROR) << "Found an invalid env: " << line << " and ignored."; 288 continue; 289 } 290 std::string key = line.substr(0, pos); 291 std::string value = line.substr(pos + 1); 292 envs[key] = value; 293 } 294 return envs; 295 } 296 ExtractProcInfo(const pid_t pid)297 Result<ProcInfo> ExtractProcInfo(const pid_t pid) { 298 auto owners = CF_EXPECT(OwnerUids(pid)); 299 return ProcInfo{.pid_ = pid, 300 .real_owner_ = owners.real_, 301 .effective_owner_ = owners.effective_, 302 .actual_exec_path_ = CF_EXPECT(GetExecutablePath(pid)), 303 .envs_ = CF_EXPECT(GetEnvs(pid)), 304 .args_ = CF_EXPECT(GetCmdArgs(pid))}; 305 } 306 Ppid(const pid_t pid)307 Result<pid_t> Ppid(const pid_t pid) { 308 // parse from /proc/<pid>/status 309 std::regex uid_pattern(R"(PPid:\s*([0-9]+))"); 310 std::string status_path = fmt::format("/proc/{}/status", pid); 311 std::string status_content; 312 CF_EXPECT(android::base::ReadFileToString(status_path, &status_content)); 313 for (const auto& line : android::base::Tokenize(status_content, "\n")) { 314 std::smatch matches; 315 if (!std::regex_match(line, matches, uid_pattern)) { 316 continue; 317 } 318 unsigned ppid; 319 CF_EXPECT(android::base::ParseUint(matches[1], &ppid)); 320 return static_cast<pid_t>(ppid); 321 } 322 return CF_ERR("Status file does not have PPid: line in the right format"); 323 } 324 325 } // namespace cuttlefish 326