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