• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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