Add audit logging API
Bug: 295324350
Test: atest SecurityLoggingTest
Change-Id: Ie4abb9a5995930a5b049db5a04783a8531408729
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 340a79d..409b7b7 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -195,6 +195,7 @@
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 @@
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 @@
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 @@
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 a884ab0..3c56aaf 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -20,6 +20,7 @@
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 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 367ade0..a6fda9d 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_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 @@
private final boolean mParentInstance;
private final DevicePolicyResourcesManager mResourcesManager;
-
/** @hide */
public DevicePolicyManager(Context context, IDevicePolicyManager service) {
this(context, service, false);
@@ -14059,6 +14059,74 @@
}
/**
+ * 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 07ee8de..1aee9fe 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -338,4 +338,9 @@
* 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 0000000..ab87117
--- /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 ca4a5ab..3a7a891c 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.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 @@
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 0000000..e5ae2df
--- /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 4131644..71f06f1 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 4305d91..53f2caf 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 9b84f39..58e198e 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_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.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 @@
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 @@
}
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 @@
@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 @@
}
}
- 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 @@
}
@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 3474db3..1247f9002 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 @@
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 @@
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 4aaefa6..54242ab 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 @@
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 7a4454b..02f3918 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 @@
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 @@
@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 @@
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 @@
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 @@
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 @@
*/
@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 @@
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 @@
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 @@
}
}
// 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 @@
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 @@
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();
+ }
+ }
}