• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 package com.android.server.sdksandbox;
18 
19 import android.app.sdksandbox.LoadSdkException;
20 import android.content.Context;
21 import android.content.pm.ApplicationInfo;
22 import android.content.pm.PackageManager.NameNotFoundException;
23 import android.os.Binder;
24 import android.os.Process;
25 import android.os.UserHandle;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 import com.android.modules.utils.BasicShellCommandHandler;
29 import com.android.sdksandbox.ISdkSandboxService;
30 
31 import java.io.PrintWriter;
32 import java.util.concurrent.CountDownLatch;
33 import java.util.concurrent.TimeUnit;
34 
35 class SdkSandboxShellCommand extends BasicShellCommandHandler {
36 
37     private final SdkSandboxManagerService mService;
38     private final Context mContext;
39     private final Injector mInjector;
40 
41     private int mUserId = UserHandle.CURRENT.getIdentifier();
42     private CallingInfo mCallingInfo;
43 
44     static class Injector {
getCallingUid()45         int getCallingUid() {
46             return Binder.getCallingUid();
47         }
48     }
49 
50     @VisibleForTesting
SdkSandboxShellCommand(SdkSandboxManagerService service, Context context, Injector injector)51     SdkSandboxShellCommand(SdkSandboxManagerService service, Context context, Injector injector) {
52         mService = service;
53         mContext = context;
54         mInjector = injector;
55     }
56 
SdkSandboxShellCommand(SdkSandboxManagerService service, Context context)57     SdkSandboxShellCommand(SdkSandboxManagerService service, Context context) {
58         this(service, context, new Injector());
59     }
60 
61     @Override
onCommand(String cmd)62     public int onCommand(String cmd) {
63         int callingUid = mInjector.getCallingUid();
64 
65         if (callingUid != Process.ROOT_UID && callingUid != Process.SHELL_UID) {
66             throw new SecurityException("sdk_sandbox shell command is only callable by ADB");
67         }
68         final long token = Binder.clearCallingIdentity();
69 
70         int result;
71         try {
72             if (cmd == null) {
73                 result = handleDefaultCommands(null);
74             } else {
75                 switch (cmd) {
76                     case "start":
77                         result = runStart();
78                         break;
79                     case "stop":
80                         result = runStop();
81                         break;
82                     case "set-state":
83                         result = runSetState();
84                         break;
85                     default:
86                         result = handleDefaultCommands(cmd);
87                 }
88             }
89         } finally {
90             Binder.restoreCallingIdentity(token);
91         }
92         return result;
93     }
94 
handleSandboxArguments()95     private void handleSandboxArguments() {
96         String opt;
97         while ((opt = getNextOption()) != null) {
98             if (opt.equals("--user")) {
99                 mUserId = parseUserArg(getNextArgRequired());
100             } else {
101                 throw new IllegalArgumentException("Unknown option: " + opt);
102             }
103         }
104 
105         if (mUserId == UserHandle.CURRENT.getIdentifier()) {
106             mUserId = mContext.getUser().getIdentifier();
107         }
108 
109         String callingPackageName = getNextArgRequired();
110         try {
111             ApplicationInfo info = mContext.getPackageManager().getApplicationInfoAsUser(
112                     callingPackageName, /* flags */ 0, UserHandle.of(mUserId));
113 
114             if ((info.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
115                 throw new IllegalArgumentException(
116                         "Package " + callingPackageName + " must be debuggable.");
117             }
118             mCallingInfo = new CallingInfo(info.uid, callingPackageName);
119         } catch (NameNotFoundException e) {
120             throw new IllegalArgumentException(
121                     "No such package " + callingPackageName + " for user " + mUserId);
122         }
123     }
124 
parseUserArg(String arg)125     private int parseUserArg(String arg) {
126         switch (arg) {
127             case "all":
128                 throw new IllegalArgumentException("Cannot run sdk_sandbox command for user 'all'");
129             case "current":
130                 return mContext.getUser().getIdentifier();
131             default:
132                 try {
133                     return Integer.parseInt(arg);
134                 } catch (NumberFormatException e) {
135                     throw new IllegalArgumentException("Bad user number: " + arg);
136                 }
137         }
138     }
139 
140     /** Callback for binding sandbox. Provides blocking interface {@link #isSuccessful()}. */
141     private class LatchSandboxServiceConnectionCallback
142             implements SdkSandboxManagerService.SandboxBindingCallback {
143 
144         private final CountDownLatch mLatch = new CountDownLatch(1);
145         private boolean mSuccess = false;
146         private ISdkSandboxService mService;
147         public static final int SANDBOX_BIND_TIMEOUT_S = 5;
148 
149         @Override
onBindingSuccessful(ISdkSandboxService service, int time)150         public void onBindingSuccessful(ISdkSandboxService service, int time) {
151             mSuccess = true;
152             mService = service;
153             mLatch.countDown();
154         }
155 
156         @Override
onBindingFailed(LoadSdkException e, long time)157         public void onBindingFailed(LoadSdkException e, long time) {
158             mLatch.countDown();
159         }
160 
isSuccessful()161         public boolean isSuccessful() {
162             try {
163                 boolean completed = mLatch.await(SANDBOX_BIND_TIMEOUT_S, TimeUnit.SECONDS);
164                 if (!completed) {
165                     getErrPrintWriter()
166                             .println(
167                                     "Error: Sdk sandbox failed to start in "
168                                             + SANDBOX_BIND_TIMEOUT_S
169                                             + " seconds");
170                     return false;
171                 }
172                 if (!mSuccess) {
173                     getErrPrintWriter().println("Error: Sdk sandbox failed to start");
174                     return false;
175                 }
176                 return true;
177             } catch (InterruptedException e) {
178                 return false;
179             }
180         }
181 
getService()182         private ISdkSandboxService getService() {
183             return mService;
184         }
185     }
186 
runStart()187     private int runStart() {
188         handleSandboxArguments();
189         if (mService.isSdkSandboxServiceRunning(mCallingInfo)) {
190             getErrPrintWriter().println("Error: Sdk sandbox already running for "
191                     + mCallingInfo.getPackageName() + " and user " + mUserId);
192             return -1;
193         }
194 
195         LatchSandboxServiceConnectionCallback callback =
196                 new LatchSandboxServiceConnectionCallback();
197 
198         mService.startSdkSandboxIfNeeded(mCallingInfo, callback);
199         if (callback.isSuccessful()) {
200             ISdkSandboxService service = callback.getService();
201             if (mService.isSdkSandboxDisabled(service)) {
202                 getErrPrintWriter().println("Error: SDK sandbox is disabled.");
203                 mService.stopSdkSandboxService(
204                         mCallingInfo,
205                         "Shell command `sdk_sandbox start` failed due to sandbox disabled.");
206                 return -1;
207             }
208             return 0;
209         }
210         getErrPrintWriter()
211                 .println("Error: Could not start SDK sandbox for " + mCallingInfo.getPackageName());
212         return -1;
213     }
214 
runStop()215     private int runStop() {
216         handleSandboxArguments();
217         if (!mService.isSdkSandboxServiceRunning(mCallingInfo)) {
218             getErrPrintWriter().println("Sdk sandbox not running for "
219                     + mCallingInfo.getPackageName() + " and user " + mUserId);
220             return -1;
221         }
222         mService.stopSdkSandboxService(mCallingInfo, "Shell command 'sdk_sandbox stop' issued");
223         return 0;
224     }
225 
runSetState()226     private int runSetState() {
227         String opt;
228         if ((opt = getNextOption()) != null) {
229             switch (opt) {
230                 case "--enabled":
231                     mService.forceEnableSandbox();
232                     break;
233                 case "--reset":
234                     mService.clearSdkSandboxState();
235                     break;
236                 default:
237                     throw new IllegalArgumentException("Unknown argument: " + opt);
238             }
239         } else {
240             throw new IllegalArgumentException("No argument supplied to `sdk_sandbox set-state`");
241         }
242         return 0;
243     }
244 
245     @Override
onHelp()246     public void onHelp() {
247         final PrintWriter pw = getOutPrintWriter();
248         pw.println("SDK sandbox (sdk_sandbox) commands: ");
249         pw.println("    help: ");
250         pw.println("        Prints this help text.");
251         pw.println();
252         pw.println("    start [--user <USER_ID> | current] <PACKAGE>");
253         pw.println("        Start the SDK sandbox for the app <PACKAGE>. Options are:");
254         pw.println("        --user <USER_ID> | current: Specify user for app; uses current user");
255         pw.println("            if not specified");
256         pw.println();
257         pw.println("    stop [--user <USER_ID> | current] <PACKAGE>");
258         pw.println("        Stop the SDK sandbox for the app <PACKAGE>. Options are:");
259         pw.println("        --user <USER_ID> | current: Specify user for app; uses current user");
260         pw.println("            if not specified");
261         pw.println();
262         pw.println("    set-state [--enabled | --reset]");
263         pw.println("        Sets the SDK sandbox state for testing purposes. Options are:");
264         pw.println("        --enabled: Sets the state to enabled");
265         pw.println("        --reset: Resets the state. It will be calculated the next time an");
266         pw.println("                 SDK is loaded");
267     }
268 }
269