1 /* 2 * Copyright (C) 2019 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.pm; 18 19 import android.annotation.NonNull; 20 import android.content.ComponentName; 21 import android.content.pm.DataLoaderParams; 22 import android.content.pm.InstallationFile; 23 import android.content.pm.PackageInstaller; 24 import android.os.ParcelFileDescriptor; 25 import android.os.ShellCommand; 26 import android.service.dataloader.DataLoaderService; 27 import android.util.Slog; 28 import android.util.SparseArray; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 32 import libcore.io.IoUtils; 33 34 import java.io.IOException; 35 import java.lang.ref.WeakReference; 36 import java.nio.ByteBuffer; 37 import java.nio.ByteOrder; 38 import java.nio.charset.StandardCharsets; 39 import java.security.SecureRandom; 40 import java.util.Collection; 41 import java.util.concurrent.atomic.AtomicLong; 42 43 /** 44 * Callback data loader for PackageManagerShellCommand installations. 45 */ 46 public class PackageManagerShellCommandDataLoader extends DataLoaderService { 47 public static final String TAG = "PackageManagerShellCommandDataLoader"; 48 49 private static final String PACKAGE = "android"; 50 private static final String CLASS = PackageManagerShellCommandDataLoader.class.getName(); 51 52 static final SecureRandom sRandom = new SecureRandom(); 53 static final SparseArray<WeakReference<ShellCommand>> sShellCommands = new SparseArray<>(); 54 55 private static final char ARGS_DELIM = '&'; 56 private static final String SHELL_COMMAND_ID_PREFIX = "shellCommandId="; 57 private static final int INVALID_SHELL_COMMAND_ID = -1; 58 private static final int TOO_MANY_PENDING_SHELL_COMMANDS = 10; 59 60 private static final String STDIN_PATH = "-"; 61 getDataLoaderParamsArgs(ShellCommand shellCommand)62 private static String getDataLoaderParamsArgs(ShellCommand shellCommand) { 63 nativeInitialize(); 64 65 int commandId; 66 synchronized (sShellCommands) { 67 // Clean up old references. 68 for (int i = sShellCommands.size() - 1; i >= 0; i--) { 69 WeakReference<ShellCommand> oldRef = sShellCommands.valueAt(i); 70 if (oldRef.get() == null) { 71 sShellCommands.removeAt(i); 72 } 73 } 74 75 if (sShellCommands.size() > TOO_MANY_PENDING_SHELL_COMMANDS) { 76 Slog.e(TAG, "Too many pending shell commands: " + sShellCommands.size()); 77 } 78 79 // Generate new id and put ref to the array. 80 do { 81 commandId = sRandom.nextInt(Integer.MAX_VALUE - 1) + 1; 82 } while (sShellCommands.contains(commandId)); 83 84 sShellCommands.put(commandId, new WeakReference<>(shellCommand)); 85 } 86 87 return SHELL_COMMAND_ID_PREFIX + commandId; 88 } 89 getStreamingDataLoaderParams(ShellCommand shellCommand)90 static DataLoaderParams getStreamingDataLoaderParams(ShellCommand shellCommand) { 91 return DataLoaderParams.forStreaming(new ComponentName(PACKAGE, CLASS), 92 getDataLoaderParamsArgs(shellCommand)); 93 } 94 getIncrementalDataLoaderParams(ShellCommand shellCommand)95 static DataLoaderParams getIncrementalDataLoaderParams(ShellCommand shellCommand) { 96 return DataLoaderParams.forIncremental(new ComponentName(PACKAGE, CLASS), 97 getDataLoaderParamsArgs(shellCommand)); 98 } 99 extractShellCommandId(String args)100 private static int extractShellCommandId(String args) { 101 int sessionIdIdx = args.indexOf(SHELL_COMMAND_ID_PREFIX); 102 if (sessionIdIdx < 0) { 103 Slog.e(TAG, "Missing shell command id param."); 104 return INVALID_SHELL_COMMAND_ID; 105 } 106 sessionIdIdx += SHELL_COMMAND_ID_PREFIX.length(); 107 int delimIdx = args.indexOf(ARGS_DELIM, sessionIdIdx); 108 try { 109 if (delimIdx < 0) { 110 return Integer.parseInt(args.substring(sessionIdIdx)); 111 } else { 112 return Integer.parseInt(args.substring(sessionIdIdx, delimIdx)); 113 } 114 } catch (NumberFormatException e) { 115 Slog.e(TAG, "Incorrect shell command id format.", e); 116 return INVALID_SHELL_COMMAND_ID; 117 } 118 } 119 120 /** @hide */ 121 @VisibleForTesting 122 public static class Metadata { 123 /** 124 * Full files read from stdin. 125 */ 126 static final byte STDIN = 0; 127 /** 128 * Full files read from local file. 129 */ 130 static final byte LOCAL_FILE = 1; 131 /** 132 * Signature tree read from stdin, data streamed. 133 */ 134 static final byte DATA_ONLY_STREAMING = 2; 135 /** 136 * Everything streamed. 137 */ 138 static final byte STREAMING = 3; 139 140 private final byte mMode; 141 private final String mData; 142 private final String mSalt; 143 144 private static AtomicLong sGlobalSalt = new AtomicLong((new SecureRandom()).nextLong()); nextGlobalSalt()145 private static Long nextGlobalSalt() { 146 return sGlobalSalt.incrementAndGet(); 147 } 148 forStdIn(String fileId)149 static Metadata forStdIn(String fileId) { 150 return new Metadata(STDIN, fileId); 151 } 152 153 /** @hide */ 154 @VisibleForTesting forLocalFile(String filePath)155 public static Metadata forLocalFile(String filePath) { 156 return new Metadata(LOCAL_FILE, filePath, nextGlobalSalt().toString()); 157 } 158 forDataOnlyStreaming(String fileId)159 static Metadata forDataOnlyStreaming(String fileId) { 160 return new Metadata(DATA_ONLY_STREAMING, fileId); 161 } 162 forStreaming(String fileId)163 static Metadata forStreaming(String fileId) { 164 return new Metadata(STREAMING, fileId); 165 } 166 Metadata(byte mode, String data)167 private Metadata(byte mode, String data) { 168 this(mode, data, null); 169 } 170 Metadata(byte mode, String data, String salt)171 private Metadata(byte mode, String data, String salt) { 172 this.mMode = mode; 173 this.mData = (data == null) ? "" : data; 174 this.mSalt = salt; 175 } 176 fromByteArray(byte[] bytes)177 static Metadata fromByteArray(byte[] bytes) throws IOException { 178 if (bytes == null || bytes.length < 5) { 179 return null; 180 } 181 int offset = 0; 182 final byte mode = bytes[offset]; 183 offset += 1; 184 final String data; 185 final String salt; 186 switch (mode) { 187 case LOCAL_FILE: { 188 int dataSize = ByteBuffer.wrap(bytes, offset, 4).order( 189 ByteOrder.LITTLE_ENDIAN).getInt(); 190 offset += 4; 191 data = new String(bytes, offset, dataSize, StandardCharsets.UTF_8); 192 offset += dataSize; 193 salt = new String(bytes, offset, bytes.length - offset, 194 StandardCharsets.UTF_8); 195 break; 196 } 197 default: 198 data = new String(bytes, offset, bytes.length - offset, 199 StandardCharsets.UTF_8); 200 salt = null; 201 break; 202 } 203 return new Metadata(mode, data, salt); 204 } 205 206 /** @hide */ 207 @VisibleForTesting toByteArray()208 public byte[] toByteArray() { 209 final byte[] result; 210 final byte[] dataBytes = this.mData.getBytes(StandardCharsets.UTF_8); 211 switch (this.mMode) { 212 case LOCAL_FILE: { 213 int dataSize = dataBytes.length; 214 byte[] saltBytes = this.mSalt.getBytes(StandardCharsets.UTF_8); 215 result = new byte[1 + 4 + dataSize + saltBytes.length]; 216 int offset = 0; 217 result[offset] = this.mMode; 218 offset += 1; 219 ByteBuffer.wrap(result, offset, 4).order(ByteOrder.LITTLE_ENDIAN).putInt( 220 dataSize); 221 offset += 4; 222 System.arraycopy(dataBytes, 0, result, offset, dataSize); 223 offset += dataSize; 224 System.arraycopy(saltBytes, 0, result, offset, saltBytes.length); 225 break; 226 } 227 default: 228 result = new byte[1 + dataBytes.length]; 229 result[0] = this.mMode; 230 System.arraycopy(dataBytes, 0, result, 1, dataBytes.length); 231 break; 232 } 233 return result; 234 } 235 getMode()236 byte getMode() { 237 return this.mMode; 238 } 239 getData()240 String getData() { 241 return this.mData; 242 } 243 } 244 245 private static class DataLoader implements DataLoaderService.DataLoader { 246 private DataLoaderParams mParams = null; 247 private FileSystemConnector mConnector = null; 248 249 @Override onCreate(@onNull DataLoaderParams dataLoaderParams, @NonNull FileSystemConnector connector)250 public boolean onCreate(@NonNull DataLoaderParams dataLoaderParams, 251 @NonNull FileSystemConnector connector) { 252 mParams = dataLoaderParams; 253 mConnector = connector; 254 return true; 255 } 256 257 @Override onPrepareImage(@onNull Collection<InstallationFile> addedFiles, @NonNull Collection<String> removedFiles)258 public boolean onPrepareImage(@NonNull Collection<InstallationFile> addedFiles, 259 @NonNull Collection<String> removedFiles) { 260 ShellCommand shellCommand = lookupShellCommand(mParams.getArguments()); 261 if (shellCommand == null) { 262 Slog.e(TAG, "Missing shell command."); 263 return false; 264 } 265 try { 266 for (InstallationFile file : addedFiles) { 267 Metadata metadata = Metadata.fromByteArray(file.getMetadata()); 268 if (metadata == null) { 269 Slog.e(TAG, "Invalid metadata for file: " + file.getName()); 270 return false; 271 } 272 switch (metadata.getMode()) { 273 case Metadata.STDIN: { 274 final ParcelFileDescriptor inFd = getStdInPFD(shellCommand); 275 mConnector.writeData(file.getName(), 0, file.getLengthBytes(), inFd); 276 break; 277 } 278 case Metadata.LOCAL_FILE: { 279 ParcelFileDescriptor incomingFd = null; 280 try { 281 incomingFd = getLocalFilePFD(shellCommand, metadata.getData()); 282 mConnector.writeData(file.getName(), 0, incomingFd.getStatSize(), 283 incomingFd); 284 } finally { 285 IoUtils.closeQuietly(incomingFd); 286 } 287 break; 288 } 289 default: 290 Slog.e(TAG, "Unsupported metadata mode: " + metadata.getMode()); 291 return false; 292 } 293 } 294 return true; 295 } catch (IOException e) { 296 Slog.e(TAG, "Exception while streaming files", e); 297 return false; 298 } 299 } 300 } 301 lookupShellCommand(String args)302 static ShellCommand lookupShellCommand(String args) { 303 final int commandId = extractShellCommandId(args); 304 if (commandId == INVALID_SHELL_COMMAND_ID) { 305 return null; 306 } 307 308 final WeakReference<ShellCommand> shellCommandRef; 309 synchronized (sShellCommands) { 310 shellCommandRef = sShellCommands.get(commandId, null); 311 } 312 final ShellCommand shellCommand = 313 shellCommandRef != null ? shellCommandRef.get() : null; 314 315 return shellCommand; 316 } 317 getStdInPFD(ShellCommand shellCommand)318 static ParcelFileDescriptor getStdInPFD(ShellCommand shellCommand) { 319 try { 320 return ParcelFileDescriptor.dup(shellCommand.getInFileDescriptor()); 321 } catch (IOException e) { 322 Slog.e(TAG, "Exception while obtaining STDIN fd", e); 323 return null; 324 } 325 } 326 getLocalFilePFD(ShellCommand shellCommand, String filePath)327 static ParcelFileDescriptor getLocalFilePFD(ShellCommand shellCommand, String filePath) { 328 return shellCommand.openFileForSystem(filePath, "r"); 329 } 330 getStdIn(ShellCommand shellCommand)331 static int getStdIn(ShellCommand shellCommand) { 332 ParcelFileDescriptor pfd = getStdInPFD(shellCommand); 333 return pfd == null ? -1 : pfd.detachFd(); 334 } 335 getLocalFile(ShellCommand shellCommand, String filePath)336 static int getLocalFile(ShellCommand shellCommand, String filePath) { 337 ParcelFileDescriptor pfd = getLocalFilePFD(shellCommand, filePath); 338 return pfd == null ? -1 : pfd.detachFd(); 339 } 340 341 @Override onCreateDataLoader( @onNull DataLoaderParams dataLoaderParams)342 public DataLoaderService.DataLoader onCreateDataLoader( 343 @NonNull DataLoaderParams dataLoaderParams) { 344 if (dataLoaderParams.getType() == PackageInstaller.DATA_LOADER_TYPE_STREAMING) { 345 // This DataLoader only supports streaming installations. 346 return new DataLoader(); 347 } 348 return null; 349 } 350 351 /* Native methods */ nativeInitialize()352 private static native void nativeInitialize(); 353 } 354