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 "./command-line.h"
18
19 #include <getopt.h>
20
21 #include <iostream>
22 #include <string>
23
24 #include "./context.h"
25 #include "./string-utils.h"
26
27 namespace shell_as {
28
29 namespace {
30 const std::string kUsage =
31 R"(Usage: shell-as [options] [<program> <arguments>...]
32
33 shell-as executes a program in a specified Android security context. The default
34 program that is executed if none is specified is `/bin/system/sh`.
35
36 The following options can be used to define the target security context.
37
38 --verbose, -v Enables verbose logging.
39 --uid <uid>, -u <uid> The target real and effective user ID.
40 --gid <gid>, -g <gid> The target real and effective group ID.
41 --groups <gid1,2,..>, -G <1,2,..> A comma separated list of supplementary group
42 IDs.
43 --nogroups Specifies that all supplementary groups should
44 be cleared.
45 --selinux <context>, -s <context> The target SELinux context.
46 --seccomp <filter>, -f <filter> The target seccomp filter. Valid values of
47 filter are 'none', 'uid-inferred', 'app',
48 'app-zygote', and 'system'.
49 --caps <capabilities> A libcap textual expression that describes
50 the desired capability sets. The only
51 capability set that matters is the permitted
52 set, the other sets are ignored.
53
54 Examples:
55
56 "=" - Clear all capabilities
57 "=p" - Raise all capabilities
58 "23,CAP_SYS_ADMIN+p" - Raise CAP_SYS_ADMIN
59 and capability 23.
60
61 For a full description of the possible values
62 see `man 3 cap_from_text` (the libcap-dev
63 package provides this man page).
64 --pid <pid>, -p <pid> Infer the target security context from a
65 running process with the given process ID.
66 This option implies --seccomp uid_inferred.
67 This option infers the capability from the
68 target process's permitted capability set.
69 --profile <profile>, -P <profile> Infer the target security context from a
70 predefined security profile. Using this
71 option will install and execute a test app on
72 the device. Currently, the only valid profile
73 is 'untrusted-app' which corresponds to an
74 untrusted app which has been granted every
75 non-system permission.
76
77 Options are evaluated in the order that they are given. For example, the
78 following will set the target context to that of process 1234 but override the
79 user ID to 0:
80
81 shell-as --pid 1234 --uid 0
82 )";
83
84 const char* kShellExecvArgs[] = {"/system/bin/sh", nullptr};
85
ParseGroups(char * line,std::vector<gid_t> * ids)86 bool ParseGroups(char* line, std::vector<gid_t>* ids) {
87 // Allow a null line as a valid input since this method is used to handle both
88 // --groups and --nogroups.
89 if (line == nullptr) {
90 return true;
91 }
92 return SplitIdsAndSkip(line, ",", /*num_to_skip=*/0, ids);
93 }
94 } // namespace
95
ParseOptions(const int argc,char * const argv[],bool * verbose,SecurityContext * context,char * const * execv_args[])96 bool ParseOptions(const int argc, char* const argv[], bool* verbose,
97 SecurityContext* context, char* const* execv_args[]) {
98 char short_options[] = "+s:hp:u:g:G:f:c:vP:";
99 struct option long_options[] = {
100 {"selinux", true, nullptr, 's'}, {"help", false, nullptr, 'h'},
101 {"uid", true, nullptr, 'u'}, {"gid", true, nullptr, 'g'},
102 {"pid", true, nullptr, 'p'}, {"verbose", false, nullptr, 'v'},
103 {"groups", true, nullptr, 'G'}, {"nogroups", false, nullptr, 'G'},
104 {"seccomp", true, nullptr, 'f'}, {"caps", true, nullptr, 'c'},
105 {"profile", true, nullptr, 'P'},
106 };
107 int option;
108 bool infer_seccomp_filter = false;
109 SecurityContext working_context;
110 std::vector<gid_t> supplementary_group_ids;
111 uint32_t working_id = 0;
112 while ((option = getopt_long(argc, argv, short_options, long_options,
113 nullptr)) != -1) {
114 switch (option) {
115 case 'v':
116 *verbose = true;
117 break;
118 case 'h':
119 std::cerr << kUsage;
120 return false;
121 case 'u':
122 if (!StringToUInt32(optarg, &working_id)) {
123 return false;
124 }
125 working_context.user_id = working_id;
126 break;
127 case 'g':
128 if (!StringToUInt32(optarg, &working_id)) {
129 return false;
130 }
131 working_context.group_id = working_id;
132 break;
133 case 'c':
134 working_context.capabilities = cap_from_text(optarg);
135 if (working_context.capabilities.value() == nullptr) {
136 std::cerr << "Unable to parse capabilities" << std::endl;
137 return false;
138 }
139 break;
140 case 'G':
141 supplementary_group_ids.clear();
142 if (!ParseGroups(optarg, &supplementary_group_ids)) {
143 std::cerr << "Unable to parse supplementary groups" << std::endl;
144 return false;
145 }
146 working_context.supplementary_group_ids = supplementary_group_ids;
147 break;
148 case 's':
149 working_context.selinux_context = optarg;
150 break;
151 case 'f':
152 infer_seccomp_filter = false;
153 if (strcmp(optarg, "uid-inferred") == 0) {
154 infer_seccomp_filter = true;
155 } else if (strcmp(optarg, "app") == 0) {
156 working_context.seccomp_filter = kAppFilter;
157 } else if (strcmp(optarg, "app-zygote") == 0) {
158 working_context.seccomp_filter = kAppZygoteFilter;
159 } else if (strcmp(optarg, "system") == 0) {
160 working_context.seccomp_filter = kSystemFilter;
161 } else if (strcmp(optarg, "none") == 0) {
162 working_context.seccomp_filter.reset();
163 } else {
164 std::cerr << "Invalid value for --seccomp: " << optarg << std::endl;
165 return false;
166 }
167 break;
168 case 'p':
169 if (!SecurityContextFromProcess(atoi(optarg), &working_context)) {
170 return false;
171 }
172 infer_seccomp_filter = true;
173 break;
174 case 'P':
175 if (strcmp(optarg, "untrusted-app") == 0) {
176 if (!SecurityContextFromTestApp(&working_context)) {
177 return false;
178 }
179 } else {
180 std::cerr << "Invalid value for --profile: " << optarg << std::endl;
181 return false;
182 }
183 infer_seccomp_filter = true;
184 break;
185 default:
186 std::cerr << "Unknown option '" << (char)optopt << "'" << std::endl;
187 return false;
188 }
189 }
190
191 if (infer_seccomp_filter) {
192 if (!working_context.user_id.has_value()) {
193 std::cerr << "No user ID; unable to infer appropriate seccomp filter."
194 << std::endl;
195 return false;
196 }
197 working_context.seccomp_filter =
198 SeccompFilterFromUserId(working_context.user_id.value());
199 }
200
201 *context = working_context;
202 if (optind < argc) {
203 *execv_args = argv + optind;
204 } else {
205 *execv_args = (char**)kShellExecvArgs;
206 }
207 return true;
208 }
209
210 } // namespace shell_as
211