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 <string_view>
27 #include <vector>
28 
29 #include <android-base/file.h>
30 #include <android-base/parseint.h>
31 #include <android-base/stringprintf.h>
32 #include <android-base/strings.h>
33 #include <packagelistparser/packagelistparser.h>
34 #include <private/android_filesystem_config.h>
35 #include <scoped_minijail.h>
36 #include <selinux/android.h>
37 
38 #include "../cmd_api_impl.h"
39 #include "../cmd_record_impl.h"
40 #include "../cmd_stat_impl.h"
41 
42 using android::base::ParseInt;
43 using android::base::ParseUint;
44 using android::base::Realpath;
45 using android::base::StartsWith;
46 using android::base::StringPrintf;
47 using namespace simpleperf;
48 
49 // simpleperf_app_runner is used to run simpleperf to profile apps with <profileable shell="true">
50 // on user devices. It works as below:
51 //   simpleperf cmds in shell -> simpleperf_app_runner -> /system/bin/simpleperf in app's context
52 //
53 // 1. User types simpleperf cmds in adb shell. If that is to profile an app, simpleperf calls
54 //    /system/bin/simpleperf_app_runner with profiling arguments.
55 // 2. simpleperf_app_runner checks if the app is profileable_from_shell. Then it switches the
56 //    process to the app's user id / group id, switches secontext to the app's domain, and
57 //    executes /system/bin/simpleperf with profiling arguments.
58 // 3. /system/bin/simpleperf records profiling data and writes profiling data to a file descriptor
59 //    opened by simpleperf cmds in shell.
60 
61 struct PackageListCallbackArg {
62   const char* name;
63   pkg_info* info;
64 };
65 
PackageListParseCallback(pkg_info * info,void * userdata)66 static bool PackageListParseCallback(pkg_info* info, void* userdata) {
67   PackageListCallbackArg* arg = static_cast<PackageListCallbackArg*>(userdata);
68   if (strcmp(arg->name, info->name) == 0) {
69     arg->info = info;
70     return false;
71   }
72   packagelist_free(info);
73   return true;
74 }
75 
ReadPackageInfo(const char * pkgname)76 pkg_info* ReadPackageInfo(const char* pkgname) {
77   // Switch to package_info gid to read package info.
78   gid_t old_egid = getegid();
79   if (setegid(AID_PACKAGE_INFO) == -1) {
80     error(1, errno, "setegid failed");
81   }
82   PackageListCallbackArg arg;
83   arg.name = pkgname;
84   arg.info = nullptr;
85   if (!packagelist_parse(PackageListParseCallback, &arg)) {
86     error(1, errno, "packagelist_parse failed");
87   }
88   if (setegid(old_egid) == -1) {
89     error(1, errno, "setegid failed");
90   }
91   return arg.info;
92 }
93 
GetSupplementaryGids(uid_t userAppId)94 std::vector<gid_t> GetSupplementaryGids(uid_t userAppId) {
95   std::vector<gid_t> gids;
96   int size = getgroups(0, &gids[0]);
97   if (size < 0) {
98     error(1, errno, "getgroups failed");
99   }
100   gids.resize(size);
101   size = getgroups(size, &gids[0]);
102   if (size != static_cast<int>(gids.size())) {
103     error(1, errno, "getgroups failed");
104   }
105   // Profile guide compiled oat files (like /data/app/xxx/oat/arm64/base.odex) are not readable
106   // worldwide (DEXOPT_PUBLIC flag isn't set). To support reading them (needed by simpleperf for
107   // profiling), add shared app gid to supplementary groups.
108   gid_t shared_app_gid = userAppId % AID_USER_OFFSET - AID_APP_START + AID_SHARED_GID_START;
109   gids.push_back(shared_app_gid);
110   return gids;
111 }
112 
CheckSimpleperfArguments(std::string_view cmd_name,char ** args)113 static void CheckSimpleperfArguments(std::string_view cmd_name, char** args) {
114   const OptionFormatMap& common_formats = GetCommonOptionFormatMap();
115   const OptionFormatMap* formats = nullptr;
116   if (cmd_name == "api-collect") {
117     formats = &GetApiCollectCmdOptionFormats();
118   } else if (cmd_name == "record") {
119     formats = &GetRecordCmdOptionFormats();
120   } else if (cmd_name == "stat") {
121     formats = &GetStatCmdOptionFormats();
122   } else {
123     error(1, 0, "cmd isn't allowed: %s", cmd_name.data());
124   }
125 
126   for (size_t i = 0; args[i] != nullptr; ++i) {
127     auto it = formats->find(args[i]);
128     if (it == formats->end()) {
129       it = common_formats.find(args[i]);
130       if (it == common_formats.end()) {
131         error(1, 0, "arg isn't allowed: %s", args[i]);
132       }
133     }
134     const OptionFormat& format = it->second;
135     if (format.value_type != OptionValueType::NONE && args[i + 1] == nullptr) {
136       error(1, 0, "invalid arg: %s", args[i]);
137     }
138     switch (format.app_runner_type) {
139       case AppRunnerType::ALLOWED:
140         break;
141       case AppRunnerType::NOT_ALLOWED:
142         error(1, 0, "arg isn't allowed: %s", args[i]);
143         break;
144       case AppRunnerType::CHECK_FD: {
145         int fd;
146         if (!ParseInt(args[i + 1], &fd) || fd < 3 || fcntl(fd, F_GETFD) == -1) {
147           error(1, 0, "invalid fd for arg: %s", args[i]);
148         }
149         break;
150       }
151       case AppRunnerType::CHECK_PATH: {
152         std::string path;
153         if (!Realpath(args[i + 1], &path) || !StartsWith(path, "/data/local/tmp/")) {
154           error(1, 0, "invalid path for arg: %s", args[i]);
155         }
156         break;
157       }
158     }
159     if (format.value_type != OptionValueType::NONE) {
160       ++i;
161     }
162   }
163 }
164 
main(int argc,char * argv[])165 int main(int argc, char* argv[]) {
166   if (argc < 3) {
167     fprintf(
168         stderr,
169         // clang-format off
170 "Usage: simpleperf_app_runner package_name [options] [simpleperf cmd simpleperf_cmd_args]\n"
171 "Options:\n"
172 "--user uid        profile app process run by uid\n"
173 "--show-app-type   show if the app is debuggable or profileable\n"
174         // clang-format on
175     );
176     return 1;
177   }
178   int i = 1;
179   char* pkgname = argv[i++];
180   uint32_t user_id = 0;
181   if (i + 1 < argc && strcmp(argv[i], "--user") == 0) {
182     if (!ParseUint(argv[i + 1], &user_id)) {
183       error(1, 0, "invalid uid");
184     }
185     i += 2;
186   }
187   if (i < argc && strcmp(argv[i], "--show-app-type") == 0) {
188     pkg_info* info = ReadPackageInfo(pkgname);
189     if (info == nullptr) {
190       error(1, 0, "failed to find package %s", pkgname);
191     }
192     if (info->debuggable) {
193       printf("debuggable\n");
194     } else if (info->profileable_from_shell) {
195       printf("profileable\n");
196     } else {
197       printf("non_profileable\n");
198     }
199     return 0;
200   }
201 
202   if (i == argc) {
203     error(1, 0, "no simpleperf command name");
204   }
205   char* simpleperf_cmdname = argv[i];
206   int simpleperf_arg_start = i + 1;
207   CheckSimpleperfArguments(simpleperf_cmdname, argv + simpleperf_arg_start);
208 
209   if (getuid() != AID_SHELL && getuid() != AID_ROOT) {
210     error(1, 0, "program can only run from shell or root");
211   }
212 
213   // Get package info.
214   pkg_info* info = ReadPackageInfo(pkgname);
215   if (info == nullptr) {
216     error(1, 0, "failed to find package %s", pkgname);
217   }
218   if (info->uid < AID_APP_START || info->uid > AID_APP_END) {
219     error(1, 0, "package isn't an application: %s", pkgname);
220   }
221   if (!(info->debuggable || info->profileable_from_shell)) {
222     error(1, 0, "package is neither debuggable nor profileable from shell: %s", pkgname);
223   }
224 
225   uid_t user_app_id = info->uid;
226   std::string data_dir = info->data_dir;
227   if (user_id > 0) {
228     // Make sure user_app_id doesn't overflow.
229     if ((UID_MAX - info->uid) / AID_USER_OFFSET < user_id) {
230       error(1, 0, "user id is too big: %d", user_id);
231     }
232     user_app_id = (AID_USER_OFFSET * user_id) + info->uid;
233     data_dir = StringPrintf("/data/user/%d/%s", user_id, pkgname);
234   }
235 
236   // Switch to the app's user id and group id.
237   uid_t uid = user_app_id;
238   gid_t gid = user_app_id;
239   std::vector<gid_t> supplementary_gids = GetSupplementaryGids(user_app_id);
240   ScopedMinijail j(minijail_new());
241   minijail_change_uid(j.get(), uid);
242   minijail_change_gid(j.get(), gid);
243   minijail_set_supplementary_gids(j.get(), supplementary_gids.size(), &supplementary_gids[0]);
244   minijail_enter(j.get());
245 
246   // Switch to the app's selinux context.
247   if (selinux_android_setcontext(uid, 0, info->seinfo, pkgname) < 0) {
248     error(1, errno, "couldn't set SELinux security context");
249   }
250 
251   // Switch to the app's data directory.
252   if (TEMP_FAILURE_RETRY(chdir(data_dir.c_str())) == -1) {
253     error(1, errno, "couldn't chdir to package's data directory");
254   }
255 
256   // Run /system/bin/simpleperf.
257   std::string simpleperf_in_system_img = "/system/bin/simpleperf";
258   int new_argc = 4 + argc - simpleperf_arg_start;
259   char* new_argv[new_argc + 1];
260 
261   new_argv[0] = &simpleperf_in_system_img[0];
262   new_argv[1] = simpleperf_cmdname;
263   std::string app_option = "--app";
264   new_argv[2] = &app_option[0];
265   new_argv[3] = pkgname;
266   for (int i = 4, j = simpleperf_arg_start; j < argc;) {
267     new_argv[i++] = argv[j++];
268   }
269   new_argv[new_argc] = nullptr;
270   execvp(new_argv[0], new_argv);
271   error(1, errno, "exec failed");
272 }
273