• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 <errno.h>
18 #include <error.h>
19 #include <fcntl.h>
20 #include <stdio.h>
21 #include <sys/types.h>
22 #include <unistd.h>
23 
24 #include <set>
25 #include <string>
26 #include <vector>
27 
28 #include <android-base/file.h>
29 #include <android-base/parseint.h>
30 #include <android-base/strings.h>
31 #include <packagelistparser/packagelistparser.h>
32 #include <private/android_filesystem_config.h>
33 #include <scoped_minijail.h>
34 #include <selinux/android.h>
35 
36 // simpleperf_app_runner is used to run simpleperf to profile apps with <profileable shell="true">
37 // on user devices. It works as below:
38 //   simpleperf cmds in shell -> simpleperf_app_runner -> /system/bin/simpleperf in app's context
39 //
40 // 1. User types simpleperf cmds in adb shell. If that is to profile an app, simpleperf calls
41 //    /system/bin/simpleperf_app_runner with profiling arguments.
42 // 2. simpleperf_app_runner checks if the app is profileable_from_shell. Then it switches the
43 //    process to the app's user id / group id, switches secontext to the app's domain, and
44 //    executes /system/bin/simpleperf with profiling arguments.
45 // 3. /system/bin/simpleperf records profiling data and writes profiling data to a file descriptor
46 //    opened by simpleperf cmds in shell.
47 
48 struct PackageListCallbackArg {
49   const char* name;
50   pkg_info* info;
51 };
52 
PackageListParseCallback(pkg_info * info,void * userdata)53 static bool PackageListParseCallback(pkg_info* info, void* userdata) {
54   PackageListCallbackArg* arg = static_cast<PackageListCallbackArg*>(userdata);
55   if (strcmp(arg->name, info->name) == 0) {
56     arg->info = info;
57     return false;
58   }
59   packagelist_free(info);
60   return true;
61 }
62 
ReadPackageInfo(const char * pkgname)63 pkg_info* ReadPackageInfo(const char* pkgname) {
64   // Switch to package_info gid to read package info.
65   gid_t old_egid = getegid();
66   if (setegid(AID_PACKAGE_INFO) == -1) {
67     error(1, errno, "setegid failed");
68   }
69   PackageListCallbackArg arg;
70   arg.name = pkgname;
71   arg.info = nullptr;
72   if (!packagelist_parse(PackageListParseCallback, &arg)) {
73     error(1, errno, "packagelist_parse failed");
74   }
75   if (setegid(old_egid) == -1) {
76     error(1, errno, "setegid failed");
77   }
78   return arg.info;
79 }
80 
GetSupplementaryGids(uid_t userAppId)81 std::vector<gid_t> GetSupplementaryGids(uid_t userAppId) {
82   std::vector<gid_t> gids;
83   int size = getgroups(0, &gids[0]);
84   if (size < 0) {
85     error(1, errno, "getgroups failed");
86   }
87   gids.resize(size);
88   size = getgroups(size, &gids[0]);
89   if (size != static_cast<int>(gids.size())) {
90     error(1, errno, "getgroups failed");
91   }
92   // Profile guide compiled oat files (like /data/app/xxx/oat/arm64/base.odex) are not readable
93   // worldwide (DEXOPT_PUBLIC flag isn't set). To support reading them (needed by simpleperf for
94   // profiling), add shared app gid to supplementary groups.
95   gid_t shared_app_gid = userAppId % AID_USER_OFFSET - AID_APP_START + AID_SHARED_GID_START;
96   gids.push_back(shared_app_gid);
97   return gids;
98 }
99 
CheckSimpleperfArguments(const char * cmdname,char ** args)100 static void CheckSimpleperfArguments(const char* cmdname, char** args) {
101   if (strcmp(cmdname, "stat") != 0 && strcmp(cmdname, "record") != 0 &&
102       strcmp(cmdname, "api-collect") != 0) {
103     error(1, 0, "cmd isn't allowed: %s", cmdname);
104   }
105   std::set<std::string> zero_arg_options = {
106       "-b",
107       "--csv",
108       "--exclude-perf",
109       "--exit-with-parent",
110       "-g",
111       "--in-app",
112       "--interval-only-values",
113       "--log-to-android-buffer",
114       "--no-callchain-joiner",
115       "--no-cut-samples",
116       "--no-dump-kernel-symbols",
117       "--no-dump-symbols",
118       "--no-inherit",
119       "--no-unwind",
120       "--per-core",
121       "--per-thread",
122       "--post-unwind=no",
123       "--post-unwind=yes",
124       "--trace-offcpu",
125       "--verbose",
126   };
127   std::set<std::string> one_arg_options = {
128       "--aux-buffer-size",
129       "-c",
130       "--call-graph",
131       "--callchain-joiner-min-matching-nodes",
132       "--clockid",
133       "--cpu",
134       "--cpu-percent",
135       "--duration",
136       "-e",
137       "-f",
138       "--group",
139       "--include-filter",
140       "--interval",
141       "-j",
142       "--log",
143       "-m",
144       "-p",
145       "--size-limit",
146       "-t",
147   };
148   // options with a file descriptor
149   std::set<std::string> fd_options = {
150       "--start_profiling_fd",
151       "--stop-signal-fd",
152       "--out-fd",
153   };
154   // options with path from /data/local/tmp/
155   std::set<std::string> path_options = {
156       "--symfs",
157       "--tracepoint-events",
158   };
159   one_arg_options.insert(fd_options.begin(), fd_options.end());
160   one_arg_options.insert(path_options.begin(), path_options.end());
161   for (int i = 0; args[i] != nullptr; ++i) {
162     if (zero_arg_options.count(args[i])) {
163       continue;
164     } else if (one_arg_options.count(args[i])) {
165       if (args[i + 1] == nullptr) {
166         error(1, 0, "invalid arg: %s", args[i]);
167       }
168       if (fd_options.count(args[i])) {
169         // Check if the file descriptor is valid.
170         int fd;
171         if (!android::base::ParseInt(args[i + 1], &fd) || fd < 3 || fcntl(fd, F_GETFD) == -1) {
172           error(1, 0, "invalid fd for arg: %s", args[i]);
173         }
174       } else if (path_options.count(args[i])) {
175         std::string path;
176         if (!android::base::Realpath(args[i + 1], &path) ||
177             !android::base::StartsWith(path, "/data/local/tmp/")) {
178           error(1, 0, "invalid path for arg: %s", args[i]);
179         }
180       }
181       ++i;
182     } else {
183       error(1, 0, "arg isn't allowed: %s", args[i]);
184     }
185   }
186 }
187 
main(int argc,char * argv[])188 int main(int argc, char* argv[]) {
189   if (argc < 2) {
190     error(1, 0, "usage: simpleperf_app_runner package_name simpleperf_cmd simpleperf_cmd_args...");
191   }
192   if (argc < 3) {
193     error(1, 0, "no simpleperf command name");
194   }
195   char* pkgname = argv[1];
196   char* simpleperf_cmdname = argv[2];
197   int simpleperf_arg_start = 3;
198   CheckSimpleperfArguments(simpleperf_cmdname, argv + simpleperf_arg_start);
199 
200   if (getuid() != AID_SHELL && getuid() != AID_ROOT) {
201     error(1, 0, "program can only run from shell or root");
202   }
203 
204   pkg_info* info = ReadPackageInfo(pkgname);
205   if (info == nullptr) {
206     error(1, 0, "failed to find package %s", pkgname);
207   }
208   if (info->uid < AID_APP_START || info->uid > AID_APP_END) {
209     error(1, 0, "package isn't an application: %s", pkgname);
210   }
211   if (!info->profileable_from_shell) {
212     error(1, 0, "package isn't profileable from shell: %s", pkgname);
213   }
214 
215   // Switch to the app's user id and group id.
216   uid_t uid = info->uid;
217   gid_t gid = info->uid;
218   std::vector<gid_t> supplementary_gids = GetSupplementaryGids(info->uid);
219   ScopedMinijail j(minijail_new());
220   minijail_change_uid(j.get(), uid);
221   minijail_change_gid(j.get(), gid);
222   minijail_set_supplementary_gids(j.get(), supplementary_gids.size(), &supplementary_gids[0]);
223   minijail_enter(j.get());
224 
225   // Switch to the app's selinux context.
226   if (selinux_android_setcontext(uid, 0, info->seinfo, pkgname) < 0) {
227     error(1, errno, "couldn't set SELinux security context");
228   }
229 
230   // Switch to the app's data directory.
231   if (TEMP_FAILURE_RETRY(chdir(info->data_dir)) == -1) {
232     error(1, errno, "couldn't chdir to package's data directory");
233   }
234 
235   // Run /system/bin/simpleperf.
236   std::string simpleperf_in_system_img = "/system/bin/simpleperf";
237   int new_argc = 4 + argc - simpleperf_arg_start;
238   char* new_argv[new_argc + 1];
239 
240   new_argv[0] = &simpleperf_in_system_img[0];
241   new_argv[1] = simpleperf_cmdname;
242   std::string app_option = "--app";
243   new_argv[2] = &app_option[0];
244   new_argv[3] = pkgname;
245   for (int i = 4, j = simpleperf_arg_start; j < argc;) {
246     new_argv[i++] = argv[j++];
247   }
248   new_argv[new_argc] = nullptr;
249   execvp(new_argv[0], new_argv);
250   error(1, errno, "exec failed");
251 }
252