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