• 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 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