SecurityLogMonitor.java revision c3cd05f8a45ab789aae1cb553df86f94667d595a
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
17package com.android.server.devicepolicy;
18
19import android.app.admin.DeviceAdminReceiver;
20import android.auditing.SecurityLog;
21import android.auditing.SecurityLog.SecurityEvent;
22import android.util.Log;
23import android.util.Slog;
24
25import com.android.internal.annotations.GuardedBy;
26
27import java.io.IOException;
28import java.util.ArrayList;
29import java.util.List;
30import java.util.concurrent.TimeUnit;
31
32import android.os.Process;
33
34/**
35 * A class managing access to the security logs. It maintains an internal buffer of pending
36 * logs to be retrieved by the device owner. The logs are retrieved from the logd daemon via
37 * JNI binding, and kept until device owner has retrieved to prevent loss of logs. Access to
38 * the logs from the device owner is rate-limited, and device owner is notified when the logs
39 * are ready to be retrieved. This happens every two hours, or when our internal buffer is
40 * larger than a certain threshold.
41 */
42class SecurityLogMonitor implements Runnable {
43    private final DevicePolicyManagerService mService;
44
45    SecurityLogMonitor(DevicePolicyManagerService service) {
46        mService = service;
47    }
48
49    private static final boolean DEBUG = false;
50    private static final String TAG = "SecurityLogMonitor";
51    /**
52     * Each log entry can hold up to 4K bytes (but as of {@link android.os.Build.VERSION_CODES#N}
53     * it should be less than 100 bytes), setting 1024 entries as the threshold to notify Device
54     * Owner.
55     */
56    private static final int BUFFER_ENTRIES_NOTIFICATION_LEVEL = 1024;
57    /**
58     * The maximum number of entries we should store before dropping earlier logs, to limit the
59     * memory usage.
60     */
61    private static final int BUFFER_ENTRIES_MAXIMUM_LEVEL = BUFFER_ENTRIES_NOTIFICATION_LEVEL * 10;
62    /**
63     * How often should Device Owner be notified under normal circumstances.
64     */
65    private static final long RATE_LIMIT_INTERVAL_MILLISECONDS = TimeUnit.HOURS.toMillis(2);
66    /**
67     * Internally how often should the monitor poll the security logs from logd.
68     */
69    private static final long POLLING_INTERVAL_MILLISECONDS = TimeUnit.MINUTES.toMillis(1);
70
71    @GuardedBy("this")
72    private Thread mMonitorThread = null;
73    @GuardedBy("this")
74    private ArrayList<SecurityEvent> mPendingLogs = new ArrayList<SecurityEvent>();
75    @GuardedBy("this")
76    private boolean mAllowedToRetrieve = false;
77    // When DO will be allowed to retrieves the log, in milliseconds.
78    @GuardedBy("this")
79    private long mNextAllowedRetrivalTimeMillis = -1;
80
81    synchronized void start() {
82        if (mMonitorThread == null) {
83            mPendingLogs = new ArrayList<SecurityEvent>();
84            mAllowedToRetrieve = false;
85            mNextAllowedRetrivalTimeMillis = -1;
86
87            mMonitorThread = new Thread(this);
88            mMonitorThread.start();
89        }
90    }
91
92    synchronized void stop() {
93        if (mMonitorThread != null) {
94            mMonitorThread.interrupt();
95            try {
96                mMonitorThread.join(TimeUnit.SECONDS.toMillis(5));
97            } catch (InterruptedException e) {
98                Log.e(TAG, "Interrupted while waiting for thread to stop", e);
99            }
100            mMonitorThread = null;
101        }
102    }
103
104    /**
105     * Returns the new batch of logs since the last call to this method. Returns null if
106     * rate limit is exceeded.
107     */
108    synchronized List<SecurityEvent> retrieveLogs() {
109        if (mAllowedToRetrieve) {
110            mAllowedToRetrieve = false;
111            mNextAllowedRetrivalTimeMillis = System.currentTimeMillis()
112                    + RATE_LIMIT_INTERVAL_MILLISECONDS;
113            List<SecurityEvent> result = mPendingLogs;
114            mPendingLogs = new ArrayList<SecurityEvent>();
115            return result;
116        } else {
117            return null;
118        }
119    }
120
121    @Override
122    public void run() {
123        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
124
125        ArrayList<SecurityEvent> logs = new ArrayList<SecurityEvent>();
126        // The timestamp of the latest log entry that has been read, in nanoseconds
127        long lastLogTimestampNanos = -1;
128        while (!Thread.currentThread().isInterrupted()) {
129            try {
130                Thread.sleep(POLLING_INTERVAL_MILLISECONDS);
131
132                if (lastLogTimestampNanos < 0) {
133                    // Non-blocking read that returns all logs immediately.
134                    if (DEBUG) Slog.d(TAG, "SecurityLog.readEvents");
135                    SecurityLog.readEvents(logs);
136                } else {
137                    if (DEBUG) Slog.d(TAG,
138                            "SecurityLog.readEventsSince: " + lastLogTimestampNanos);
139                    // Non-blocking read that returns all logs >= the  timestamp immediately.
140                    SecurityLog.readEventsSince(lastLogTimestampNanos + 1, logs);
141                }
142                if (!logs.isEmpty()) {
143                    if (DEBUG) Slog.d(TAG, "processing new logs");
144                    synchronized (this) {
145                        mPendingLogs.addAll(logs);
146                        if (mPendingLogs.size() > BUFFER_ENTRIES_MAXIMUM_LEVEL) {
147                            // Truncate buffer down to half of BUFFER_ENTRIES_MAXIMUM_LEVEL
148                            mPendingLogs = new ArrayList<SecurityEvent>(mPendingLogs.subList(
149                                    mPendingLogs.size() - (BUFFER_ENTRIES_MAXIMUM_LEVEL / 2),
150                                    mPendingLogs.size()));
151                        }
152                    }
153                    lastLogTimestampNanos = logs.get(logs.size() - 1).getTimeNanos();
154                    logs.clear();
155                }
156                notifyDeviceOwnerIfNeeded();
157            } catch (IOException e) {
158                Log.e(TAG, "Failed to read security log", e);
159            } catch (InterruptedException e) {
160                Log.i(TAG, "Thread interrupted, exiting.", e);
161                // We are asked to stop.
162                break;
163            }
164        }
165        if (DEBUG) Slog.d(TAG, "MonitorThread exit.");
166        synchronized (this) {
167            // Reset state and clear buffer
168            mPendingLogs = new ArrayList<SecurityEvent>();
169            mAllowedToRetrieve = false;
170            mNextAllowedRetrivalTimeMillis = -1;
171        }
172    }
173
174    private void notifyDeviceOwnerIfNeeded() {
175        boolean shouldNotifyDO = false;
176        boolean allowToRetrieveNow = false;
177        synchronized (this) {
178            int logSize = mPendingLogs.size();
179            if (logSize >= BUFFER_ENTRIES_NOTIFICATION_LEVEL) {
180                // Allow DO to retrieve logs if too many pending logs
181                allowToRetrieveNow = true;
182            } else if (logSize > 0) {
183                if (mNextAllowedRetrivalTimeMillis == -1 ||
184                        System.currentTimeMillis() >= mNextAllowedRetrivalTimeMillis) {
185                    // Rate limit reset
186                    allowToRetrieveNow = true;
187                }
188            }
189            shouldNotifyDO = (!mAllowedToRetrieve) && allowToRetrieveNow;
190            mAllowedToRetrieve = allowToRetrieveNow;
191        }
192        if (shouldNotifyDO) {
193            if (DEBUG) Slog.d(TAG, "notify DO");
194            mService.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_SECURITY_LOGS_AVAILABLE,
195                    null);
196        }
197    }
198}