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
21 #include <regex>
22 #include <sstream>
23
24 #include <android-base/file.h>
25 #include <android-base/parseint.h>
26 #include <android-base/strings.h>
27
28 #include "common/libs/fs/shared_buf.h"
29 #include "common/libs/fs/shared_fd.h"
30 #include "common/libs/utils/files.h"
31
32 namespace cuttlefish {
33
34 // TODO(kwstephenkim): This logic is used broadly, so consider
35 // to create a new library.
36 template <typename... Args>
ConcatToString(Args &&...args)37 static std::string ConcatToString(Args&&... args) {
38 std::stringstream concatenator;
39 (concatenator << ... << std::forward<Args>(args));
40 return concatenator.str();
41 }
42
PidDirPath(const pid_t pid)43 static std::string PidDirPath(const pid_t pid) {
44 return ConcatToString(kProcDir, "/", pid);
45 }
46
47 /* ReadFile does not work for /proc/<pid>/<some files>
48 * ReadFile requires the file size to be known in advance,
49 * which is not the case here.
50 */
ReadAll(const std::string & file_path)51 static Result<std::string> ReadAll(const std::string& file_path) {
52 SharedFD fd = SharedFD::Open(file_path, O_RDONLY);
53 CF_EXPECT(fd->IsOpen());
54 // should be good size to read all Envs or Args,
55 // whichever bigger
56 const int buf_size = 1024;
57 std::string output;
58 ssize_t nread = 0;
59 do {
60 std::vector<char> buf(buf_size);
61 nread = ReadExact(fd, buf.data(), buf_size);
62 CF_EXPECT(nread >= 0, "ReadExact returns " << nread);
63 output.append(buf.begin(), buf.end());
64 } while (nread > 0);
65 return output;
66 }
67
68 /**
69 * Tokenizes the given string, using '\0' as a delimiter
70 *
71 * android::base::Tokenize works mostly except the delimiter can't be '\0'.
72 * The /proc/<pid>/environ file has the list of environment variables, delimited
73 * by '\0'. Needs a dedicated tokenizer.
74 *
75 */
TokenizeByNullChar(const std::string & input)76 static std::vector<std::string> TokenizeByNullChar(const std::string& input) {
77 if (input.empty()) {
78 return {};
79 }
80 std::vector<std::string> tokens;
81 std::string token;
82 for (int i = 0; i < input.size(); i++) {
83 if (input.at(i) != '\0') {
84 token.append(1, input.at(i));
85 } else {
86 if (token.empty()) {
87 break;
88 }
89 tokens.push_back(token);
90 token.clear();
91 }
92 }
93 if (!token.empty()) {
94 tokens.push_back(token);
95 }
96 return tokens;
97 }
98
CollectPids(const uid_t uid)99 Result<std::vector<pid_t>> CollectPids(const uid_t uid) {
100 CF_EXPECT(DirectoryExists(kProcDir));
101 auto subdirs = CF_EXPECT(DirectoryContents(kProcDir));
102 std::regex pid_dir_pattern("[0-9]+");
103 std::vector<pid_t> pids;
104 for (const auto& subdir : subdirs) {
105 if (!std::regex_match(subdir, pid_dir_pattern)) {
106 continue;
107 }
108 int pid;
109 // Shouldn't failed here. If failed, either regex or
110 // android::base::ParseInt needs serious fixes
111 CF_EXPECT(android::base::ParseInt(subdir, &pid));
112 struct stat dir_stat_buf;
113 if (::stat(PidDirPath(pid).data(), &dir_stat_buf) != 0) {
114 continue;
115 }
116 if (dir_stat_buf.st_uid != uid) {
117 continue;
118 }
119 // as we collect cuttlefish-related stuff, we want exe to be
120 // shared by the same owner
121 struct stat exe_stat_buf;
122 std::string exe_path = PidDirPath(pid) + "/exe";
123 if (::stat(exe_path.data(), &exe_stat_buf) != 0) {
124 continue;
125 }
126 if (exe_stat_buf.st_uid != uid) {
127 continue;
128 }
129 pids.push_back(pid);
130 }
131 return pids;
132 }
133
GetCmdArgs(const pid_t pid)134 Result<std::vector<std::string>> GetCmdArgs(const pid_t pid) {
135 std::string cmdline_file_path = PidDirPath(pid) + "/cmdline";
136 auto owner = CF_EXPECT(OwnerUid(cmdline_file_path));
137 CF_EXPECT(getuid() == owner);
138 std::string contents = CF_EXPECT(ReadAll(cmdline_file_path));
139 return TokenizeByNullChar(contents);
140 }
141
GetExecutablePath(const pid_t pid)142 Result<std::string> GetExecutablePath(const pid_t pid) {
143 std::string exec_target_path;
144 std::string proc_exe_path = ConcatToString("/proc/", pid, "/exe");
145 CF_EXPECT(
146 android::base::Readlink(proc_exe_path, std::addressof(exec_target_path)),
147 proc_exe_path << " Should be a symbolic link but it is not.");
148 std::string suffix(" (deleted)");
149 if (android::base::EndsWith(exec_target_path, suffix)) {
150 return exec_target_path.substr(0, exec_target_path.size() - suffix.size());
151 }
152 return exec_target_path;
153 }
154
CollectPidsByExecName(const std::string & exec_name,const uid_t uid)155 Result<std::vector<pid_t>> CollectPidsByExecName(const std::string& exec_name,
156 const uid_t uid) {
157 CF_EXPECT(cpp_basename(exec_name) == exec_name);
158 auto input_pids = CF_EXPECT(CollectPids(uid));
159 std::vector<pid_t> output_pids;
160 for (const auto pid : input_pids) {
161 auto pid_exec_path = GetExecutablePath(pid);
162 if (!pid_exec_path.ok()) {
163 LOG(ERROR) << pid_exec_path.error().Trace();
164 continue;
165 }
166 if (cpp_basename(*pid_exec_path) == exec_name) {
167 output_pids.push_back(pid);
168 }
169 }
170 return output_pids;
171 }
172
CollectPidsByExecPath(const std::string & exec_path,const uid_t uid)173 Result<std::vector<pid_t>> CollectPidsByExecPath(const std::string& exec_path,
174 const uid_t uid) {
175 auto input_pids = CF_EXPECT(CollectPids(uid));
176 std::vector<pid_t> output_pids;
177 for (const auto pid : input_pids) {
178 auto pid_exec_path = GetExecutablePath(pid);
179 if (!pid_exec_path.ok()) {
180 continue;
181 }
182 if (*pid_exec_path == exec_path) {
183 output_pids.push_back(pid);
184 }
185 }
186 return output_pids;
187 }
188
CollectPidsByArgv0(const std::string & expected_argv0,const uid_t uid)189 Result<std::vector<pid_t>> CollectPidsByArgv0(const std::string& expected_argv0,
190 const uid_t uid) {
191 auto input_pids = CF_EXPECT(CollectPids(uid));
192 std::vector<pid_t> output_pids;
193 for (const auto pid : input_pids) {
194 auto argv_result = GetCmdArgs(pid);
195 if (!argv_result.ok()) {
196 continue;
197 }
198 if (argv_result->empty()) {
199 continue;
200 }
201 if (argv_result->front() == expected_argv0) {
202 output_pids.push_back(pid);
203 }
204 }
205 return output_pids;
206 }
207
OwnerUid(const pid_t pid)208 Result<uid_t> OwnerUid(const pid_t pid) {
209 auto proc_pid_path = PidDirPath(pid);
210 auto uid = CF_EXPECT(OwnerUid(proc_pid_path));
211 return uid;
212 }
213
OwnerUid(const std::string & path)214 Result<uid_t> OwnerUid(const std::string& path) {
215 struct stat buf;
216 CF_EXPECT_EQ(::stat(path.data(), &buf), 0);
217 return buf.st_uid;
218 }
219
GetEnvs(const pid_t pid)220 Result<std::unordered_map<std::string, std::string>> GetEnvs(const pid_t pid) {
221 std::string environ_file_path = PidDirPath(pid) + "/environ";
222 auto owner = CF_EXPECT(OwnerUid(environ_file_path));
223 CF_EXPECT(getuid() == owner, "Owned by another user of uid" << owner);
224 std::string environ = CF_EXPECT(ReadAll(environ_file_path));
225 std::vector<std::string> lines = TokenizeByNullChar(environ);
226 // now, each line looks like: HOME=/home/user
227 std::unordered_map<std::string, std::string> envs;
228 for (const auto& line : lines) {
229 auto pos = line.find_first_of('=');
230 if (pos == std::string::npos) {
231 LOG(ERROR) << "Found an invalid env: " << line << " and ignored.";
232 continue;
233 }
234 std::string key = line.substr(0, pos);
235 std::string value = line.substr(pos + 1);
236 envs[key] = value;
237 }
238 return envs;
239 }
240
ExtractProcInfo(const pid_t pid)241 Result<ProcInfo> ExtractProcInfo(const pid_t pid) {
242 return ProcInfo{.pid_ = pid,
243 .actual_exec_path_ = CF_EXPECT(GetExecutablePath(pid)),
244 .envs_ = CF_EXPECT(GetEnvs(pid)),
245 .args_ = CF_EXPECT(GetCmdArgs(pid))};
246 }
247
248 } // namespace cuttlefish
249