diff options
| author | 2024-02-02 14:04:52 +0000 | |
|---|---|---|
| committer | 2024-02-15 02:02:32 +0000 | |
| commit | 9b5e9d8436924b81eff9afb95104d59c780dd772 (patch) | |
| tree | a8aae9013c7ad17cbec3c3a701e2f8ade27d6cb1 | |
| parent | 13f9d65c456ee75c488b5d5eccdaa277437528ef (diff) | |
Add audit logging API
Bug: 295324350
Test: atest SecurityLoggingTest
Change-Id: Ie4abb9a5995930a5b049db5a04783a8531408729
13 files changed, 549 insertions, 56 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 340a79d0228d..409b7b7f9fae 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -195,6 +195,7 @@ package android { field public static final String MANAGE_DEFAULT_APPLICATIONS = "android.permission.MANAGE_DEFAULT_APPLICATIONS"; field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS"; field public static final String MANAGE_DEVICE_POLICY_APP_EXEMPTIONS = "android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS"; + field @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") public static final String MANAGE_DEVICE_POLICY_AUDIT_LOGGING = "android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING"; field @FlaggedApi("android.app.admin.flags.device_theft_api_enabled") public static final String MANAGE_DEVICE_POLICY_THEFT_DETECTION = "android.permission.MANAGE_DEVICE_POLICY_THEFT_DETECTION"; field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String MANAGE_ENHANCED_CONFIRMATION_STATES = "android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES"; field public static final String MANAGE_ETHERNET_NETWORKS = "android.permission.MANAGE_ETHERNET_NETWORKS"; @@ -1288,6 +1289,10 @@ package android.app.admin { field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DevicePolicyDrawableResource> CREATOR; } + public final class DevicePolicyIdentifiers { + field @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") public static final String AUDIT_LOGGING_POLICY = "auditLogging"; + } + public class DevicePolicyKeyguardService extends android.app.Service { ctor public DevicePolicyKeyguardService(); method @Nullable public void dismiss(); @@ -1316,6 +1321,7 @@ package android.app.admin { method @Nullable public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException; method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException; method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public int getUserProvisioningState(); + method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public boolean isAuditLogEnabled(); method public boolean isDeviceManaged(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioningConfigApplied(); @@ -1331,6 +1337,8 @@ package android.app.admin { method @RequiresPermission(android.Manifest.permission.TRIGGER_LOST_MODE) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException; method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public void setApplicationExemptions(@NonNull String, @NonNull java.util.Set<java.lang.Integer>) throws android.content.pm.PackageManager.NameNotFoundException; + method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEnabled(boolean); + method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied(); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean); method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setMaxPolicyStorageLimit(int); diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java index a884ab0c4099..3c56aaf33ef3 100644 --- a/core/java/android/app/admin/DevicePolicyIdentifiers.java +++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java @@ -20,6 +20,7 @@ import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_ENABLED; import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.admin.flags.Flags; import android.os.UserManager; @@ -53,6 +54,15 @@ public final class DevicePolicyIdentifiers { public static final String SECURITY_LOGGING_POLICY = "securityLogging"; /** + * String identifier for {@link DevicePolicyManager#setAuditLogEnabled}. + * + * @hide + */ + @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED) + @SystemApi + public static final String AUDIT_LOGGING_POLICY = "auditLogging"; + + /** * String identifier for {@link DevicePolicyManager#setLockTaskPackages}. */ public static final String LOCK_TASK_POLICY = "lockTask"; diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 367ade0e29fe..a6fda9d23aca 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -55,6 +55,7 @@ import static android.Manifest.permission.SET_TIME; import static android.Manifest.permission.SET_TIME_ZONE; import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED; import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED; +import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_ENABLED; import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled; import static android.app.admin.flags.Flags.FLAG_IS_MTE_POLICY_ENFORCED; import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM; @@ -233,7 +234,6 @@ public class DevicePolicyManager { private final boolean mParentInstance; private final DevicePolicyResourcesManager mResourcesManager; - /** @hide */ public DevicePolicyManager(Context context, IDevicePolicyManager service) { this(context, service, false); @@ -14059,6 +14059,74 @@ public class DevicePolicyManager { } /** + * Controls whether audit logging is enabled. + * + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED) + @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) + public void setAuditLogEnabled(boolean enabled) { + throwIfParentInstance("setAuditLogEnabled"); + try { + mService.setAuditLogEnabled(mContext.getPackageName(), true); + } catch (RemoteException re) { + re.rethrowFromSystemServer(); + } + } + + /** + * @return Whether audit logging is enabled. + * + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED) + @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) + public boolean isAuditLogEnabled() { + throwIfParentInstance("isAuditLogEnabled"); + try { + return mService.isAuditLogEnabled(mContext.getPackageName()); + } catch (RemoteException re) { + re.rethrowFromSystemServer(); + // unreachable + return false; + } + } + + /** + * Sets audit log event callback. Only one callback per UID is active at any time, when a new + * callback is set, the previous one is forgotten. Should only be called when audit log policy + * is enforced by the caller. Disabling the policy clears the callback. Each time a new callback + * is set, it will first be invoked with all the audit log events available at the time. + * + * @param callback callback to invoke when new audit log events become available or {@code null} + * to clear the callback. + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED) + @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) + public void setAuditLogEventCallback( + @NonNull @CallbackExecutor Executor executor, + @Nullable Consumer<List<SecurityEvent>> callback) { + throwIfParentInstance("setAuditLogEventCallback"); + final IAuditLogEventsCallback wrappedCallback = callback == null + ? null + : new IAuditLogEventsCallback.Stub() { + @Override + public void onNewAuditLogEvents(List<SecurityEvent> events) { + executor.execute(() -> callback.accept(events)); + } + }; + try { + mService.setAuditLogEventsCallback(mContext.getPackageName(), wrappedCallback); + } catch (RemoteException re) { + re.rethrowFromSystemServer(); + } + } + + /** * Called by device owner or profile owner of an organization-owned managed profile to retrieve * all new security logging entries since the last call to this API after device boots. * diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index 07ee8de587f8..1aee9fe57466 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -338,4 +338,9 @@ public abstract class DevicePolicyManagerInternal { * Enforces resolved security logging policy, should only be invoked from device policy engine. */ public abstract void enforceSecurityLoggingPolicy(boolean enabled); + + /** + * Enforces resolved audit logging policy, should only be invoked from device policy engine. + */ + public abstract void enforceAuditLoggingPolicy(boolean enabled); } diff --git a/core/java/android/app/admin/IAuditLogEventsCallback.aidl b/core/java/android/app/admin/IAuditLogEventsCallback.aidl new file mode 100644 index 000000000000..ab871178a9b9 --- /dev/null +++ b/core/java/android/app/admin/IAuditLogEventsCallback.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 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 android.app.admin; + +import android.app.admin.SecurityLog; + +/** @hide */ +oneway interface IAuditLogEventsCallback { + void onNewAuditLogEvents(in List<SecurityLog.SecurityEvent> events); +}
\ No newline at end of file diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index ca4a5abb2961..3a7a891c7995 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -32,6 +32,7 @@ import android.app.admin.SystemUpdatePolicy; import android.app.admin.PackagePolicy; import android.app.admin.PasswordMetrics; import android.app.admin.FactoryResetProtectionPolicy; +import android.app.admin.IAuditLogEventsCallback; import android.app.admin.ManagedProfileProvisioningParams; import android.app.admin.FullyManagedDeviceProvisioningParams; import android.app.admin.ManagedSubscriptionsPolicy; @@ -441,6 +442,10 @@ interface IDevicePolicyManager { long forceNetworkLogs(); long forceSecurityLogs(); + void setAuditLogEnabled(String callerPackage, boolean enabled); + boolean isAuditLogEnabled(String callerPackage); + void setAuditLogEventsCallback(String callerPackage, in IAuditLogEventsCallback callback); + boolean isUninstallInQueue(String packageName); void uninstallPackageWithActiveAdmins(String packageName); diff --git a/core/java/android/app/admin/SecurityLog.aidl b/core/java/android/app/admin/SecurityLog.aidl new file mode 100644 index 000000000000..e5ae2df8c21b --- /dev/null +++ b/core/java/android/app/admin/SecurityLog.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2024 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 android.app.admin; + +/** @hide */ +parcelable SecurityLog.SecurityEvent;
\ No newline at end of file diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 4131644466c6..71f06f1106de 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3690,6 +3690,14 @@ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_SECURITY_LOGGING" android:protectionLevel="internal|role" /> + <!-- Allows an application to use audit logging API. + @hide + @SystemApi + @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") + --> + <permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING" + android:protectionLevel="internal|role" /> + <!-- Allows an application to set policy related to system updates. <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call APIs protected by this permission on users different to the calling user. diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 4305d912e806..53f2caf0b793 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -771,6 +771,9 @@ <!-- Permission required for CTS test - CtsDevicePolicyManagerTestCases --> <uses-permission android:name="android.permission.READ_NEARBY_STREAMING_POLICY" /> + <!-- Permission required for CTS test - CtsDevicePolicyTestCases --> + <uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING" /> + <!-- Permission required for CTS test - CtsKeystoreTestCases --> <uses-permission android:name="android.permission.REQUEST_UNIQUE_ID_ATTESTATION" /> diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 9b84f395af5e..58e198e532cd 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -28,6 +28,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AIRPLANE_MODE; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_RESTRICTIONS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIO_OUTPUT; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUTOFILL; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_BLUETOOTH; @@ -332,6 +333,7 @@ import android.app.admin.DevicePolicyStringResource; import android.app.admin.DeviceStateCache; import android.app.admin.FactoryResetProtectionPolicy; import android.app.admin.FullyManagedDeviceProvisioningParams; +import android.app.admin.IAuditLogEventsCallback; import android.app.admin.IDevicePolicyManager; import android.app.admin.IntegerPolicyValue; import android.app.admin.IntentFilterPolicyKey; @@ -2064,7 +2066,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mLockPatternUtils = injector.newLockPatternUtils(); mLockSettingsInternal = injector.getLockSettingsInternal(); // TODO: why does SecurityLogMonitor need to be created even when mHasFeature == false? - mSecurityLogMonitor = new SecurityLogMonitor(this); + mSecurityLogMonitor = new SecurityLogMonitor(this, mHandler); mHasFeature = mInjector.hasFeature(); mIsWatch = mInjector.getPackageManager() @@ -2722,8 +2724,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private void maybeStartSecurityLogMonitorOnActivityManagerReady() { - synchronized (getLockObject()) { - if (mInjector.securityLogIsLoggingEnabled()) { + if (!mInjector.securityLogIsLoggingEnabled()) { + return; + } + + if (securityLogV2Enabled()) { + boolean auditLoggingEnabled = Boolean.TRUE.equals( + mDevicePolicyEngine.getResolvedPolicy( + PolicyDefinition.AUDIT_LOGGING, UserHandle.USER_ALL)); + boolean securityLoggingEnabled = Boolean.TRUE.equals( + mDevicePolicyEngine.getResolvedPolicy( + PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL)); + setLoggingConfiguration(securityLoggingEnabled, auditLoggingEnabled); + } else { + synchronized (getLockObject()) { mSecurityLogMonitor.start(getSecurityLoggingEnabledUser()); mInjector.runCryptoSelfTest(); maybePauseDeviceWideLoggingLocked(); @@ -15784,7 +15798,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void enforceSecurityLoggingPolicy(boolean enabled) { - enforceLoggingPolicy(enabled); + if (!securityLogV2Enabled()) { + return; + } + Boolean auditLoggingEnabled = mDevicePolicyEngine.getResolvedPolicy( + PolicyDefinition.AUDIT_LOGGING, UserHandle.USER_ALL); + enforceLoggingPolicy(enabled, Boolean.TRUE.equals(auditLoggingEnabled)); + } + + @Override + public void enforceAuditLoggingPolicy(boolean enabled) { + if (!securityLogV2Enabled()) { + return; + } + Boolean securityLoggingEnabled = mDevicePolicyEngine.getResolvedPolicy( + PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL); + enforceLoggingPolicy(Boolean.TRUE.equals(securityLoggingEnabled), enabled); } private List<EnforcingUser> getEnforcingUsers(Set<EnforcingAdmin> admins) { @@ -15804,17 +15833,23 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - private void enforceLoggingPolicy(boolean securityLoggingEnabled) { - Slogf.i(LOG_TAG, "Enforcing security logging, securityLoggingEnabled: %b", - securityLoggingEnabled); - SecurityLog.setLoggingEnabledProperty(securityLoggingEnabled); - if (securityLoggingEnabled) { - mSecurityLogMonitor.start(getSecurityLoggingEnabledUser()); + private void enforceLoggingPolicy( + boolean securityLoggingEnabled, boolean auditLoggingEnabled) { + Slogf.i(LOG_TAG, "Enforcing logging policy, security: %b audit: %b", + securityLoggingEnabled, auditLoggingEnabled); + SecurityLog.setLoggingEnabledProperty(securityLoggingEnabled || auditLoggingEnabled); + setLoggingConfiguration(securityLoggingEnabled, auditLoggingEnabled); + } + + private void setLoggingConfiguration( + boolean securityLoggingEnabled, boolean auditLoggingEnabled) { + final int loggingEnabledUser = getSecurityLoggingEnabledUser(); + mSecurityLogMonitor.setLoggingParams( + loggingEnabledUser, securityLoggingEnabled, auditLoggingEnabled); + if (securityLoggingEnabled || auditLoggingEnabled) { synchronized (getLockObject()) { maybePauseDeviceWideLoggingLocked(); } - } else { - mSecurityLogMonitor.stop(); } } @@ -17895,6 +17930,82 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override + public void setAuditLogEnabled(String callingPackage, boolean enabled) { + if (!mHasFeature) { + return; + } + final CallerIdentity caller = getCallerIdentity(callingPackage); + + if (!securityLogV2Enabled()) { + throw new UnsupportedOperationException("Audit log not enabled"); + } + + EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin( + null /* admin */, + MANAGE_DEVICE_POLICY_AUDIT_LOGGING, + caller.getPackageName(), + caller.getUserId()); + if (enabled) { + mDevicePolicyEngine.setGlobalPolicy( + PolicyDefinition.AUDIT_LOGGING, + admin, + new BooleanPolicyValue(true)); + } else { + mDevicePolicyEngine.removeGlobalPolicy( + PolicyDefinition.AUDIT_LOGGING, + admin); + mSecurityLogMonitor.setAuditLogEventsCallback(caller.getUid(), null /* callback */); + } + } + + @Override + public boolean isAuditLogEnabled(String callingPackage) { + if (!mHasFeature) { + return false; + } + + if (!securityLogV2Enabled()) { + throw new UnsupportedOperationException("Audit log not enabled"); + } + + final CallerIdentity caller = getCallerIdentity(callingPackage); + EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin( + null /* admin */, + MANAGE_DEVICE_POLICY_AUDIT_LOGGING, + caller.getPackageName(), + caller.getUserId()); + + Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin( + PolicyDefinition.AUDIT_LOGGING, admin); + + return Boolean.TRUE.equals(policy); + } + + @Override + public void setAuditLogEventsCallback(String callingPackage, IAuditLogEventsCallback callback) { + if (!mHasFeature) { + return; + } + + final CallerIdentity caller = getCallerIdentity(callingPackage); + EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin( + null /* admin */, + MANAGE_DEVICE_POLICY_AUDIT_LOGGING, + caller.getPackageName(), + caller.getUserId()); + + Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin( + PolicyDefinition.AUDIT_LOGGING, admin); + + if (!Boolean.TRUE.equals(policy)) { + throw new IllegalStateException( + "Managing app has to enable audit log before setting events callback"); + } + + mSecurityLogMonitor.setAuditLogEventsCallback(caller.getUid(), callback); + } + + @Override public long forceSecurityLogs() { Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()) || hasCallingOrSelfPermission(permission.FORCE_DEVICE_POLICY_MANAGER_LOGS), diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index 3474db3c7c1f..1247f900260a 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -141,6 +141,13 @@ final class PolicyDefinition<V> { PolicyEnforcerCallbacks::enforceSecurityLogging, new BooleanPolicySerializer()); + static PolicyDefinition<Boolean> AUDIT_LOGGING = new PolicyDefinition<>( + new NoArgsPolicyKey(DevicePolicyIdentifiers.AUDIT_LOGGING_POLICY), + TRUE_MORE_RESTRICTIVE, + POLICY_FLAG_GLOBAL_ONLY_POLICY, + PolicyEnforcerCallbacks::enforceAuditLogging, + new BooleanPolicySerializer()); + static PolicyDefinition<LockTaskPolicy> LOCK_TASK = new PolicyDefinition<>( new NoArgsPolicyKey(DevicePolicyIdentifiers.LOCK_TASK_POLICY), new TopPriority<>(List.of( @@ -365,6 +372,8 @@ final class PolicyDefinition<V> { GENERIC_PERMISSION_GRANT); POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.SECURITY_LOGGING_POLICY, SECURITY_LOGGING); + POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.AUDIT_LOGGING_POLICY, + AUDIT_LOGGING); POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.LOCK_TASK_POLICY, LOCK_TASK); POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.USER_CONTROL_DISABLED_PACKAGES_POLICY, USER_CONTROLLED_DISABLED_PACKAGES); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java index 4aaefa670ea2..54242ab279b0 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java @@ -136,6 +136,14 @@ final class PolicyEnforcerCallbacks { return true; } + static boolean enforceAuditLogging( + @Nullable Boolean value, @NonNull Context context, int userId, + @NonNull PolicyKey policyKey) { + final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class); + dpmi.enforceAuditLoggingPolicy(Boolean.TRUE.equals(value)); + return true; + } + static boolean setLockTask( @Nullable LockTaskPolicy policy, @NonNull Context context, int userId) { List<String> packages = Collections.emptyList(); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java index 7a4454b11fce..02f39189ae72 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java @@ -16,22 +16,32 @@ package com.android.server.devicepolicy; +import static android.app.admin.flags.Flags.securityLogV2Enabled; + import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; import android.app.admin.DeviceAdminReceiver; +import android.app.admin.IAuditLogEventsCallback; import android.app.admin.SecurityLog; import android.app.admin.SecurityLog.SecurityEvent; +import android.os.Handler; +import android.os.IBinder; import android.os.Process; +import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; import android.util.Slog; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.utils.Slogf; import java.io.IOException; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; @@ -53,15 +63,11 @@ class SecurityLogMonitor implements Runnable { private int mEnabledUser; - SecurityLogMonitor(DevicePolicyManagerService service) { - this(service, 0 /* id */); - } - - @VisibleForTesting - SecurityLogMonitor(DevicePolicyManagerService service, long id) { + SecurityLogMonitor(DevicePolicyManagerService service, Handler handler) { mService = service; - mId = id; + mId = 0; mLastForceNanos = System.nanoTime(); + mHandler = handler; } private static final boolean DEBUG = false; // STOPSHIP if true. @@ -118,6 +124,9 @@ class SecurityLogMonitor implements Runnable { @GuardedBy("mLock") private boolean mCriticalLevelLogged = false; + private boolean mLegacyLogEnabled; + private boolean mAuditLogEnabled; + /** * Last events fetched from log to check for overlap between batches. We can leave it empty if * we are sure there will be no overlap anymore, e.g. when we get empty batch. @@ -143,6 +152,40 @@ class SecurityLogMonitor implements Runnable { private long mLastForceNanos = 0; /** + * Handler shared with DPMS. + */ + private final Handler mHandler; + + /** + * Oldest events get purged from audit log buffer if total number exceeds this value. + */ + private static final int MAX_AUDIT_LOG_EVENTS = 10000; + /** + * Events older than this get purged from audit log buffer. + */ + private static final long MAX_AUDIT_LOG_EVENT_AGE_NS = TimeUnit.HOURS.toNanos(8); + + /** + * Audit log callbacks keyed by UID. The code should maintain the following invariant: all + * callbacks in this map have received (or are scheduled to receive) all events in + * mAuditLogEventsBuffer. To ensure this, before a callback is put into this map, it must be + * scheduled to receive all the events in the buffer, and conversely, before a new chunk of + * events is added to the buffer, it must be scheduled to be sent to all callbacks already in + * this list. All scheduling should happen on mHandler, so that they aren't reordered, and + * while holding the lock. This ensures that no callback misses an event or receives a duplicate + * or out of order events. + */ + @GuardedBy("mLock") + private final SparseArray<IAuditLogEventsCallback> mAuditLogCallbacks = new SparseArray<>(); + + /** + * Audit log event buffer. It is shrunk automatically whenever either there are too many events + * or the oldest one is too old. + */ + @GuardedBy("mLock") + private final ArrayDeque<SecurityEvent> mAuditLogEventBuffer = new ArrayDeque<>(); + + /** * Start security logging. * * @param enabledUser which user logging is enabled on, or USER_ALL to enable logging for all @@ -154,18 +197,8 @@ class SecurityLogMonitor implements Runnable { mLock.lock(); try { if (mMonitorThread == null) { - mPendingLogs = new ArrayList<>(); - mCriticalLevelLogged = false; - mId = 0; - mAllowedToRetrieve = false; - mNextAllowedRetrievalTimeMillis = -1; - mPaused = false; - - mMonitorThread = new Thread(this); - mMonitorThread.start(); - - SecurityLog.writeEvent(SecurityLog.TAG_LOGGING_STARTED); - Slog.i(TAG, "Security log monitor thread started"); + resetLegacyBufferLocked(); + startMonitorThreadLocked(); } else { Slog.i(TAG, "Security log monitor thread is already running"); } @@ -176,29 +209,82 @@ class SecurityLogMonitor implements Runnable { void stop() { Slog.i(TAG, "Stopping security logging."); - SecurityLog.writeEvent(SecurityLog.TAG_LOGGING_STOPPED); mLock.lock(); try { if (mMonitorThread != null) { - mMonitorThread.interrupt(); - try { - mMonitorThread.join(TimeUnit.SECONDS.toMillis(5)); - } catch (InterruptedException e) { - Log.e(TAG, "Interrupted while waiting for thread to stop", e); - } - // Reset state and clear buffer - mPendingLogs = new ArrayList<>(); - mId = 0; - mAllowedToRetrieve = false; - mNextAllowedRetrievalTimeMillis = -1; - mPaused = false; - mMonitorThread = null; + stopMonitorThreadLocked(); + resetLegacyBufferLocked(); } } finally { mLock.unlock(); } } + void setLoggingParams(int enabledUser, boolean legacyLogEnabled, boolean auditLogEnabled) { + Slogf.i(TAG, "Setting logging params, user = %d -> %d, legacy: %b -> %b, audit %b -> %b", + mEnabledUser, enabledUser, mLegacyLogEnabled, legacyLogEnabled, mAuditLogEnabled, + auditLogEnabled); + mLock.lock(); + try { + mEnabledUser = enabledUser; + if (mMonitorThread == null && (legacyLogEnabled || auditLogEnabled)) { + startMonitorThreadLocked(); + } else if (mMonitorThread != null && !legacyLogEnabled && !auditLogEnabled) { + stopMonitorThreadLocked(); + } + + if (mLegacyLogEnabled != legacyLogEnabled) { + resetLegacyBufferLocked(); + mLegacyLogEnabled = legacyLogEnabled; + } + + if (mAuditLogEnabled != auditLogEnabled) { + resetAuditBufferLocked(); + mAuditLogEnabled = auditLogEnabled; + } + } finally { + mLock.unlock(); + } + + } + + @GuardedBy("mLock") + private void startMonitorThreadLocked() { + mId = 0; + mPaused = false; + mMonitorThread = new Thread(this); + mMonitorThread.start(); + SecurityLog.writeEvent(SecurityLog.TAG_LOGGING_STARTED); + Slog.i(TAG, "Security log monitor thread started"); + } + + @GuardedBy("mLock") + private void stopMonitorThreadLocked() { + 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; + SecurityLog.writeEvent(SecurityLog.TAG_LOGGING_STOPPED); + } + + @GuardedBy("mLock") + private void resetLegacyBufferLocked() { + mPendingLogs = new ArrayList<>(); + mCriticalLevelLogged = false; + mAllowedToRetrieve = false; + mNextAllowedRetrievalTimeMillis = -1; + Slog.i(TAG, "Legacy buffer reset."); + } + + @GuardedBy("mLock") + private void resetAuditBufferLocked() { + mAuditLogEventBuffer.clear(); + mAuditLogCallbacks.clear(); + } + /** * If logs are being collected, keep collecting them but stop notifying the device owner that * new logs are available (since they cannot be retrieved). @@ -338,8 +424,7 @@ class SecurityLogMonitor implements Runnable { */ @GuardedBy("mLock") private void mergeBatchLocked(final ArrayList<SecurityEvent> newLogs) { - // Reserve capacity so that copying doesn't occur. - mPendingLogs.ensureCapacity(mPendingLogs.size() + newLogs.size()); + List<SecurityEvent> dedupedLogs = new ArrayList<>(); // Run through the first events of the batch to check if there is an overlap with previous // batch and if so, skip overlapping events. Events are sorted by timestamp, so we can // compare it in linear time by advancing two pointers, one for each batch. @@ -358,8 +443,7 @@ class SecurityLogMonitor implements Runnable { if (lastNanos > currentNanos) { // New event older than the last we've seen so far, must be due to reordering. if (DEBUG) Slog.d(TAG, "New event in the overlap: " + currentNanos); - assignLogId(curEvent); - mPendingLogs.add(curEvent); + dedupedLogs.add(curEvent); curPos++; } else if (lastNanos < currentNanos) { if (DEBUG) Slog.d(TAG, "Event disappeared from the overlap: " + lastNanos); @@ -371,8 +455,7 @@ class SecurityLogMonitor implements Runnable { if (DEBUG) Slog.d(TAG, "Skipped dup event with timestamp: " + lastNanos); } else { // Wow, what a coincidence, or probably the clock is too coarse. - assignLogId(curEvent); - mPendingLogs.add(curEvent); + dedupedLogs.add(curEvent); if (DEBUG) Slog.d(TAG, "Event timestamp collision: " + lastNanos); } lastPos++; @@ -380,12 +463,23 @@ class SecurityLogMonitor implements Runnable { } } // Assign an id to the new logs, after the overlap with mLastEvents. - List<SecurityEvent> idLogs = newLogs.subList(curPos, newLogs.size()); - for (SecurityEvent event : idLogs) { + dedupedLogs.addAll(newLogs.subList(curPos, newLogs.size())); + for (SecurityEvent event : dedupedLogs) { assignLogId(event); } + + if (!securityLogV2Enabled() || mLegacyLogEnabled) { + addToLegacyBuffer(dedupedLogs); + } + + if (securityLogV2Enabled() && mAuditLogEnabled) { + addAuditLogEvents(dedupedLogs); + } + } + + private void addToLegacyBuffer(List<SecurityEvent> dedupedLogs) { // Save the rest of the new batch. - mPendingLogs.addAll(idLogs); + mPendingLogs.addAll(dedupedLogs); checkCriticalLevel(); @@ -453,7 +547,10 @@ class SecurityLogMonitor implements Runnable { saveLastEvents(newLogs); newLogs.clear(); - notifyDeviceOwnerOrProfileOwnerIfNeeded(force); + + if (!securityLogV2Enabled() || mLegacyLogEnabled) { + notifyDeviceOwnerOrProfileOwnerIfNeeded(force); + } } catch (IOException e) { Log.e(TAG, "Failed to read security log", e); } catch (InterruptedException e) { @@ -532,4 +629,121 @@ class SecurityLogMonitor implements Runnable { return 0; } } + + public void setAuditLogEventsCallback(int uid, IAuditLogEventsCallback callback) { + mLock.lock(); + try { + if (callback == null) { + mAuditLogCallbacks.remove(uid); + Slogf.i(TAG, "Cleared audit log callback for UID %d", uid); + return; + } + // Create a copy while holding the lock, so that that new events are not added + // resulting in duplicates. + final List<SecurityEvent> events = new ArrayList<>(mAuditLogEventBuffer); + scheduleSendAuditLogs(uid, callback, events); + mAuditLogCallbacks.append(uid, callback); + } finally { + mLock.unlock(); + } + Slogf.i(TAG, "Set audit log callback for UID %d", uid); + } + + private void addAuditLogEvents(List<SecurityEvent> events) { + mLock.lock(); + try { + if (mPaused) { + // TODO: maybe we need to stash the logs in some temp buffer wile paused so that + // they can be accessed after affiliation is fixed. + return; + } + if (!events.isEmpty()) { + for (int i = 0; i < mAuditLogCallbacks.size(); i++) { + final int uid = mAuditLogCallbacks.keyAt(i); + scheduleSendAuditLogs(uid, mAuditLogCallbacks.valueAt(i), events); + } + } + if (DEBUG) { + Slogf.d(TAG, "Adding audit %d events to % already present in the buffer", + events.size(), mAuditLogEventBuffer.size()); + } + mAuditLogEventBuffer.addAll(events); + trimAuditLogBufferLocked(); + if (DEBUG) { + Slogf.d(TAG, "Audit event buffer size after trimming: %d", + mAuditLogEventBuffer.size()); + } + } finally { + mLock.unlock(); + } + } + + @GuardedBy("mLock") + private void trimAuditLogBufferLocked() { + long nowNanos = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); + + final Iterator<SecurityEvent> iterator = mAuditLogEventBuffer.iterator(); + while (iterator.hasNext()) { + final SecurityEvent event = iterator.next(); + if (mAuditLogEventBuffer.size() <= MAX_AUDIT_LOG_EVENTS + && nowNanos - event.getTimeNanos() <= MAX_AUDIT_LOG_EVENT_AGE_NS) { + break; + } + + iterator.remove(); + } + } + + private void scheduleSendAuditLogs( + int uid, IAuditLogEventsCallback callback, List<SecurityEvent> events) { + if (DEBUG) { + Slogf.d(TAG, "Scheduling to send %d audit log events to UID %d", events.size(), uid); + } + mHandler.post(() -> sendAuditLogs(uid, callback, events)); + } + + private void sendAuditLogs( + int uid, IAuditLogEventsCallback callback, List<SecurityEvent> events) { + try { + final int size = events.size(); + if (DEBUG) { + Slogf.d(TAG, "Sending %d audit log events to UID %d", size, uid); + } + callback.onNewAuditLogEvents(events); + if (DEBUG) { + Slogf.d(TAG, "Sent %d audit log events to UID %d", size, uid); + } + } catch (RemoteException e) { + Slogf.e(TAG, e, "Failed to invoke audit log callback for UID %d", uid); + removeAuditLogEventsCallbackIfDead(uid, callback); + } + } + + private void removeAuditLogEventsCallbackIfDead(int uid, IAuditLogEventsCallback callback) { + final IBinder binder = callback.asBinder(); + if (binder.isBinderAlive()) { + Slog.i(TAG, "Callback binder is still alive, not removing."); + return; + } + + mLock.lock(); + try { + int index = mAuditLogCallbacks.indexOfKey(uid); + if (index < 0) { + Slogf.i(TAG, "Callback not registered for UID %d, nothing to remove", uid); + return; + } + + final IBinder storedBinder = mAuditLogCallbacks.valueAt(index).asBinder(); + if (!storedBinder.equals(binder)) { + Slogf.i(TAG, "Callback is already replaced for UID %d, not removing", uid); + return; + } + + Slogf.i(TAG, "Removing callback for UID %d", uid); + mAuditLogCallbacks.removeAt(index); + } finally { + mLock.unlock(); + } + } } |