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