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