/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.devicepolicy; import android.app.admin.DeviceAdminReceiver; import android.app.admin.SecurityLog; import android.app.admin.SecurityLog.SecurityEvent; import android.util.Log; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import android.os.Process; /** * A class managing access to the security logs. It maintains an internal buffer of pending * logs to be retrieved by the device owner. The logs are retrieved from the logd daemon via * JNI binding, and kept until device owner has retrieved to prevent loss of logs. Access to * the logs from the device owner is rate-limited, and device owner is notified when the logs * are ready to be retrieved. This happens every two hours, or when our internal buffer is * larger than a certain threshold. */ class SecurityLogMonitor implements Runnable { private final DevicePolicyManagerService mService; private final Lock mLock = new ReentrantLock(); SecurityLogMonitor(DevicePolicyManagerService service) { mService = service; } private static final boolean DEBUG = false; private static final String TAG = "SecurityLogMonitor"; /** * Each log entry can hold up to 4K bytes (but as of {@link android.os.Build.VERSION_CODES#N} * it should be less than 100 bytes), setting 1024 entries as the threshold to notify Device * Owner. */ private static final int BUFFER_ENTRIES_NOTIFICATION_LEVEL = 1024; /** * The maximum number of entries we should store before dropping earlier logs, to limit the * memory usage. */ private static final int BUFFER_ENTRIES_MAXIMUM_LEVEL = BUFFER_ENTRIES_NOTIFICATION_LEVEL * 10; /** * How often should Device Owner be notified under normal circumstances. */ private static final long RATE_LIMIT_INTERVAL_MILLISECONDS = TimeUnit.HOURS.toMillis(2); /** * Internally how often should the monitor poll the security logs from logd. */ private static final long POLLING_INTERVAL_MILLISECONDS = TimeUnit.MINUTES.toMillis(1); @GuardedBy("mLock") private Thread mMonitorThread = null; @GuardedBy("mLock") private ArrayList<SecurityEvent> mPendingLogs = new ArrayList<SecurityEvent>(); @GuardedBy("mLock") private boolean mAllowedToRetrieve = false; // When DO will be allowed to retrieves the log, in milliseconds. @GuardedBy("mLock") private long mNextAllowedRetrivalTimeMillis = -1; void start() { mLock.lock(); try { if (mMonitorThread == null) { mPendingLogs = new ArrayList<SecurityEvent>(); mAllowedToRetrieve = false; mNextAllowedRetrivalTimeMillis = -1; mMonitorThread = new Thread(this); mMonitorThread.start(); } } finally { mLock.unlock(); } } void stop() { mLock.lock(); try { if (mMonitorThread != null) { mMonitorThread.interrupt(); try { mMonitorThread.join(TimeUnit.SECONDS.toMillis(5)); } catch (InterruptedException e) { Log.e(TAG, "Interrupted while waiting for thread to stop", e); } // Reset state and clear buffer mPendingLogs = new ArrayList<SecurityEvent>(); mAllowedToRetrieve = false; mNextAllowedRetrivalTimeMillis = -1; mMonitorThread = null; } } finally { mLock.unlock(); } } /** * Returns the new batch of logs since the last call to this method. Returns null if * rate limit is exceeded. */ List<SecurityEvent> retrieveLogs() { mLock.lock(); try { if (mAllowedToRetrieve) { mAllowedToRetrieve = false; mNextAllowedRetrivalTimeMillis = System.currentTimeMillis() + RATE_LIMIT_INTERVAL_MILLISECONDS; List<SecurityEvent> result = mPendingLogs; mPendingLogs = new ArrayList<SecurityEvent>(); return result; } else { return null; } } finally { mLock.unlock(); } } @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); ArrayList<SecurityEvent> logs = new ArrayList<SecurityEvent>(); // The timestamp of the latest log entry that has been read, in nanoseconds long lastLogTimestampNanos = -1; while (!Thread.currentThread().isInterrupted()) { try { Thread.sleep(POLLING_INTERVAL_MILLISECONDS); if (lastLogTimestampNanos < 0) { // Non-blocking read that returns all logs immediately. if (DEBUG) Slog.d(TAG, "SecurityLog.readEvents"); SecurityLog.readEvents(logs); } else { if (DEBUG) Slog.d(TAG, "SecurityLog.readEventsSince: " + lastLogTimestampNanos); // Non-blocking read that returns all logs >= the timestamp immediately. SecurityLog.readEventsSince(lastLogTimestampNanos + 1, logs); } if (!logs.isEmpty()) { if (DEBUG) Slog.d(TAG, "processing new logs"); mLock.lockInterruptibly(); try { mPendingLogs.addAll(logs); if (mPendingLogs.size() > BUFFER_ENTRIES_MAXIMUM_LEVEL) { // Truncate buffer down to half of BUFFER_ENTRIES_MAXIMUM_LEVEL mPendingLogs = new ArrayList<SecurityEvent>(mPendingLogs.subList( mPendingLogs.size() - (BUFFER_ENTRIES_MAXIMUM_LEVEL / 2), mPendingLogs.size())); } } finally { mLock.unlock(); } lastLogTimestampNanos = logs.get(logs.size() - 1).getTimeNanos(); logs.clear(); } notifyDeviceOwnerIfNeeded(); } catch (IOException e) { Log.e(TAG, "Failed to read security log", e); } catch (InterruptedException e) { Log.i(TAG, "Thread interrupted, exiting.", e); // We are asked to stop. break; } } if (DEBUG) Slog.d(TAG, "MonitorThread exit."); } private void notifyDeviceOwnerIfNeeded() throws InterruptedException { boolean shouldNotifyDO = false; boolean allowToRetrieveNow = false; mLock.lockInterruptibly(); try { int logSize = mPendingLogs.size(); if (logSize >= BUFFER_ENTRIES_NOTIFICATION_LEVEL) { // Allow DO to retrieve logs if too many pending logs allowToRetrieveNow = true; } else if (logSize > 0) { if (mNextAllowedRetrivalTimeMillis == -1 || System.currentTimeMillis() >= mNextAllowedRetrivalTimeMillis) { // Rate limit reset allowToRetrieveNow = true; } } shouldNotifyDO = (!mAllowedToRetrieve) && allowToRetrieveNow; mAllowedToRetrieve = allowToRetrieveNow; } finally { mLock.unlock(); } if (shouldNotifyDO) { if (DEBUG) Slog.d(TAG, "notify DO"); mService.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_SECURITY_LOGS_AVAILABLE, null); } } }