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