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(cpp_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