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