• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.app.admin.SecurityLog;
20 import android.content.Context;
21 import android.content.pm.ApkChecksum;
22 import android.content.pm.Checksum;
23 import android.content.pm.IOnChecksumsReadyListener;
24 import android.content.pm.PackageManagerInternal;
25 import android.os.Bundle;
26 import android.os.Handler;
27 import android.os.HandlerExecutor;
28 import android.os.RemoteException;
29 import android.text.TextUtils;
30 import android.util.ArrayMap;
31 import android.util.Slog;
32 
33 import com.android.internal.os.BackgroundThread;
34 
35 import java.util.ArrayList;
36 import java.util.List;
37 import java.util.concurrent.Executor;
38 
39 public final class ProcessLoggingHandler extends Handler {
40     private static final String TAG = "ProcessLoggingHandler";
41 
42     private static final int CHECKSUM_TYPE = Checksum.TYPE_WHOLE_SHA256;
43 
44     static class LoggingInfo {
45         public String apkHash = null;
46         public List<Bundle> pendingLogEntries = new ArrayList<>();
47     }
48 
49     // Executor to handle checksum calculations.
50     private final Executor mExecutor = new HandlerExecutor(this);
51 
52     // Apk path to logging info map.
53     private final ArrayMap<String, LoggingInfo> mLoggingInfo = new ArrayMap<>();
54 
ProcessLoggingHandler()55     ProcessLoggingHandler() {
56         super(BackgroundThread.getHandler().getLooper());
57     }
58 
logAppProcessStart(Context context, PackageManagerInternal pmi, String apkFile, String packageName, String processName, int uid, String seinfo, int pid)59     void logAppProcessStart(Context context, PackageManagerInternal pmi, String apkFile,
60             String packageName, String processName, int uid, String seinfo, int pid) {
61         Bundle data = new Bundle();
62         data.putLong("startTimestamp", System.currentTimeMillis());
63         data.putString("processName", processName);
64         data.putInt("uid", uid);
65         data.putString("seinfo", seinfo);
66         data.putInt("pid", pid);
67 
68         if (apkFile == null) {
69             enqueueSecurityLogEvent(data, "No APK");
70             return;
71         }
72 
73         // Check cached apk hash.
74         boolean requestChecksums;
75         final LoggingInfo loggingInfo;
76         synchronized (mLoggingInfo) {
77             LoggingInfo cached = mLoggingInfo.get(apkFile);
78             requestChecksums = cached == null;
79             if (requestChecksums) {
80                 // Create a new pending cache entry.
81                 cached = new LoggingInfo();
82                 mLoggingInfo.put(apkFile, cached);
83             }
84             loggingInfo = cached;
85         }
86 
87         synchronized (loggingInfo) {
88             // Still pending?
89             if (!TextUtils.isEmpty(loggingInfo.apkHash)) {
90                 enqueueSecurityLogEvent(data, loggingInfo.apkHash);
91                 return;
92             }
93 
94             loggingInfo.pendingLogEntries.add(data);
95         }
96 
97         if (!requestChecksums) {
98             return;
99         }
100 
101         // Request base checksums when first added entry.
102         // Capturing local loggingInfo to still log even if hash was invalidated.
103         try {
104             pmi.requestChecksums(packageName, false, 0, CHECKSUM_TYPE, null,
105                     new IOnChecksumsReadyListener.Stub() {
106                         @Override
107                         public void onChecksumsReady(List<ApkChecksum> checksums)
108                                 throws RemoteException {
109                             processChecksums(loggingInfo, checksums);
110                         }
111                     }, context.getUserId(),
112                     mExecutor, this);
113         } catch (Throwable t) {
114             Slog.e(TAG, "requestChecksums() failed", t);
115             enqueueProcessChecksum(loggingInfo, null);
116         }
117     }
118 
processChecksums(final LoggingInfo loggingInfo, List<ApkChecksum> checksums)119     void processChecksums(final LoggingInfo loggingInfo, List<ApkChecksum> checksums) {
120         for (int i = 0, size = checksums.size(); i < size; ++i) {
121             ApkChecksum checksum = checksums.get(i);
122             if (checksum.getType() == CHECKSUM_TYPE) {
123                 processChecksum(loggingInfo, checksum.getValue());
124                 return;
125             }
126         }
127 
128         Slog.e(TAG, "requestChecksums() failed to return SHA256, see logs for details.");
129         processChecksum(loggingInfo, null);
130     }
131 
enqueueProcessChecksum(final LoggingInfo loggingInfo, final byte[] hash)132     void enqueueProcessChecksum(final LoggingInfo loggingInfo, final byte[] hash) {
133         this.post(() -> processChecksum(loggingInfo, null));
134     }
135 
processChecksum(final LoggingInfo loggingInfo, final byte[] hash)136     void processChecksum(final LoggingInfo loggingInfo, final byte[] hash) {
137         final String apkHash;
138         if (hash != null) {
139             StringBuilder sb = new StringBuilder();
140             for (int i = 0; i < hash.length; i++) {
141                 sb.append(String.format("%02x", hash[i]));
142             }
143             apkHash = sb.toString();
144         } else {
145             apkHash = "Failed to count APK hash";
146         }
147 
148         List<Bundle> pendingLogEntries;
149         synchronized (loggingInfo) {
150             if (!TextUtils.isEmpty(loggingInfo.apkHash)) {
151                 return;
152             }
153             loggingInfo.apkHash = apkHash;
154 
155             pendingLogEntries = loggingInfo.pendingLogEntries;
156             loggingInfo.pendingLogEntries = null;
157         }
158 
159         if (pendingLogEntries != null) {
160             for (Bundle data : pendingLogEntries) {
161                 logSecurityLogEvent(data, apkHash);
162             }
163         }
164     }
165 
invalidateBaseApkHash(String apkFile)166     void invalidateBaseApkHash(String apkFile) {
167         synchronized (mLoggingInfo) {
168             mLoggingInfo.remove(apkFile);
169         }
170     }
171 
enqueueSecurityLogEvent(Bundle data, String apkHash)172     void enqueueSecurityLogEvent(Bundle data, String apkHash) {
173         this.post(() -> logSecurityLogEvent(data, apkHash));
174     }
175 
logSecurityLogEvent(Bundle bundle, String apkHash)176     void logSecurityLogEvent(Bundle bundle, String apkHash) {
177         long startTimestamp = bundle.getLong("startTimestamp");
178         String processName = bundle.getString("processName");
179         int uid = bundle.getInt("uid");
180         String seinfo = bundle.getString("seinfo");
181         int pid = bundle.getInt("pid");
182         SecurityLog.writeEvent(SecurityLog.TAG_APP_PROCESS_START, processName,
183                 startTimestamp, uid, pid, seinfo, apkHash);
184     }
185 }
186