From c3cd05f8a45ab789aae1cb553df86f94667d595a Mon Sep 17 00:00:00 2001 From: Rubin Xu Date: Mon, 11 Jan 2016 12:11:35 +0000 Subject: [PATCH] Add DevicePolicyManager APIs for process logging. Add Device Owner APIs for controlling and retrieving the logs. Retrieving the logs should be rate limited unless we are at the risk of losing logs due to constrained buffer space. Bug: 22860162 Change-Id: I80658f5a14e86d7cfd42402fbc5e98dc11698c0e --- api/current.txt | 5 + api/system-current.txt | 5 + api/test-current.txt | 5 + .../app/admin/DeviceAdminReceiver.java | 22 ++ .../app/admin/DevicePolicyManager.java | 86 +++++++- .../app/admin/IDevicePolicyManager.aidl | 6 + core/res/AndroidManifest.xml | 1 + .../DevicePolicyManagerService.java | 140 +++++++++++-- .../devicepolicy/SecurityLogMonitor.java | 198 ++++++++++++++++++ 9 files changed, 455 insertions(+), 13 deletions(-) create mode 100644 services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java diff --git a/api/current.txt b/api/current.txt index 0a20a7379ff55..26b7aa99dfd7d 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5783,6 +5783,7 @@ package android.app.admin { method public void onProfileProvisioningComplete(android.content.Context, android.content.Intent); method public deprecated void onReadyForUserInitialization(android.content.Context, android.content.Intent); method public void onReceive(android.content.Context, android.content.Intent); + method public void onSecurityLogsAvailable(android.content.Context, android.content.Intent); method public void onSystemUpdatePending(android.content.Context, android.content.Intent, long); field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED"; field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED"; @@ -5829,6 +5830,7 @@ package android.app.admin { method public boolean getCrossProfileContactsSearchDisabled(android.content.ComponentName); method public java.util.List getCrossProfileWidgetProviders(android.content.ComponentName); method public int getCurrentFailedPasswordAttempts(); + method public boolean getDeviceLoggingEnabled(android.content.ComponentName); method public java.lang.String getDeviceOwnerLockScreenInfo(); method public java.util.List getInstalledCaCerts(android.content.ComponentName); method public int getKeyguardDisabledFeatures(android.content.ComponentName); @@ -5885,6 +5887,8 @@ package android.app.admin { method public boolean removeUser(android.content.ComponentName, android.os.UserHandle); method public boolean requestBugreport(android.content.ComponentName); method public boolean resetPassword(java.lang.String, int); + method public java.util.List retrieveDeviceLogs(android.content.ComponentName); + method public java.util.List retrievePreviousDeviceLogs(android.content.ComponentName); method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean); method public boolean setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String); method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean); @@ -5896,6 +5900,7 @@ package android.app.admin { method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException; method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean); method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean); + method public void setDeviceLoggingEnabled(android.content.ComponentName, boolean); method public boolean setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.String); method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String); method public boolean setKeyguardDisabled(android.content.ComponentName, boolean); diff --git a/api/system-current.txt b/api/system-current.txt index e4ddbdfefea8d..d46decf0cdff4 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5919,6 +5919,7 @@ package android.app.admin { method public void onProfileProvisioningComplete(android.content.Context, android.content.Intent); method public deprecated void onReadyForUserInitialization(android.content.Context, android.content.Intent); method public void onReceive(android.content.Context, android.content.Intent); + method public void onSecurityLogsAvailable(android.content.Context, android.content.Intent); method public void onSystemUpdatePending(android.content.Context, android.content.Intent, long); field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED"; field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED"; @@ -5967,6 +5968,7 @@ package android.app.admin { method public int getCurrentFailedPasswordAttempts(); method public deprecated java.lang.String getDeviceInitializerApp(); method public deprecated android.content.ComponentName getDeviceInitializerComponent(); + method public boolean getDeviceLoggingEnabled(android.content.ComponentName); method public java.lang.String getDeviceOwner(); method public java.lang.String getDeviceOwnerLockScreenInfo(); method public java.lang.String getDeviceOwnerNameOnAnyUser(); @@ -6030,6 +6032,8 @@ package android.app.admin { method public boolean removeUser(android.content.ComponentName, android.os.UserHandle); method public boolean requestBugreport(android.content.ComponentName); method public boolean resetPassword(java.lang.String, int); + method public java.util.List retrieveDeviceLogs(android.content.ComponentName); + method public java.util.List retrievePreviousDeviceLogs(android.content.ComponentName); method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean); method public deprecated boolean setActiveProfileOwner(android.content.ComponentName, java.lang.String) throws java.lang.IllegalArgumentException; method public boolean setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String); @@ -6042,6 +6046,7 @@ package android.app.admin { method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException; method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean); method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean); + method public void setDeviceLoggingEnabled(android.content.ComponentName, boolean); method public boolean setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.String); method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String); method public boolean setKeyguardDisabled(android.content.ComponentName, boolean); diff --git a/api/test-current.txt b/api/test-current.txt index 2348747995454..6d9004daf8ae1 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -5785,6 +5785,7 @@ package android.app.admin { method public void onProfileProvisioningComplete(android.content.Context, android.content.Intent); method public deprecated void onReadyForUserInitialization(android.content.Context, android.content.Intent); method public void onReceive(android.content.Context, android.content.Intent); + method public void onSecurityLogsAvailable(android.content.Context, android.content.Intent); method public void onSystemUpdatePending(android.content.Context, android.content.Intent, long); field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED"; field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED"; @@ -5831,6 +5832,7 @@ package android.app.admin { method public boolean getCrossProfileContactsSearchDisabled(android.content.ComponentName); method public java.util.List getCrossProfileWidgetProviders(android.content.ComponentName); method public int getCurrentFailedPasswordAttempts(); + method public boolean getDeviceLoggingEnabled(android.content.ComponentName); method public java.lang.String getDeviceOwnerLockScreenInfo(); method public java.util.List getInstalledCaCerts(android.content.ComponentName); method public int getKeyguardDisabledFeatures(android.content.ComponentName); @@ -5887,6 +5889,8 @@ package android.app.admin { method public boolean removeUser(android.content.ComponentName, android.os.UserHandle); method public boolean requestBugreport(android.content.ComponentName); method public boolean resetPassword(java.lang.String, int); + method public java.util.List retrieveDeviceLogs(android.content.ComponentName); + method public java.util.List retrievePreviousDeviceLogs(android.content.ComponentName); method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean); method public boolean setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String); method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean); @@ -5898,6 +5902,7 @@ package android.app.admin { method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException; method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean); method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean); + method public void setDeviceLoggingEnabled(android.content.ComponentName, boolean); method public boolean setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.String); method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String); method public boolean setKeyguardDisabled(android.content.ComponentName, boolean); diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index 3c1ecc79c8882..34dfb26ba33e9 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -265,6 +265,14 @@ public class DeviceAdminReceiver extends BroadcastReceiver { public static final String ACTION_BUGREPORT_SHARE = "android.app.action.BUGREPORT_SHARE"; + /** + * Broadcast action: notify that a new batch of device logs is ready to be collected. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_SECURITY_LOGS_AVAILABLE + = "android.app.action.SECURITY_LOGS_AVAILABLE"; + /** * A string containing the SHA-256 hash of the bugreport file. * @@ -595,6 +603,18 @@ public class DeviceAdminReceiver extends BroadcastReceiver { @BugreportFailureCode int failureCode) { } + /** + * Called when a new batch of device logs can be retrieved. + * + *

This callback is only applicable to device owners. + * + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @see DevicePolicyManager#retrieveDeviceLogs(ComponentName) + */ + public void onSecurityLogsAvailable(Context context, Intent intent) { + } + /** * Intercept standard device administrator broadcasts. Implementations * should not override this method; it is better to implement the @@ -647,6 +667,8 @@ public class DeviceAdminReceiver extends BroadcastReceiver { int failureCode = intent.getIntExtra(EXTRA_BUGREPORT_FAILURE_REASON, BUGREPORT_FAILURE_FAILED_COMPLETING); onBugreportFailed(context, intent, failureCode); + } else if (ACTION_SECURITY_LOGS_AVAILABLE.equals(action)) { + onSecurityLogsAvailable(context, intent); } } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index c79c4072e7452..f04e76e22074a 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -23,12 +23,15 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.app.Activity; +import android.auditing.SecurityLog; +import android.auditing.SecurityLog.SecurityEvent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.graphics.Bitmap; @@ -2210,7 +2213,6 @@ public class DevicePolicyManager { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION"; - /** * Widgets are enabled in keyguard */ @@ -5352,6 +5354,66 @@ public class DevicePolicyManager { throw new SecurityException("The current user does not have a parent profile."); } return new DevicePolicyManager(mContext, true); + } catch (RemoteException e) { + Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e); + return null; + } + } + + /** + * Called by device owner to control the device logging feature. Logging can only be + * enabled on single user devices where the sole user is managed by the device owner. + * + *

Device logs contain various information intended for security auditing purposes. + * See {@link SecurityEvent} for details. + * + * @param admin Which device owner this request is associated with. + * @param enabled whether device logging should be enabled or not. + * @see #retrieveDeviceLogs + */ + public void setDeviceLoggingEnabled(@NonNull ComponentName admin, boolean enabled) { + try { + mService.setDeviceLoggingEnabled(admin, enabled); + } catch (RemoteException re) { + Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re); + } + } + + /** + * Return whether device logging is enabled or not by the device owner. + * + * @param admin Which device owner this request is associated with. + * @return {@code true} if device logging is enabled by device owner, {@code false} otherwise. + */ + public boolean getDeviceLoggingEnabled(@NonNull ComponentName admin) { + try { + return mService.getDeviceLoggingEnabled(admin); + } catch (RemoteException re) { + Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re); + return false; + } + } + + /** + * Called by device owner to retrieve all new device logging entries since the last call to + * this API after device boots. + * + *

Access to the logs is rate limited and it will only return new logs after the device + * owner has been notified via {@link DeviceAdminReceiver#onSecurityLogsAvailable}. + * + * @param admin Which device owner this request is associated with. + * @return the new batch of device logs which is a list of {@link SecurityEvent}, + * or {@code null} if rate limitation is exceeded or if logging is currently disabled. + */ + public List retrieveDeviceLogs(@NonNull ComponentName admin) { + try { + ParceledListSlice list = mService.retrieveDeviceLogs(admin); + if (list != null) { + return list.getList(); + } else { + // Rate limit exceeded. + return null; + } } catch (RemoteException re) { Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re); return null; @@ -5374,6 +5436,28 @@ public class DevicePolicyManager { return new DevicePolicyManager(mContext, true); } + /** + * Called by device owners to retrieve device logs from before the device's last reboot. + * + *

+ * The device logs are retrieved from a RAM region which is not guaranteed to be + * corruption-free during power cycles, due to hardware variations and limitations. As a + * result, this API is provided as best-effort and the returned logs may contain corrupted data. + * + * + * @param admin Which device owner this request is associated with. + * @return Device logs from before the latest reboot of the system. + */ + public List retrievePreviousDeviceLogs(@NonNull ComponentName admin) { + try { + ParceledListSlice list = mService.retrievePreviousDeviceLogs(admin); + return list.getList(); + } catch (RemoteException re) { + Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re); + return Collections.emptyList(); + } + } + /** * Called by a profile owner of a managed profile to set the color used for customization. * This color is used as background color of the confirm credentials screen for that user. diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 37d13e54b8266..b57e1b7d80816 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -21,6 +21,7 @@ import android.app.admin.SystemUpdatePolicy; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ParceledListSlice; import android.graphics.Bitmap; import android.net.ProxyInfo; import android.net.Uri; @@ -285,4 +286,9 @@ interface IDevicePolicyManager { void setAffiliationIds(in ComponentName admin, in List ids); boolean isAffiliatedUser(); + + void setDeviceLoggingEnabled(in ComponentName admin, boolean enabled); + boolean getDeviceLoggingEnabled(in ComponentName admin); + ParceledListSlice retrieveDeviceLogs(in ComponentName admin); + ParceledListSlice retrievePreviousDeviceLogs(in ComponentName admin); } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index a04146f5e4d77..3ef81ba011b18 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -408,6 +408,7 @@ + diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 7831c4d3a7fd5..83aa64ecad65a 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -50,6 +50,7 @@ import android.app.admin.IDevicePolicyManager; import android.app.admin.SystemUpdatePolicy; import android.app.backup.IBackupManager; import android.auditing.SecurityLog; +import android.auditing.SecurityLog.SecurityEvent; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -63,6 +64,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; +import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; @@ -210,6 +212,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String ATTR_APPLICATION_RESTRICTIONS_MANAGER = "application-restrictions-manager"; + /** + * System property whose value is either "true" or "false", indicating whether + */ + private static final String PROPERTY_DEVICE_OWNER_PRESENT = "ro.device_owner"; + private static final int STATUS_BAR_DISABLE_MASK = StatusBarManager.DISABLE_EXPAND | StatusBarManager.DISABLE_NOTIFICATION_ICONS | @@ -287,6 +294,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { */ private boolean mHasFeature; + private final SecurityLogMonitor mSecurityLogMonitor; + private final AtomicBoolean mRemoteBugreportServiceIsActive = new AtomicBoolean(); private final AtomicBoolean mRemoteBugreportSharingAccepted = new AtomicBoolean(); @@ -438,7 +447,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || KeyChain.ACTION_STORAGE_CHANGED.equals(action)) { new MonitoringCertNotificationTask().execute(intent); } - if (Intent.ACTION_USER_REMOVED.equals(action)) { + if (Intent.ACTION_USER_ADDED.equals(action)) { + disableDeviceLoggingIfNotCompliant(); + } else if (Intent.ACTION_USER_REMOVED.equals(action)) { + disableDeviceLoggingIfNotCompliant(); removeUserData(userHandle); } else if (Intent.ACTION_USER_STARTED.equals(action)) { synchronized (DevicePolicyManagerService.this) { @@ -1470,6 +1482,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mLocalService = new LocalService(); mLockPatternUtils = injector.newLockPatternUtils(); + mSecurityLogMonitor = new SecurityLogMonitor(this); + mHasFeature = mContext.getPackageManager() .hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN); if (!mHasFeature) { @@ -1479,6 +1493,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BOOT_COMPLETED); filter.addAction(ACTION_EXPIRED_PASSWORD_NOTIFICATION); + filter.addAction(Intent.ACTION_USER_ADDED); filter.addAction(Intent.ACTION_USER_REMOVED); filter.addAction(Intent.ACTION_USER_STARTED); filter.addAction(KeyChain.ACTION_STORAGE_CHANGED); @@ -1559,6 +1574,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { void loadOwners() { synchronized (this) { mOwners.load(); + setDeviceOwnerSystemPropertyLocked(); findOwnerComponentIfNecessaryLocked(); migrateUserRestrictionsIfNecessaryLocked(); @@ -1568,6 +1584,34 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + private void setDeviceOwnerSystemPropertyLocked() { + // Device owner may still be provisioned, do not set the read-only system property yet. + if (mInjector.settingsGlobalGetInt(Settings.Global.DEVICE_PROVISIONED, 0) == 0) { + return; + } + // Still at the first stage of CryptKeeper double bounce, mOwners.hasDeviceOwner is + // always false at this point. + if ("encrypted".equals(mInjector.systemPropertiesGet("ro.crypto.state")) + && "trigger_restart_min_framework".equals( + mInjector.systemPropertiesGet("vold.decrypt"))){ + return; + } + + if (!TextUtils.isEmpty(mInjector.systemPropertiesGet(PROPERTY_DEVICE_OWNER_PRESENT))) { + Slog.wtf(LOG_TAG, "Trying to set ro.device_owner, but it has already been set?"); + } else { + if (mOwners.hasDeviceOwner()) { + mInjector.systemPropertiesSet(PROPERTY_DEVICE_OWNER_PRESENT, "true"); + disableDeviceLoggingIfNotCompliant(); + if (SecurityLog.getLoggingEnabledProperty()) { + mSecurityLogMonitor.start(); + } + } else { + mInjector.systemPropertiesSet(PROPERTY_DEVICE_OWNER_PRESENT, "false"); + } + } + } + private void findOwnerComponentIfNecessaryLocked() { if (!mOwners.hasDeviceOwner()) { return; @@ -4897,29 +4941,37 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - private void ensureDeviceOwnerManagingSingleUser(ComponentName who) throws SecurityException { + private boolean isDeviceOwnerManagedSingleUserDevice() { synchronized (this) { - getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + if (!mOwners.hasDeviceOwner()) { + return false; + } } final long callingIdentity = mInjector.binderClearCallingIdentity(); try { if (mInjector.userManagerIsSplitSystemUser()) { // In split system user mode, only allow the case where the device owner is managing // the only non-system user of the device - if (mUserManager.getUserCount() > 2 - || mOwners.getDeviceOwnerUserId() == UserHandle.USER_SYSTEM) { - throw new SecurityException( - "There should only be one user, managed by Device Owner"); - } - } else if (mUserManager.getUserCount() > 1) { - throw new SecurityException( - "There should only be one user, managed by Device Owner"); + return (mUserManager.getUserCount() == 2 + && mOwners.getDeviceOwnerUserId() != UserHandle.USER_SYSTEM); + } else { + return mUserManager.getUserCount() == 1; } } finally { mInjector.binderRestoreCallingIdentity(callingIdentity); } } + private void ensureDeviceOwnerManagingSingleUser(ComponentName who) throws SecurityException { + synchronized (this) { + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + } + if (!isDeviceOwnerManagedSingleUserDevice()) { + throw new SecurityException( + "There should only be one user, managed by Device Owner"); + } + } + @Override public boolean requestBugreport(ComponentName who) { if (!mHasFeature) { @@ -4961,7 +5013,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - private synchronized void sendDeviceOwnerCommand(String action, Bundle extras) { + synchronized void sendDeviceOwnerCommand(String action, Bundle extras) { Intent intent = new Intent(action); intent.setComponent(mOwners.getDeviceOwnerComponent()); if (extras != null) { @@ -5305,6 +5357,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mOwners.setDeviceOwner(admin, ownerName, userId); mOwners.writeDeviceOwner(); updateDeviceOwnerLocked(); + setDeviceOwnerSystemPropertyLocked(); Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED); ident = mInjector.binderClearCallingIdentity(); @@ -5436,6 +5489,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mOwners.clearDeviceOwner(); mOwners.writeDeviceOwner(); updateDeviceOwnerLocked(); + disableDeviceLoggingIfNotCompliant(); // Reactivate backup service. long ident = mInjector.binderClearCallingIdentity(); try { @@ -7561,6 +7615,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private final Uri mUserSetupComplete = Settings.Secure.getUriFor( Settings.Secure.USER_SETUP_COMPLETE); + private final Uri mDeviceProvisioned = Settings.Global.getUriFor( + Settings.Global.DEVICE_PROVISIONED); public SetupContentObserver(Handler handler) { super(handler); @@ -7568,12 +7624,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { void register(ContentResolver resolver) { resolver.registerContentObserver(mUserSetupComplete, false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(mDeviceProvisioned, false, this, UserHandle.USER_ALL); } @Override public void onChange(boolean selfChange, Uri uri) { if (mUserSetupComplete.equals(uri)) { updateUserSetupComplete(); + } else if (mDeviceProvisioned.equals(uri)) { + // Set PROPERTY_DEVICE_OWNER_PRESENT, for the SUW case where setting the property + // is delayed until device is marked as provisioned. + setDeviceOwnerSystemPropertyLocked(); } } } @@ -8253,4 +8314,59 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } return false; } + + private void disableDeviceLoggingIfNotCompliant() { + if (!isDeviceOwnerManagedSingleUserDevice()) { + SecurityLog.setLoggingEnabledProperty(false); + Slog.w(LOG_TAG, "Device logging turned off as it's no longer a single user device."); + } + } + + @Override + public void setDeviceLoggingEnabled(ComponentName admin, boolean enabled) { + Preconditions.checkNotNull(admin); + ensureDeviceOwnerManagingSingleUser(admin); + + synchronized (this) { + SecurityLog.setLoggingEnabledProperty(enabled); + if (enabled) { + mSecurityLogMonitor.start(); + } else { + mSecurityLogMonitor.stop(); + } + } + } + + @Override + public boolean getDeviceLoggingEnabled(ComponentName admin) { + Preconditions.checkNotNull(admin); + synchronized (this) { + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + return SecurityLog.getLoggingEnabledProperty(); + } + } + + @Override + public ParceledListSlice retrievePreviousDeviceLogs(ComponentName admin) { + Preconditions.checkNotNull(admin); + ensureDeviceOwnerManagingSingleUser(admin); + + ArrayList output = new ArrayList(); + try { + SecurityLog.readPreviousEvents(output); + return new ParceledListSlice(output); + } catch (IOException e) { + Slog.w(LOG_TAG, "Fail to read previous events" , e); + return new ParceledListSlice(Collections.emptyList()); + } + } + + @Override + public ParceledListSlice retrieveDeviceLogs(ComponentName admin) { + Preconditions.checkNotNull(admin); + ensureDeviceOwnerManagingSingleUser(admin); + + List logs = mSecurityLogMonitor.retrieveLogs(); + return logs != null ? new ParceledListSlice(logs) : null; + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java new file mode 100644 index 0000000000000..f2d6180d34a37 --- /dev/null +++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java @@ -0,0 +1,198 @@ +/* + * 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.auditing.SecurityLog; +import android.auditing.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 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; + + 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("this") + private Thread mMonitorThread = null; + @GuardedBy("this") + private ArrayList mPendingLogs = new ArrayList(); + @GuardedBy("this") + private boolean mAllowedToRetrieve = false; + // When DO will be allowed to retrieves the log, in milliseconds. + @GuardedBy("this") + private long mNextAllowedRetrivalTimeMillis = -1; + + synchronized void start() { + if (mMonitorThread == null) { + mPendingLogs = new ArrayList(); + mAllowedToRetrieve = false; + mNextAllowedRetrivalTimeMillis = -1; + + mMonitorThread = new Thread(this); + mMonitorThread.start(); + } + } + + synchronized void stop() { + 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); + } + mMonitorThread = null; + } + } + + /** + * Returns the new batch of logs since the last call to this method. Returns null if + * rate limit is exceeded. + */ + synchronized List retrieveLogs() { + if (mAllowedToRetrieve) { + mAllowedToRetrieve = false; + mNextAllowedRetrivalTimeMillis = System.currentTimeMillis() + + RATE_LIMIT_INTERVAL_MILLISECONDS; + List result = mPendingLogs; + mPendingLogs = new ArrayList(); + return result; + } else { + return null; + } + } + + @Override + public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + + ArrayList logs = new ArrayList(); + // 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"); + synchronized (this) { + mPendingLogs.addAll(logs); + if (mPendingLogs.size() > BUFFER_ENTRIES_MAXIMUM_LEVEL) { + // Truncate buffer down to half of BUFFER_ENTRIES_MAXIMUM_LEVEL + mPendingLogs = new ArrayList(mPendingLogs.subList( + mPendingLogs.size() - (BUFFER_ENTRIES_MAXIMUM_LEVEL / 2), + mPendingLogs.size())); + } + } + 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."); + synchronized (this) { + // Reset state and clear buffer + mPendingLogs = new ArrayList(); + mAllowedToRetrieve = false; + mNextAllowedRetrivalTimeMillis = -1; + } + } + + private void notifyDeviceOwnerIfNeeded() { + boolean shouldNotifyDO = false; + boolean allowToRetrieveNow = false; + synchronized (this) { + 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; + } + if (shouldNotifyDO) { + if (DEBUG) Slog.d(TAG, "notify DO"); + mService.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_SECURITY_LOGS_AVAILABLE, + null); + } + } +} \ No newline at end of file