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.devicepolicy;
18 
19 import static java.util.concurrent.TimeUnit.MILLISECONDS;
20 import static java.util.concurrent.TimeUnit.NANOSECONDS;
21 
22 import android.app.admin.DeviceAdminReceiver;
23 import android.app.admin.SecurityLog;
24 import android.app.admin.SecurityLog.SecurityEvent;
25 import android.os.Process;
26 import android.os.SystemClock;
27 import android.util.Log;
28 import android.util.Slog;
29 
30 import com.android.internal.annotations.GuardedBy;
31 import com.android.internal.annotations.VisibleForTesting;
32 
33 import java.io.IOException;
34 import java.util.ArrayList;
35 import java.util.List;
36 import java.util.concurrent.Semaphore;
37 import java.util.concurrent.TimeUnit;
38 import java.util.concurrent.locks.Lock;
39 import java.util.concurrent.locks.ReentrantLock;
40 
41 /**
42  * A class managing access to the security logs. It maintains an internal buffer of pending
43  * logs to be retrieved by the device owner. The logs are retrieved from the logd daemon via
44  * JNI binding, and kept until device owner has retrieved to prevent loss of logs. Access to
45  * the logs from the device owner is rate-limited, and device owner is notified when the logs
46  * are ready to be retrieved. This happens every two hours, or when our internal buffer is
47  * larger than a certain threshold.
48  */
49 class SecurityLogMonitor implements Runnable {
50     private final DevicePolicyManagerService mService;
51 
52     private final Lock mLock = new ReentrantLock();
53 
SecurityLogMonitor(DevicePolicyManagerService service)54     SecurityLogMonitor(DevicePolicyManagerService service) {
55         this(service, 0 /* id */);
56     }
57 
58     @VisibleForTesting
SecurityLogMonitor(DevicePolicyManagerService service, long id)59     SecurityLogMonitor(DevicePolicyManagerService service, long id) {
60         this.mService = service;
61         this.mId = id;
62         this.mLastForceNanos = System.nanoTime();
63     }
64 
65     private static final boolean DEBUG = false;  // STOPSHIP if true.
66     private static final String TAG = "SecurityLogMonitor";
67     /**
68      * Each log entry can hold up to 4K bytes (but as of {@link android.os.Build.VERSION_CODES#N}
69      * it should be less than 100 bytes), setting 1024 entries as the threshold to notify Device
70      * Owner.
71      */
72     @VisibleForTesting static final int BUFFER_ENTRIES_NOTIFICATION_LEVEL = 1024;
73     /**
74      * The maximum number of entries we should store before dropping earlier logs, to limit the
75      * memory usage.
76      */
77     private static final int BUFFER_ENTRIES_MAXIMUM_LEVEL = BUFFER_ENTRIES_NOTIFICATION_LEVEL * 10;
78     /**
79      * Critical log buffer level, 90% of capacity.
80      */
81     private static final int BUFFER_ENTRIES_CRITICAL_LEVEL = BUFFER_ENTRIES_MAXIMUM_LEVEL * 9 / 10;
82     /**
83      * How often should Device Owner be notified under normal circumstances.
84      */
85     private static final long RATE_LIMIT_INTERVAL_MS = TimeUnit.HOURS.toMillis(2);
86     /**
87      * How often to retry the notification about available logs if it is ignored or missed by DO.
88      */
89     private static final long BROADCAST_RETRY_INTERVAL_MS = TimeUnit.MINUTES.toMillis(30);
90     /**
91      * Internally how often should the monitor poll the security logs from logd.
92      */
93     private static final long POLLING_INTERVAL_MS = TimeUnit.MINUTES.toMillis(1);
94     /**
95      * Overlap between two subsequent log requests, required to avoid losing out of order events.
96      */
97     private static final long OVERLAP_NS = TimeUnit.SECONDS.toNanos(3);
98 
99     /** Minimum time between forced fetch attempts. */
100     private static final long FORCE_FETCH_THROTTLE_NS = TimeUnit.SECONDS.toNanos(10);
101 
102     @GuardedBy("mLock")
103     private Thread mMonitorThread = null;
104     @GuardedBy("mLock")
105     private ArrayList<SecurityEvent> mPendingLogs = new ArrayList<>();
106     @GuardedBy("mLock")
107     private long mId;
108     @GuardedBy("mLock")
109     private boolean mAllowedToRetrieve = false;
110 
111     // Whether we have already logged the fact that log buffer reached 90%, to avoid dupes.
112     @GuardedBy("mLock")
113     private boolean mCriticalLevelLogged = false;
114 
115     /**
116      * Last events fetched from log to check for overlap between batches. We can leave it empty if
117      * we are sure there will be no overlap anymore, e.g. when we get empty batch.
118      */
119     private final ArrayList<SecurityEvent> mLastEvents = new ArrayList<>();
120     /** Timestamp of the very last event, -1 means request from the beginning of time. */
121     private long mLastEventNanos = -1;
122 
123     /**
124      * When DO will be allowed to retrieve the log, in milliseconds since boot (as per
125      * {@link SystemClock#elapsedRealtime()}). After that it will mark the time to retry broadcast.
126      */
127     @GuardedBy("mLock")
128     private long mNextAllowedRetrievalTimeMillis = -1;
129     @GuardedBy("mLock")
130     private boolean mPaused = false;
131 
132     /** Semaphore used to force log fetching on request from adb. */
133     private final Semaphore mForceSemaphore = new Semaphore(0 /* permits */);
134 
135     /** The last timestamp when force fetch was used, to prevent abuse. */
136     @GuardedBy("mForceSemaphore")
137     private long mLastForceNanos = 0;
138 
start()139     void start() {
140         Slog.i(TAG, "Starting security logging.");
141         SecurityLog.writeEvent(SecurityLog.TAG_LOGGING_STARTED);
142         mLock.lock();
143         try {
144             if (mMonitorThread == null) {
145                 mPendingLogs = new ArrayList<>();
146                 mCriticalLevelLogged = false;
147                 mId = 0;
148                 mAllowedToRetrieve = false;
149                 mNextAllowedRetrievalTimeMillis = -1;
150                 mPaused = false;
151 
152                 mMonitorThread = new Thread(this);
153                 mMonitorThread.start();
154             }
155         } finally {
156             mLock.unlock();
157         }
158     }
159 
stop()160     void stop() {
161         Slog.i(TAG, "Stopping security logging.");
162         SecurityLog.writeEvent(SecurityLog.TAG_LOGGING_STOPPED);
163         mLock.lock();
164         try {
165             if (mMonitorThread != null) {
166                 mMonitorThread.interrupt();
167                 try {
168                     mMonitorThread.join(TimeUnit.SECONDS.toMillis(5));
169                 } catch (InterruptedException e) {
170                     Log.e(TAG, "Interrupted while waiting for thread to stop", e);
171                 }
172                 // Reset state and clear buffer
173                 mPendingLogs = new ArrayList<>();
174                 mId = 0;
175                 mAllowedToRetrieve = false;
176                 mNextAllowedRetrievalTimeMillis = -1;
177                 mPaused = false;
178                 mMonitorThread = null;
179             }
180         } finally {
181             mLock.unlock();
182         }
183     }
184 
185     /**
186      * If logs are being collected, keep collecting them but stop notifying the device owner that
187      * new logs are available (since they cannot be retrieved).
188      */
pause()189     void pause() {
190         Slog.i(TAG, "Paused.");
191 
192         mLock.lock();
193         mPaused = true;
194         mAllowedToRetrieve = false;
195         mLock.unlock();
196     }
197 
198     /**
199      * If logs are being collected, start notifying the device owner when logs are ready to be
200      * retrieved again (if it was paused).
201      * <p>If logging is enabled and there are logs ready to be retrieved, this method will attempt
202      * to notify the device owner. Therefore calling identity should be cleared before calling it
203      * (in case the method is called from a user other than the DO's user).
204      */
resume()205     void resume() {
206         mLock.lock();
207         try {
208             if (!mPaused) {
209                 Log.d(TAG, "Attempted to resume, but logging is not paused.");
210                 return;
211             }
212             mPaused = false;
213             mAllowedToRetrieve = false;
214         } finally {
215             mLock.unlock();
216         }
217 
218         Slog.i(TAG, "Resumed.");
219         try {
220             notifyDeviceOwnerIfNeeded(false /* force */);
221         } catch (InterruptedException e) {
222             Log.w(TAG, "Thread interrupted.", e);
223         }
224     }
225 
226     /**
227      * Discard all collected logs.
228      */
discardLogs()229     void discardLogs() {
230         mLock.lock();
231         mAllowedToRetrieve = false;
232         mPendingLogs = new ArrayList<>();
233         mCriticalLevelLogged = false;
234         mLock.unlock();
235         Slog.i(TAG, "Discarded all logs.");
236     }
237 
238     /**
239      * Returns the new batch of logs since the last call to this method. Returns null if
240      * rate limit is exceeded.
241      */
retrieveLogs()242     List<SecurityEvent> retrieveLogs() {
243         mLock.lock();
244         try {
245             if (mAllowedToRetrieve) {
246                 mAllowedToRetrieve = false;
247                 mNextAllowedRetrievalTimeMillis = SystemClock.elapsedRealtime()
248                         + RATE_LIMIT_INTERVAL_MS;
249                 List<SecurityEvent> result = mPendingLogs;
250                 mPendingLogs = new ArrayList<>();
251                 mCriticalLevelLogged = false;
252                 return result;
253             } else {
254                 return null;
255             }
256         } finally {
257             mLock.unlock();
258         }
259     }
260 
261     /**
262      * Requests the next (or the first) batch of events from the log with appropriate timestamp.
263      */
getNextBatch(ArrayList<SecurityEvent> newLogs)264     private void getNextBatch(ArrayList<SecurityEvent> newLogs) throws IOException {
265         if (mLastEventNanos < 0) {
266             // Non-blocking read that returns all logs immediately.
267             if (DEBUG) Slog.d(TAG, "SecurityLog.readEvents");
268             SecurityLog.readEvents(newLogs);
269         } else {
270             // If we have last events from the previous batch, request log events with time overlap
271             // with previously retrieved messages to avoid losing events due to reordering in logd.
272             final long startNanos = mLastEvents.isEmpty()
273                     ? mLastEventNanos : Math.max(0, mLastEventNanos - OVERLAP_NS);
274             if (DEBUG) Slog.d(TAG, "SecurityLog.readEventsSince: " + startNanos);
275             // Non-blocking read that returns all logs with timestamps >= startNanos immediately.
276             SecurityLog.readEventsSince(startNanos, newLogs);
277         }
278 
279         // Sometimes events may be reordered in logd due to simultaneous readers and writers. In
280         // this case, we have to sort it to make overlap checking work. This is very unlikely.
281         for (int i = 0; i < newLogs.size() - 1; i++) {
282             if (newLogs.get(i).getTimeNanos() > newLogs.get(i+1).getTimeNanos()) {
283                 if (DEBUG) Slog.d(TAG, "Got out of order events, sorting.");
284                 // Sort using comparator that compares timestamps.
285                 newLogs.sort((e1, e2) -> Long.signum(e1.getTimeNanos() - e2.getTimeNanos()));
286                 break;
287             }
288         }
289 
290         if (DEBUG) Slog.d(TAG, "Got " + newLogs.size() + " new events.");
291     }
292 
293     /**
294      * Save the last events for overlap checking with the next batch.
295      */
saveLastEvents(ArrayList<SecurityEvent> newLogs)296     private void saveLastEvents(ArrayList<SecurityEvent> newLogs) {
297         mLastEvents.clear();
298         if (newLogs.isEmpty()) {
299             // This can happen if no events were logged yet or the buffer got cleared. In this case
300             // we aren't going to have any overlap next time, leave mLastEvents events empty.
301             return;
302         }
303 
304         // Save the last timestamp.
305         mLastEventNanos = newLogs.get(newLogs.size() - 1).getTimeNanos();
306         // Position of the earliest event that has to be saved. Start from the penultimate event,
307         // going backward.
308         int pos = newLogs.size() - 2;
309         while (pos >= 0 && mLastEventNanos - newLogs.get(pos).getTimeNanos() < OVERLAP_NS) {
310             pos--;
311         }
312         // We either run past the start of the list or encountered an event that is too old to keep.
313         pos++;
314         mLastEvents.addAll(newLogs.subList(pos, newLogs.size()));
315         if (DEBUG) Slog.d(TAG, mLastEvents.size() + " events saved for overlap check");
316     }
317 
318     /**
319      * Merges a new batch into already fetched logs and deals with overlapping and out of order
320      * events.
321      */
322     @GuardedBy("mLock")
mergeBatchLocked(final ArrayList<SecurityEvent> newLogs)323     private void mergeBatchLocked(final ArrayList<SecurityEvent> newLogs) {
324         // Reserve capacity so that copying doesn't occur.
325         mPendingLogs.ensureCapacity(mPendingLogs.size() + newLogs.size());
326         // Run through the first events of the batch to check if there is an overlap with previous
327         // batch and if so, skip overlapping events. Events are sorted by timestamp, so we can
328         // compare it in linear time by advancing two pointers, one for each batch.
329         int curPos = 0;
330         int lastPos = 0;
331         // For the first batch mLastEvents will be empty, so no iterations will happen.
332         while (lastPos < mLastEvents.size() && curPos < newLogs.size()) {
333             final SecurityEvent curEvent = newLogs.get(curPos);
334             final long currentNanos = curEvent.getTimeNanos();
335             if (currentNanos > mLastEventNanos) {
336                 // We got past the last event of the last batch, no overlap possible anymore.
337                 break;
338             }
339             final SecurityEvent lastEvent = mLastEvents.get(lastPos);
340             final long lastNanos = lastEvent.getTimeNanos();
341             if (lastNanos > currentNanos) {
342                 // New event older than the last we've seen so far, must be due to reordering.
343                 if (DEBUG) Slog.d(TAG, "New event in the overlap: " + currentNanos);
344                 assignLogId(curEvent);
345                 mPendingLogs.add(curEvent);
346                 curPos++;
347             } else if (lastNanos < currentNanos) {
348                 if (DEBUG) Slog.d(TAG, "Event disappeared from the overlap: " + lastNanos);
349                 lastPos++;
350             } else {
351                 // Two events have the same timestamp, check if they are the same.
352                 if (lastEvent.eventEquals(curEvent)) {
353                     // Actual overlap, just skip the event.
354                     if (DEBUG) Slog.d(TAG, "Skipped dup event with timestamp: " + lastNanos);
355                 } else {
356                     // Wow, what a coincidence, or probably the clock is too coarse.
357                     assignLogId(curEvent);
358                     mPendingLogs.add(curEvent);
359                     if (DEBUG) Slog.d(TAG, "Event timestamp collision: " + lastNanos);
360                 }
361                 lastPos++;
362                 curPos++;
363             }
364         }
365         // Assign an id to the new logs, after the overlap with mLastEvents.
366         List<SecurityEvent> idLogs = newLogs.subList(curPos, newLogs.size());
367         for (SecurityEvent event : idLogs) {
368             assignLogId(event);
369         }
370         // Save the rest of the new batch.
371         mPendingLogs.addAll(idLogs);
372 
373         checkCriticalLevel();
374 
375         if (mPendingLogs.size() > BUFFER_ENTRIES_MAXIMUM_LEVEL) {
376             // Truncate buffer down to half of BUFFER_ENTRIES_MAXIMUM_LEVEL.
377             mPendingLogs = new ArrayList<>(mPendingLogs.subList(
378                     mPendingLogs.size() - (BUFFER_ENTRIES_MAXIMUM_LEVEL / 2),
379                     mPendingLogs.size()));
380             mCriticalLevelLogged = false;
381             Slog.i(TAG, "Pending logs buffer full. Discarding old logs.");
382         }
383         if (DEBUG) Slog.d(TAG, mPendingLogs.size() + " pending events in the buffer after merging,"
384                 + " with ids " + mPendingLogs.get(0).getId()
385                 + " to " + mPendingLogs.get(mPendingLogs.size() - 1).getId());
386     }
387 
388     @GuardedBy("mLock")
checkCriticalLevel()389     private void checkCriticalLevel() {
390         if (!SecurityLog.isLoggingEnabled()) {
391             return;
392         }
393 
394         if (mPendingLogs.size() >= BUFFER_ENTRIES_CRITICAL_LEVEL) {
395             if (!mCriticalLevelLogged) {
396                 mCriticalLevelLogged = true;
397                 SecurityLog.writeEvent(SecurityLog.TAG_LOG_BUFFER_SIZE_CRITICAL);
398             }
399         }
400     }
401 
402     @GuardedBy("mLock")
assignLogId(SecurityEvent event)403     private void assignLogId(SecurityEvent event) {
404         event.setId(mId);
405         if (mId == Long.MAX_VALUE) {
406             Slog.i(TAG, "Reached maximum id value; wrapping around.");
407             mId = 0;
408         } else {
409             mId++;
410         }
411     }
412 
413     @Override
run()414     public void run() {
415         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
416 
417         final ArrayList<SecurityEvent> newLogs = new ArrayList<>();
418         while (!Thread.currentThread().isInterrupted()) {
419             try {
420                 final boolean force = mForceSemaphore.tryAcquire(POLLING_INTERVAL_MS, MILLISECONDS);
421 
422                 getNextBatch(newLogs);
423 
424                 mLock.lockInterruptibly();
425                 try {
426                     mergeBatchLocked(newLogs);
427                 } finally {
428                     mLock.unlock();
429                 }
430 
431                 saveLastEvents(newLogs);
432                 newLogs.clear();
433                 notifyDeviceOwnerIfNeeded(force);
434             } catch (IOException e) {
435                 Log.e(TAG, "Failed to read security log", e);
436             } catch (InterruptedException e) {
437                 Log.i(TAG, "Thread interrupted, exiting.", e);
438                 // We are asked to stop.
439                 break;
440             }
441         }
442 
443         // Discard previous batch info.
444         mLastEvents.clear();
445         if (mLastEventNanos != -1) {
446             // Make sure we don't read old events if logging is re-enabled. Since mLastEvents is
447             // empty, the next request will be done without overlap, so it is enough to add 1 ns.
448             mLastEventNanos += 1;
449         }
450 
451         Slog.i(TAG, "MonitorThread exit.");
452     }
453 
notifyDeviceOwnerIfNeeded(boolean force)454     private void notifyDeviceOwnerIfNeeded(boolean force) throws InterruptedException {
455         boolean allowRetrievalAndNotifyDO = false;
456         mLock.lockInterruptibly();
457         try {
458             if (mPaused) {
459                 return;
460             }
461             final int logSize = mPendingLogs.size();
462             if (logSize >= BUFFER_ENTRIES_NOTIFICATION_LEVEL || (force && logSize > 0)) {
463                 // Allow DO to retrieve logs if too many pending logs or if forced.
464                 if (!mAllowedToRetrieve) {
465                     allowRetrievalAndNotifyDO = true;
466                 }
467                 if (DEBUG) Slog.d(TAG, "Number of log entries over threshold: " + logSize);
468             }
469             if (logSize > 0 && SystemClock.elapsedRealtime() >= mNextAllowedRetrievalTimeMillis) {
470                 // Rate limit reset
471                 allowRetrievalAndNotifyDO = true;
472                 if (DEBUG) Slog.d(TAG, "Timeout reached");
473             }
474             if (allowRetrievalAndNotifyDO) {
475                 mAllowedToRetrieve = true;
476                 // Set the timeout to retry the notification if the DO misses it.
477                 mNextAllowedRetrievalTimeMillis = SystemClock.elapsedRealtime()
478                         + BROADCAST_RETRY_INTERVAL_MS;
479             }
480         } finally {
481             mLock.unlock();
482         }
483         if (allowRetrievalAndNotifyDO) {
484             Slog.i(TAG, "notify DO");
485             mService.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_SECURITY_LOGS_AVAILABLE,
486                     null);
487         }
488     }
489 
490     /**
491      * Forces the logs to be fetched and made available. Returns 0 on success or timeout to wait
492      * before attempting in milliseconds.
493      */
forceLogs()494     public long forceLogs() {
495         final long nowNanos = System.nanoTime();
496         // We only synchronize with another calls to this function, not with the fetching thread.
497         synchronized (mForceSemaphore) {
498             final long toWaitNanos = mLastForceNanos + FORCE_FETCH_THROTTLE_NS - nowNanos;
499             if (toWaitNanos > 0) {
500                 return NANOSECONDS.toMillis(toWaitNanos) + 1; // Round up.
501             }
502             mLastForceNanos = nowNanos;
503             // There is a race condition with the fetching thread below, but if the last permit is
504             // acquired just after we do the check, logs are forced anyway and that's what we need.
505             if (mForceSemaphore.availablePermits() == 0) {
506                 mForceSemaphore.release();
507             }
508             return 0;
509         }
510     }
511 }
512