• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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