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