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