summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Rubin Xu <rubinxu@google.com> 2020-02-05 11:38:08 +0000
committer Rubin Xu <rubinxu@google.com> 2020-02-20 15:47:29 +0000
commit1480ce7b87ff012ff6016d7469c1520291ecd72f (patch)
tree830f84fc69bcda717b3161824f005f6ef011a2e9
parent1ef019d3e073098aacd37edf99ce5d0b3425ffd3 (diff)
Support security logging on org-owned managed profile devices
When security logging is enabled on org-owned profile devices, Security events will be redacted to preserve privacy on the personal profile as follows: * TAG_ADB_SHELL_CMD Shell command will be redacted. * TAG_MEDIA_MOUNT * TAG_MEDIA_UNMOUNT The media's volume name will be redacted. * TAG_APP_PROCESS_START * TAG_CERT_AUTHORITY_INSTALLED * TAG_CERT_AUTHORITY_REMOVED * TAG_KEY_GENERATED * TAG_KEY_IMPORT * TAG_KEY_DESTRUCTION * TAG_KEY_INTEGRITY_VIOLATION Only events happening inside the managed profile will be returned to the admin. Bug: 148437300 Test: atest FrameworksServicesTests:DevicePolicyManagerTest Test: atest FrameworksServicesTests:SecurityEventTest Test: atest FrameworksCoreTests:EventLogTest Test: atest com.android.cts.devicepolicy.MixedDeviceOwnerTest#testSecurityLoggingWithSingleUser Test: atest com.android.cts.devicepolicy.MixedDeviceOwnerTest#testSecurityLoggingWithTwoUsers Test: atest com.android.cts.devicepolicy.MixedDeviceOwnerTest#testSecurityLoggingEnabledLogged Test: atest com.android.cts.devicepolicy.OrgOwnedProfileOwnerTest#testSecurityLogging Change-Id: I2e52229a3163b3e0dc3d80d71700023394d84587
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java56
-rw-r--r--core/java/android/app/admin/SecurityLog.java140
-rw-r--r--core/java/android/app/admin/SecurityLogTags.logtags4
-rw-r--r--core/java/android/util/EventLog.java110
-rw-r--r--core/tests/coretests/src/android/util/EventLogTest.java74
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java127
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java21
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java9
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/SecurityEventTest.java147
10 files changed, 565 insertions, 126 deletions
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 546fef913192..1501d36ab1f0 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9975,20 +9975,27 @@ public class DevicePolicyManager {
}
/**
- * Called by device owner to control the security logging feature.
+ * Called by device owner or a profile owner of an organization-owned managed profile to
+ * control the security logging feature.
*
* <p> Security logs contain various information intended for security auditing purposes.
- * See {@link SecurityEvent} for details.
+ * When security logging is enabled by a profile owner of
+ * an organization-owned managed profile, certain security logs are not visible (for example
+ * personal app launch events) or they will be redacted (for example, details of the physical
+ * volume mount events). Please see {@link SecurityEvent} for details.
*
* <p><strong>Note:</strong> The device owner won't be able to retrieve security logs if there
* are unaffiliated secondary users or profiles on the device, regardless of whether the
* feature is enabled. Logs will be discarded if the internal buffer fills up while waiting for
* all users to become affiliated. Therefore it's recommended that affiliation ids are set for
- * new users as soon as possible after provisioning via {@link #setAffiliationIds}.
+ * new users as soon as possible after provisioning via {@link #setAffiliationIds}. Profile
+ * owner of organization-owned managed profile is not subject to this restriction since all
+ * privacy-sensitive events happening outside the managed profile would have been redacted
+ * already.
*
- * @param admin Which device owner this request is associated with.
+ * @param admin Which device admin this request is associated with.
* @param enabled whether security logging should be enabled or not.
- * @throws SecurityException if {@code admin} is not a device owner.
+ * @throws SecurityException if {@code admin} is not allowed to control security logging.
* @see #setAffiliationIds
* @see #retrieveSecurityLogs
*/
@@ -10002,14 +10009,14 @@ public class DevicePolicyManager {
}
/**
- * Return whether security logging is enabled or not by the device owner.
+ * Return whether security logging is enabled or not by the admin.
*
- * <p>Can only be called by the device owner, otherwise a {@link SecurityException} will be
- * thrown.
+ * <p>Can only be called by the device owner or a profile owner of an organization-owned
+ * managed profile, otherwise a {@link SecurityException} will be thrown.
*
- * @param admin Which device owner this request is associated with.
+ * @param admin Which device admin this request is associated with.
* @return {@code true} if security logging is enabled by device owner, {@code false} otherwise.
- * @throws SecurityException if {@code admin} is not a device owner.
+ * @throws SecurityException if {@code admin} is not allowed to control security logging.
*/
public boolean isSecurityLoggingEnabled(@Nullable ComponentName admin) {
throwIfParentInstance("isSecurityLoggingEnabled");
@@ -10021,20 +10028,21 @@ public class DevicePolicyManager {
}
/**
- * Called by device owner to retrieve all new security logging entries since the last call to
- * this API after device boots.
+ * 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.
*
* <p> 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}.
*
- * <p>If there is any other user or profile on the device, it must be affiliated with the
- * device. Otherwise a {@link SecurityException} will be thrown. See {@link #isAffiliatedUser}.
+ * <p> When called by a device owner, if there is any other user or profile on the device,
+ * it must be affiliated with the device. Otherwise a {@link SecurityException} will be thrown.
+ * See {@link #isAffiliatedUser}.
*
- * @param admin Which device owner this request is associated with.
+ * @param admin Which device admin this request is associated with.
* @return the new batch of security logs which is a list of {@link SecurityEvent},
* or {@code null} if rate limitation is exceeded or if logging is currently disabled.
- * @throws SecurityException if {@code admin} is not a device owner, or there is at least one
- * profile or secondary user that is not affiliated with the device.
+ * @throws SecurityException if {@code admin} is not allowed to access security logging,
+ * or there is at least one profile or secondary user that is not affiliated with the device.
* @see #isAffiliatedUser
* @see DeviceAdminReceiver#onSecurityLogsAvailable
*/
@@ -10167,21 +10175,23 @@ public class DevicePolicyManager {
}
/**
- * Called by device owners to retrieve device logs from before the device's last reboot.
+ * Called by device owner or profile owner of an organization-owned managed profile to retrieve
+ * device logs from before the device's last reboot.
* <p>
* <strong> This API is not supported on all devices. Calling this API on unsupported devices
* will result in {@code null} being returned. The device logs are retrieved from a RAM region
* which is not guaranteed to be corruption-free during power cycles, as a result be cautious
* about data corruption when parsing. </strong>
*
- * <p>If there is any other user or profile on the device, it must be affiliated with the
- * device. Otherwise a {@link SecurityException} will be thrown. See {@link #isAffiliatedUser}.
+ * <p> When called by a device owner, if there is any other user or profile on the device,
+ * it must be affiliated with the device. Otherwise a {@link SecurityException} will be thrown.
+ * See {@link #isAffiliatedUser}.
*
- * @param admin Which device owner this request is associated with.
+ * @param admin Which device admin this request is associated with.
* @return Device logs from before the latest reboot of the system, or {@code null} if this API
* is not supported on the device.
- * @throws SecurityException if {@code admin} is not a device owner, or there is at least one
- * profile or secondary user that is not affiliated with the device.
+ * @throws SecurityException if {@code admin} is not allowed to access security logging, or
+ * there is at least one profile or secondary user that is not affiliated with the device.
* @see #isAffiliatedUser
* @see #retrieveSecurityLogs
*/
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index 91cf120032b5..fb7f573d5535 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -23,11 +23,13 @@ import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemProperties;
+import android.os.UserHandle;
import android.util.EventLog.Event;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
@@ -104,7 +106,8 @@ public class SecurityLog {
/**
* Indicates that a shell command was issued over ADB via {@code adb shell <command>}
* The log entry contains a {@code String} payload containing the shell command, accessible
- * via {@link SecurityEvent#getData()}.
+ * via {@link SecurityEvent#getData()}. If security logging is enabled on organization-owned
+ * managed profile devices, the shell command will be redacted to an empty string.
*/
public static final int TAG_ADB_SHELL_CMD = SecurityLogTags.SECURITY_ADB_SHELL_COMMAND;
@@ -133,6 +136,8 @@ public class SecurityLog {
* <li> [3] app pid ({@code Integer})
* <li> [4] seinfo tag ({@code String})
* <li> [5] SHA-256 hash of the base APK in hexadecimal ({@code String})
+ * If security logging is enabled on organization-owned managed profile devices, only events
+ * happening inside the managed profile will be visible.
*/
public static final int TAG_APP_PROCESS_START = SecurityLogTags.SECURITY_APP_PROCESS_START;
@@ -205,7 +210,8 @@ public class SecurityLog {
* following information about the event, encapsulated in an {@link Object} array and
* accessible via {@link SecurityEvent#getData()}:
* <li> [0] mount point ({@code String})
- * <li> [1] volume label ({@code String}).
+ * <li> [1] volume label ({@code String}). Redacted to empty string on organization-owned
+ * managed profile devices.
*/
public static final int TAG_MEDIA_MOUNT = SecurityLogTags.SECURITY_MEDIA_MOUNTED;
@@ -214,7 +220,8 @@ public class SecurityLog {
* following information about the event, encapsulated in an {@link Object} array and
* accessible via {@link SecurityEvent#getData()}:
* <li> [0] mount point ({@code String})
- * <li> [1] volume label ({@code String}).
+ * <li> [1] volume label ({@code String}). Redacted to empty string on organization-owned
+ * managed profile devices.
*/
public static final int TAG_MEDIA_UNMOUNT = SecurityLogTags.SECURITY_MEDIA_UNMOUNTED;
@@ -340,6 +347,9 @@ public class SecurityLog {
* <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded)
* <li> [1] alias of the key ({@code String})
* <li> [2] requesting process uid ({@code Integer}).
+ *
+ * If security logging is enabled on organization-owned managed profile devices, only events
+ * happening inside the managed profile will be visible.
*/
public static final int TAG_KEY_GENERATED =
SecurityLogTags.SECURITY_KEY_GENERATED;
@@ -351,6 +361,9 @@ public class SecurityLog {
* <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded)
* <li> [1] alias of the key ({@code String})
* <li> [2] requesting process uid ({@code Integer}).
+ *
+ * If security logging is enabled on organization-owned managed profile devices, only events
+ * happening inside the managed profile will be visible.
*/
public static final int TAG_KEY_IMPORT = SecurityLogTags.SECURITY_KEY_IMPORTED;
@@ -361,6 +374,9 @@ public class SecurityLog {
* <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded)
* <li> [1] alias of the key ({@code String})
* <li> [2] requesting process uid ({@code Integer}).
+ *
+ * If security logging is enabled on organization-owned managed profile devices, only events
+ * happening inside the managed profile will be visible.
*/
public static final int TAG_KEY_DESTRUCTION = SecurityLogTags.SECURITY_KEY_DESTROYED;
@@ -370,6 +386,11 @@ public class SecurityLog {
* {@link Object} array and accessible via {@link SecurityEvent#getData()}:
* <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded)
* <li> [1] subject of the certificate ({@code String}).
+ * <li> [2] which user the certificate is installed for ({@code Integer}), only available from
+ * version {@link android.os.Build.VERSION_CODES#R}.
+ *
+ * If security logging is enabled on organization-owned managed profile devices, only events
+ * happening inside the managed profile will be visible.
*/
public static final int TAG_CERT_AUTHORITY_INSTALLED =
SecurityLogTags.SECURITY_CERT_AUTHORITY_INSTALLED;
@@ -380,6 +401,11 @@ public class SecurityLog {
* {@link Object} array and accessible via {@link SecurityEvent#getData()}:
* <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded)
* <li> [1] subject of the certificate ({@code String}).
+ * <li> [2] which user the certificate is removed from ({@code Integer}), only available from
+ * version {@link android.os.Build.VERSION_CODES#R}.
+ *
+ * If security logging is enabled on organization-owned managed profile devices, only events
+ * happening inside the managed profile will be visible.
*/
public static final int TAG_CERT_AUTHORITY_REMOVED =
SecurityLogTags.SECURITY_CERT_AUTHORITY_REMOVED;
@@ -422,6 +448,9 @@ public class SecurityLog {
* {@link SecurityEvent#getData()}:
* <li> [0] alias of the key ({@code String})
* <li> [1] owner application uid ({@code Integer}).
+ *
+ * If security logging is enabled on organization-owned managed profile devices, only events
+ * happening inside the managed profile will be visible.
*/
public static final int TAG_KEY_INTEGRITY_VIOLATION =
SecurityLogTags.SECURITY_KEY_INTEGRITY_VIOLATION;
@@ -535,6 +564,16 @@ public class SecurityLog {
return mEvent.getData();
}
+ /** @hide */
+ public int getIntegerData(int index) {
+ return (Integer) ((Object[]) mEvent.getData())[index];
+ }
+
+ /** @hide */
+ public String getStringData(int index) {
+ return (String) ((Object[]) mEvent.getData())[index];
+ }
+
/**
* @hide
*/
@@ -554,7 +593,7 @@ public class SecurityLog {
* Returns severity level for the event.
*/
public @SecurityLogLevel int getLogLevel() {
- switch (mEvent.getTag()) {
+ switch (getTag()) {
case TAG_ADB_SHELL_INTERACTIVE:
case TAG_ADB_SHELL_CMD:
case TAG_SYNC_RECV_FILE:
@@ -608,6 +647,75 @@ public class SecurityLog {
return array.length >= 1 && array[0] instanceof Integer && (Integer) array[0] != 0;
}
+ /**
+ * Returns a copy of the security event suitable to be consumed by the provided user.
+ * This method will either return the original event itself if the event does not contain
+ * any sensitive data; or a copy of itself but with sensitive information redacted; or
+ * {@code null} if the entire event should not be accessed by the given user.
+ *
+ * @param accessingUser which user this security event is to be accessed, must be a
+ * concrete user id.
+ * @hide
+ */
+ public SecurityEvent redact(int accessingUser) {
+ // Which user the event is associated with, for the purpose of log redaction.
+ final int userId;
+ switch (getTag()) {
+ case SecurityLog.TAG_ADB_SHELL_CMD:
+ return new SecurityEvent(getId(), mEvent.withNewData("").getBytes());
+ case SecurityLog.TAG_MEDIA_MOUNT:
+ case SecurityLog.TAG_MEDIA_UNMOUNT:
+ // Partial redaction
+ String mountPoint;
+ try {
+ mountPoint = getStringData(0);
+ } catch (Exception e) {
+ return null;
+ }
+ return new SecurityEvent(getId(),
+ mEvent.withNewData(new Object[] {mountPoint, ""}).getBytes());
+ case SecurityLog.TAG_APP_PROCESS_START:
+ try {
+ userId = UserHandle.getUserId(getIntegerData(2));
+ } catch (Exception e) {
+ return null;
+ }
+ break;
+ case SecurityLog.TAG_CERT_AUTHORITY_INSTALLED:
+ case SecurityLog.TAG_CERT_AUTHORITY_REMOVED:
+ try {
+ userId = getIntegerData(2);
+ } catch (Exception e) {
+ return null;
+ }
+ break;
+ case SecurityLog.TAG_KEY_GENERATED:
+ case SecurityLog.TAG_KEY_IMPORT:
+ case SecurityLog.TAG_KEY_DESTRUCTION:
+ try {
+ userId = UserHandle.getUserId(getIntegerData(2));
+ } catch (Exception e) {
+ return null;
+ }
+ break;
+ case SecurityLog.TAG_KEY_INTEGRITY_VIOLATION:
+ try {
+ userId = UserHandle.getUserId(getIntegerData(1));
+ } catch (Exception e) {
+ return null;
+ }
+ break;
+ default:
+ userId = UserHandle.USER_NULL;
+ }
+ // If the event is not user-specific, or matches the accessing user, return it
+ // unmodified, else redact by returning null
+ if (userId == UserHandle.USER_NULL || accessingUser == userId) {
+ return this;
+ } else {
+ return null;
+ }
+ }
@Override
public int describeContents() {
@@ -657,6 +765,30 @@ public class SecurityLog {
return other != null && mEvent.equals(other.mEvent);
}
}
+
+ /**
+ * Redacts events in-place according to which user will consume the events.
+ *
+ * @param accessingUser which user will consume the redacted events, or UserHandle.USER_ALL if
+ * redaction should be skipped.
+ * @hide
+ */
+ public static void redactEvents(ArrayList<SecurityEvent> logList, int accessingUser) {
+ if (accessingUser == UserHandle.USER_ALL) return;
+ int end = 0;
+ for (int i = 0; i < logList.size(); i++) {
+ SecurityEvent event = logList.get(i);
+ event = event.redact(accessingUser);
+ if (event != null) {
+ logList.set(end, event);
+ end++;
+ }
+ }
+ for (int i = logList.size() - 1; i >= end; i--) {
+ logList.remove(i);
+ }
+ }
+
/**
* Retrieve all security logs and return immediately.
* @hide
diff --git a/core/java/android/app/admin/SecurityLogTags.logtags b/core/java/android/app/admin/SecurityLogTags.logtags
index 4e67fe253715..100fd4cbd40f 100644
--- a/core/java/android/app/admin/SecurityLogTags.logtags
+++ b/core/java/android/app/admin/SecurityLogTags.logtags
@@ -33,8 +33,8 @@ option java_package android.app.admin
210026 security_key_destroyed (success|1),(key_id|3),(uid|1)
210027 security_user_restriction_added (package|3),(admin_user|1),(restriction|3)
210028 security_user_restriction_removed (package|3),(admin_user|1),(restriction|3)
-210029 security_cert_authority_installed (success|1),(subject|3)
-210030 security_cert_authority_removed (success|1),(subject|3)
+210029 security_cert_authority_installed (success|1),(subject|3),(target_user|1)
+210030 security_cert_authority_removed (success|1),(subject|3),(target_user|1)
210031 security_crypto_self_test_completed (success|1)
210032 security_key_integrity_violation (key_id|3),(uid|1)
210033 security_cert_validation_failure (reason|3)
diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java
index c9dc05b5aeb5..ead4e46cd28b 100644
--- a/core/java/android/util/EventLog.java
+++ b/core/java/android/util/EventLog.java
@@ -16,6 +16,8 @@
package android.util;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
@@ -62,7 +64,7 @@ public class EventLog {
private Exception mLastWtf;
// Layout of event log entry received from Android logger.
- // see system/core/include/log/log.h
+ // see system/core/liblog/include/log/log_read.h
private static final int LENGTH_OFFSET = 0;
private static final int HEADER_SIZE_OFFSET = 2;
private static final int PROCESS_OFFSET = 4;
@@ -73,7 +75,7 @@ public class EventLog {
// Layout for event log v1 format, v2 and v3 use HEADER_SIZE_OFFSET
private static final int V1_PAYLOAD_START = 20;
- private static final int DATA_OFFSET = 4;
+ private static final int TAG_LENGTH = 4;
// Value types
private static final byte INT_TYPE = 0;
@@ -121,26 +123,26 @@ public class EventLog {
/** @return the type tag code of the entry */
public int getTag() {
- int offset = mBuffer.getShort(HEADER_SIZE_OFFSET);
- if (offset == 0) {
- offset = V1_PAYLOAD_START;
- }
- return mBuffer.getInt(offset);
+ return mBuffer.getInt(getHeaderSize());
}
+ private int getHeaderSize() {
+ int length = mBuffer.getShort(HEADER_SIZE_OFFSET);
+ if (length != 0) {
+ return length;
+ }
+ return V1_PAYLOAD_START;
+ }
/** @return one of Integer, Long, Float, String, null, or Object[] of same. */
public synchronized Object getData() {
try {
- int offset = mBuffer.getShort(HEADER_SIZE_OFFSET);
- if (offset == 0) {
- offset = V1_PAYLOAD_START;
- }
+ int offset = getHeaderSize();
mBuffer.limit(offset + mBuffer.getShort(LENGTH_OFFSET));
- if ((offset + DATA_OFFSET) >= mBuffer.limit()) {
+ if ((offset + TAG_LENGTH) >= mBuffer.limit()) {
// no payload
return null;
}
- mBuffer.position(offset + DATA_OFFSET); // Just after the tag.
+ mBuffer.position(offset + TAG_LENGTH); // Just after the tag.
return decodeObject();
} catch (IllegalArgumentException e) {
Log.wtf(TAG, "Illegal entry payload: tag=" + getTag(), e);
@@ -153,6 +155,28 @@ public class EventLog {
}
}
+ /**
+ * Construct a new EventLog object from the current object, copying all log metadata
+ * but replacing the actual payload with the content provided.
+ * @hide
+ */
+ public Event withNewData(@Nullable Object object) {
+ byte[] payload = encodeObject(object);
+ if (payload.length > 65535 - TAG_LENGTH) {
+ throw new IllegalArgumentException("Payload too long");
+ }
+ int headerLength = getHeaderSize();
+ byte[] newBytes = new byte[headerLength + TAG_LENGTH + payload.length];
+ // Copy header (including the 4 bytes of tag integer at the beginning of payload)
+ System.arraycopy(mBuffer.array(), 0, newBytes, 0, headerLength + TAG_LENGTH);
+ // Fill in encoded objects
+ System.arraycopy(payload, 0, newBytes, headerLength + TAG_LENGTH, payload.length);
+ Event result = new Event(newBytes);
+ // Patch payload length in header
+ result.mBuffer.putShort(LENGTH_OFFSET, (short) (payload.length + TAG_LENGTH));
+ return result;
+ }
+
/** @return the loggable item at the current position in mBuffer. */
private Object decodeObject() {
byte type = mBuffer.get();
@@ -190,6 +214,66 @@ public class EventLog {
}
}
+ private static @NonNull byte[] encodeObject(@Nullable Object object) {
+ if (object == null) {
+ return new byte[0];
+ }
+ if (object instanceof Integer) {
+ return ByteBuffer.allocate(1 + 4)
+ .order(ByteOrder.nativeOrder())
+ .put(INT_TYPE)
+ .putInt((Integer) object)
+ .array();
+ } else if (object instanceof Long) {
+ return ByteBuffer.allocate(1 + 8)
+ .order(ByteOrder.nativeOrder())
+ .put(LONG_TYPE)
+ .putLong((Long) object)
+ .array();
+ } else if (object instanceof Float) {
+ return ByteBuffer.allocate(1 + 4)
+ .order(ByteOrder.nativeOrder())
+ .put(FLOAT_TYPE)
+ .putFloat((Float) object)
+ .array();
+ } else if (object instanceof String) {
+ String string = (String) object;
+ byte[] bytes;
+ try {
+ bytes = string.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ bytes = new byte[0];
+ }
+ return ByteBuffer.allocate(1 + 4 + bytes.length)
+ .order(ByteOrder.nativeOrder())
+ .put(STRING_TYPE)
+ .putInt(bytes.length)
+ .put(bytes)
+ .array();
+ } else if (object instanceof Object[]) {
+ Object[] objects = (Object[]) object;
+ if (objects.length > 255) {
+ throw new IllegalArgumentException("Object array too long");
+ }
+ byte[][] bytes = new byte[objects.length][];
+ int totalLength = 0;
+ for (int i = 0; i < objects.length; i++) {
+ bytes[i] = encodeObject(objects[i]);
+ totalLength += bytes[i].length;
+ }
+ ByteBuffer buffer = ByteBuffer.allocate(1 + 1 + totalLength)
+ .order(ByteOrder.nativeOrder())
+ .put(LIST_TYPE)
+ .put((byte) objects.length);
+ for (int i = 0; i < objects.length; i++) {
+ buffer.put(bytes[i]);
+ }
+ return buffer.array();
+ } else {
+ throw new IllegalArgumentException("Unknown object type " + object);
+ }
+ }
+
/** @hide */
public static Event fromBytes(byte[] data) {
return new Event(data);
diff --git a/core/tests/coretests/src/android/util/EventLogTest.java b/core/tests/coretests/src/android/util/EventLogTest.java
new file mode 100644
index 000000000000..94e72c4a8d52
--- /dev/null
+++ b/core/tests/coretests/src/android/util/EventLogTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 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.util;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.util.EventLog.Event;
+
+import junit.framework.AssertionFailedError;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Unit tests for {@link android.util.EventLog} */
+public class EventLogTest {
+
+ @Test
+ public void testWithNewData() throws Throwable {
+ Event event = createEvent(() -> {
+ EventLog.writeEvent(314, 123);
+ }, 314);
+
+ assertTrue(event.withNewData(12345678L).getData().equals(12345678L));
+ assertTrue(event.withNewData(2.718f).getData().equals(2.718f));
+ assertTrue(event.withNewData("test string").getData().equals("test string"));
+
+ Object[] objects = ((Object[]) event.withNewData(
+ new Object[] {111, 2.22f, 333L, "444"}).getData());
+ assertEquals(4, objects.length);
+ assertTrue(objects[0].equals(111));
+ assertTrue(objects[1].equals(2.22f));
+ assertTrue(objects[2].equals(333L));
+ assertTrue(objects[3].equals("444"));
+ }
+
+ /**
+ * Creates an Event object. Only the native code has the serialization and deserialization logic
+ * so need to actually emit a real log in order to generate the object.
+ */
+ private Event createEvent(Runnable generator, int expectedTag) throws Exception {
+ Long markerData = System.currentTimeMillis();
+ EventLog.writeEvent(expectedTag, markerData);
+ generator.run();
+
+ List<Event> events = new ArrayList<>();
+ // Give the message some time to show up in the log
+ Thread.sleep(20);
+ EventLog.readEvents(new int[] {expectedTag}, events);
+ for (int i = 0; i < events.size() - 1; i++) {
+ if (markerData.equals(events.get(i).getData())) {
+ return events.get(i + 1);
+ }
+ }
+ throw new AssertionFailedError("Unable to locate marker event");
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 012fdfc5bbd0..ffdc54bb6853 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -445,10 +445,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
});
/**
- * System property whose value is either "true" or "false", indicating whether
- * device owner is present.
+ * System property whose value indicates whether the device is fully owned by an organization:
+ * it can be either a device owner device, or a device with an organization-owned managed
+ * profile.
+ *
+ * <p>The state is stored as a Boolean string.
*/
- private static final String PROPERTY_DEVICE_OWNER_PRESENT = "ro.organization_owned";
+ private static final String PROPERTY_ORGANIZATION_OWNED = "ro.organization_owned";
private static final int STATUS_BAR_DISABLE_MASK =
StatusBarManager.DISABLE_EXPAND |
@@ -2555,7 +2558,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
void loadOwners() {
synchronized (getLockObject()) {
mOwners.load();
- setDeviceOwnerSystemPropertyLocked();
+ setDeviceOwnershipSystemPropertyLocked();
findOwnerComponentIfNecessaryLocked();
migrateUserRestrictionsIfNecessaryLocked();
@@ -2757,34 +2760,36 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
- private void setDeviceOwnerSystemPropertyLocked() {
+ private void setDeviceOwnershipSystemPropertyLocked() {
+ // Still at the first stage of CryptKeeper double bounce, nothing can be learnt about
+ // the real system at this point.
+ if (StorageManager.inCryptKeeperBounce()) {
+ return;
+ }
final boolean deviceProvisioned =
mInjector.settingsGlobalGetInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0;
final boolean hasDeviceOwner = mOwners.hasDeviceOwner();
- // If the device is not provisioned and there is currently no device owner, do not set the
- // read-only system property yet, since Device owner may still be provisioned.
- if (!hasDeviceOwner && !deviceProvisioned) {
- return;
- }
- // Still at the first stage of CryptKeeper double bounce, mOwners.hasDeviceOwner is
- // always false at this point.
- if (StorageManager.inCryptKeeperBounce()) {
+ final boolean hasOrgOwnedProfile = isOrganizationOwnedDeviceWithManagedProfile();
+ // If the device is not provisioned and there is currently no management, do not set the
+ // read-only system property yet, since device owner / org-owned profile may still be
+ // provisioned.
+ if (!hasDeviceOwner && !hasOrgOwnedProfile && !deviceProvisioned) {
return;
}
-
- if (!mInjector.systemPropertiesGet(PROPERTY_DEVICE_OWNER_PRESENT, "").isEmpty()) {
- Slog.w(LOG_TAG, "Trying to set ro.organization_owned, but it has already been set?");
- } else {
- final String value = Boolean.toString(hasDeviceOwner);
- mInjector.systemPropertiesSet(PROPERTY_DEVICE_OWNER_PRESENT, value);
+ final String value = Boolean.toString(hasDeviceOwner || hasOrgOwnedProfile);
+ final String currentVal = mInjector.systemPropertiesGet(PROPERTY_ORGANIZATION_OWNED, null);
+ if (TextUtils.isEmpty(currentVal)) {
Slog.i(LOG_TAG, "Set ro.organization_owned property to " + value);
+ mInjector.systemPropertiesSet(PROPERTY_ORGANIZATION_OWNED, value);
+ } else if (!value.equals(currentVal)) {
+ Slog.w(LOG_TAG, "Cannot change existing ro.organization_owned to " + value);
}
}
private void maybeStartSecurityLogMonitorOnActivityManagerReady() {
synchronized (getLockObject()) {
if (mInjector.securityLogIsLoggingEnabled()) {
- mSecurityLogMonitor.start();
+ mSecurityLogMonitor.start(getSecurityLoggingEnabledUser());
mInjector.runCryptoSelfTest();
maybePauseDeviceWideLoggingLocked();
}
@@ -8396,7 +8401,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mOwners.setDeviceOwner(admin, ownerName, userId);
mOwners.writeDeviceOwner();
updateDeviceOwnerLocked();
- setDeviceOwnerSystemPropertyLocked();
+ setDeviceOwnershipSystemPropertyLocked();
mInjector.binderWithCleanCallingIdentity(() -> {
// Restrict adding a managed profile when a device owner is set on the device.
@@ -9076,21 +9081,21 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return getApplicationLabel(profileOwner.getPackageName(), userHandle);
}
+ private @UserIdInt int getOrganizationOwnedProfileUserId() {
+ for (UserInfo ui : mUserManagerInternal.getUserInfos()) {
+ if (ui.isManagedProfile() && isProfileOwnerOfOrganizationOwnedDevice(ui.id)) {
+ return ui.id;
+ }
+ }
+ return UserHandle.USER_NULL;
+ }
+
@Override
public boolean isOrganizationOwnedDeviceWithManagedProfile() {
if (!mHasFeature) {
return false;
}
-
- return mInjector.binderWithCleanCallingIdentity(() -> {
- for (UserInfo ui : mUserManager.getUsers()) {
- if (ui.isManagedProfile() && isProfileOwnerOfOrganizationOwnedDevice(ui.id)) {
- return true;
- }
- }
-
- return false;
- });
+ return getOrganizationOwnedProfileUserId() != UserHandle.USER_NULL;
}
@Override
@@ -12062,7 +12067,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
synchronized (getLockObject()) {
// Set PROPERTY_DEVICE_OWNER_PRESENT, for the SUW case where setting the property
// is delayed until device is marked as provisioned.
- setDeviceOwnerSystemPropertyLocked();
+ setDeviceOwnershipSystemPropertyLocked();
}
} else if (mDefaultImeChanged.equals(uri)) {
synchronized (getLockObject()) {
@@ -13687,6 +13692,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
});
}
+ private boolean canStartSecurityLogging() {
+ synchronized (getLockObject()) {
+ return isOrganizationOwnedDeviceWithManagedProfile()
+ || areAllUsersAffiliatedWithDeviceLocked();
+ }
+ }
+
+ private @UserIdInt int getSecurityLoggingEnabledUser() {
+ synchronized (getLockObject()) {
+ if (mOwners.hasDeviceOwner()) {
+ return UserHandle.USER_ALL;
+ }
+ }
+ return getOrganizationOwnedProfileUserId();
+ }
+
@Override
public void setSecurityLoggingEnabled(ComponentName admin, boolean enabled) {
if (!mHasFeature) {
@@ -13695,13 +13716,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Objects.requireNonNull(admin);
synchronized (getLockObject()) {
- getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ getActiveAdminForCallerLocked(admin,
+ DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
if (enabled == mInjector.securityLogGetLoggingEnabledProperty()) {
return;
}
mInjector.securityLogSetLoggingEnabledProperty(enabled);
if (enabled) {
- mSecurityLogMonitor.start();
+ mSecurityLogMonitor.start(getSecurityLoggingEnabledUser());
maybePauseDeviceWideLoggingLocked();
} else {
mSecurityLogMonitor.stop();
@@ -13723,7 +13745,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
synchronized (getLockObject()) {
if (!isCallerWithSystemUid()) {
Objects.requireNonNull(admin);
- getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ getActiveAdminForCallerLocked(admin,
+ DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
}
return mInjector.securityLogGetLoggingEnabledProperty();
}
@@ -13747,7 +13770,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
Objects.requireNonNull(admin);
- ensureDeviceOwnerAndAllUsersAffiliated(admin);
+ enforceDeviceOwnerOrProfileOwnerOnOrganizationOwnedDevice(admin);
+ if (!isOrganizationOwnedDeviceWithManagedProfile()) {
+ ensureAllUsersAffiliated();
+ }
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.RETRIEVE_PRE_REBOOT_SECURITY_LOGS)
@@ -13763,6 +13789,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
ArrayList<SecurityEvent> output = new ArrayList<SecurityEvent>();
try {
SecurityLog.readPreviousEvents(output);
+ int enabledUser = getSecurityLoggingEnabledUser();
+ if (enabledUser != UserHandle.USER_ALL) {
+ SecurityLog.redactEvents(output, enabledUser);
+ }
return new ParceledListSlice<SecurityEvent>(output);
} catch (IOException e) {
Slog.w(LOG_TAG, "Fail to read previous events" , e);
@@ -13777,7 +13807,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
Objects.requireNonNull(admin);
- ensureDeviceOwnerAndAllUsersAffiliated(admin);
+ enforceDeviceOwnerOrProfileOwnerOnOrganizationOwnedDevice(admin);
+ if (!isOrganizationOwnedDeviceWithManagedProfile()) {
+ ensureAllUsersAffiliated();
+ }
if (!mInjector.securityLogGetLoggingEnabledProperty()) {
return null;
@@ -14303,26 +14336,34 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@GuardedBy("getLockObject()")
private void maybePauseDeviceWideLoggingLocked() {
if (!areAllUsersAffiliatedWithDeviceLocked()) {
- Slog.i(LOG_TAG, "There are unaffiliated users, security and network logging will be "
+ Slog.i(LOG_TAG, "There are unaffiliated users, network logging will be "
+ "paused if enabled.");
- mSecurityLogMonitor.pause();
if (mNetworkLogger != null) {
mNetworkLogger.pause();
}
+ if (!isOrganizationOwnedDeviceWithManagedProfile()) {
+ Slog.i(LOG_TAG, "Not org-owned managed profile device, security logging will be "
+ + "paused if enabled.");
+ mSecurityLogMonitor.pause();
+ }
}
}
/** Resumes security and network logging (if they are enabled) if all users are affiliated */
@GuardedBy("getLockObject()")
private void maybeResumeDeviceWideLoggingLocked() {
- if (areAllUsersAffiliatedWithDeviceLocked()) {
- mInjector.binderWithCleanCallingIdentity(() -> {
+ boolean allUsersAffiliated = areAllUsersAffiliatedWithDeviceLocked();
+ boolean orgOwnedProfileDevice = isOrganizationOwnedDeviceWithManagedProfile();
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ if (allUsersAffiliated || orgOwnedProfileDevice) {
mSecurityLogMonitor.resume();
+ }
+ if (allUsersAffiliated) {
if (mNetworkLogger != null) {
mNetworkLogger.resume();
}
- });
- }
+ }
+ });
}
/** Deletes any security and network logs that might have been collected so far */
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
index 1ab3b98ae78d..3c445ca78520 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
@@ -51,15 +51,17 @@ class SecurityLogMonitor implements Runnable {
private final Lock mLock = new ReentrantLock();
+ private int mEnabledUser;
+
SecurityLogMonitor(DevicePolicyManagerService service) {
this(service, 0 /* id */);
}
@VisibleForTesting
SecurityLogMonitor(DevicePolicyManagerService service, long id) {
- this.mService = service;
- this.mId = id;
- this.mLastForceNanos = System.nanoTime();
+ mService = service;
+ mId = id;
+ mLastForceNanos = System.nanoTime();
}
private static final boolean DEBUG = false; // STOPSHIP if true.
@@ -136,8 +138,15 @@ class SecurityLogMonitor implements Runnable {
@GuardedBy("mForceSemaphore")
private long mLastForceNanos = 0;
- void start() {
- Slog.i(TAG, "Starting security logging.");
+ /**
+ * Start security logging.
+ *
+ * @param enabledUser which user logging is enabled on, or USER_ALL to enable logging for all
+ * users on the device.
+ */
+ void start(int enabledUser) {
+ Slog.i(TAG, "Starting security logging for user " + enabledUser);
+ mEnabledUser = enabledUser;
SecurityLog.writeEvent(SecurityLog.TAG_LOGGING_STARTED);
mLock.lock();
try {
@@ -286,7 +295,7 @@ class SecurityLogMonitor implements Runnable {
break;
}
}
-
+ SecurityLog.redactEvents(newLogs, mEnabledUser);
if (DEBUG) Slog.d(TAG, "Got " + newLogs.size() + " new events.");
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index dbf2f14146fb..ac818ea8385f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -3952,13 +3952,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
public void testIsOrganizationOwnedDevice() throws Exception {
- setupProfileOwner();
// Set up the user manager to return correct user info
- UserInfo managedProfileUserInfo = new UserInfo(DpmMockContext.CALLER_USER_HANDLE,
- "managed profile",
- UserInfo.FLAG_MANAGED_PROFILE);
- when(getServices().userManager.getUsers())
- .thenReturn(Arrays.asList(managedProfileUserInfo));
+ addManagedProfile(admin1, DpmMockContext.CALLER_UID, admin1);
// Any caller should be able to call this method.
assertFalse(dpm.isOrganizationOwnedDeviceWithManagedProfile());
@@ -5909,8 +5904,6 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
private void configureProfileOwnerOfOrgOwnedDevice(ComponentName who, int userId) {
- when(getServices().userManager.getProfileParent(eq(UserHandle.of(userId))))
- .thenReturn(UserHandle.SYSTEM);
final long ident = mServiceContext.binder.clearCallingIdentity();
mServiceContext.binder.callingUid = UserHandle.getUid(userId, DpmMockContext.SYSTEM_UID);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 37d40811571f..01f1a3e92f2c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -265,6 +265,9 @@ public class MockSystemServices {
.toArray();
}
);
+ when(userManagerInternal.getUserInfos()).thenReturn(
+ mUserInfos.toArray(new UserInfo[mUserInfos.size()]));
+
when(accountManager.getAccountsAsUser(anyInt())).thenReturn(new Account[0]);
// Create a data directory.
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/SecurityEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/SecurityEventTest.java
index 0f0521298641..8dcf21f9fe77 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/SecurityEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/SecurityEventTest.java
@@ -1,31 +1,43 @@
package com.android.server.devicepolicy;
import static android.app.admin.SecurityLog.TAG_ADB_SHELL_CMD;
+import static android.app.admin.SecurityLog.TAG_APP_PROCESS_START;
+import static android.app.admin.SecurityLog.TAG_CERT_AUTHORITY_INSTALLED;
+import static android.app.admin.SecurityLog.TAG_CERT_AUTHORITY_REMOVED;
+import static android.app.admin.SecurityLog.TAG_KEY_DESTRUCTION;
+import static android.app.admin.SecurityLog.TAG_KEY_GENERATED;
+import static android.app.admin.SecurityLog.TAG_KEY_IMPORT;
+import static android.app.admin.SecurityLog.TAG_KEY_INTEGRITY_VIOLATION;
+import static android.app.admin.SecurityLog.TAG_MEDIA_MOUNT;
+import static android.app.admin.SecurityLog.TAG_MEDIA_UNMOUNT;
import android.app.admin.SecurityLog.SecurityEvent;
import android.os.Parcel;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.EventLog;
+import android.util.EventLog.Event;
+
+import junit.framework.AssertionFailedError;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
-@SmallTest
public class SecurityEventTest extends DpmTestBase {
- private static long ID = 549;
- private static String DATA = "adb shell some_command";
- public void testSecurityEventId() {
- SecurityEvent event = buildSecurityEvents(1 /* generate a single event */, ID).get(0);
- assertEquals(ID, event.getId());
+ public void testSecurityEventId() throws Exception {
+ SecurityEvent event = createEvent(() -> {
+ EventLog.writeEvent(TAG_ADB_SHELL_CMD, 0);
+ }, TAG_ADB_SHELL_CMD);
event.setId(20);
assertEquals(20, event.getId());
}
- public void testSecurityEventParceling() {
+ public void testSecurityEventParceling() throws Exception {
// GIVEN an event.
- SecurityEvent event = buildSecurityEvents(1 /* generate a single event */, ID).get(0);
+ SecurityEvent event = createEvent(() -> {
+ EventLog.writeEvent(TAG_ADB_SHELL_CMD, "test");
+ }, TAG_ADB_SHELL_CMD);
// WHEN parceling the event.
Parcel p = Parcel.obtain();
p.writeParcelable(event, 0);
@@ -39,23 +51,104 @@ public class SecurityEventTest extends DpmTestBase {
assertEquals(event.getId(), unparceledEvent.getId());
}
- private List<SecurityEvent> buildSecurityEvents(int numEvents, long id) {
- // Write an event to the EventLog.
- for (int i = 0; i < numEvents; i++) {
- EventLog.writeEvent(TAG_ADB_SHELL_CMD, DATA + "_" + i);
- }
- List<EventLog.Event> events = new ArrayList<>();
- try {
- EventLog.readEvents(new int[]{TAG_ADB_SHELL_CMD}, events);
- } catch (IOException e) {
- fail("Reading a test event from storage failed: " + e);
- }
- assertTrue("Unexpected number of events read from the log.", events.size() >= numEvents);
- // Read events generated by test, from the end of the log.
- List<SecurityEvent> securityEvents = new ArrayList<>();
- for (int i = events.size() - numEvents; i < events.size(); i++) {
- securityEvents.add(new SecurityEvent(id++, events.get(i).getBytes()));
+ public void testSecurityEventRedaction() throws Exception {
+ SecurityEvent event;
+
+ // TAG_ADB_SHELL_CMD will has the command redacted
+ event = createEvent(() -> {
+ EventLog.writeEvent(TAG_ADB_SHELL_CMD, "command");
+ }, TAG_ADB_SHELL_CMD);
+ assertFalse(TextUtils.isEmpty((String) event.getData()));
+
+ // TAG_MEDIA_MOUNT will have the volume label redacted (second data)
+ event = createEvent(() -> {
+ EventLog.writeEvent(TAG_MEDIA_MOUNT, new Object[] {"path", "label"});
+ }, TAG_MEDIA_MOUNT);
+ assertFalse(TextUtils.isEmpty(event.getStringData(1)));
+ assertTrue(TextUtils.isEmpty(event.redact(0).getStringData(1)));
+
+ // TAG_MEDIA_UNMOUNT will have the volume label redacted (second data)
+ event = createEvent(() -> {
+ EventLog.writeEvent(TAG_MEDIA_UNMOUNT, new Object[] {"path", "label"});
+ }, TAG_MEDIA_UNMOUNT);
+ assertFalse(TextUtils.isEmpty(event.getStringData(1)));
+ assertTrue(TextUtils.isEmpty(event.redact(0).getStringData(1)));
+
+ // TAG_APP_PROCESS_START will be fully redacted if user does not match
+ event = createEvent(() -> {
+ EventLog.writeEvent(TAG_APP_PROCESS_START, new Object[] {"process", 12345L,
+ UserHandle.getUid(10, 123), 456, "seinfo", "hash"});
+ }, TAG_APP_PROCESS_START);
+ assertNotNull(event.redact(10));
+ assertNull(event.redact(11));
+
+ // TAG_CERT_AUTHORITY_INSTALLED will be fully redacted if user does not match
+ event = createEvent(() -> {
+ EventLog.writeEvent(TAG_CERT_AUTHORITY_INSTALLED, new Object[] {1, "subject", 10});
+ }, TAG_CERT_AUTHORITY_INSTALLED);
+ assertNotNull(event.redact(10));
+ assertNull(event.redact(11));
+
+ // TAG_CERT_AUTHORITY_REMOVED will be fully redacted if user does not match
+ event = createEvent(() -> {
+ EventLog.writeEvent(TAG_CERT_AUTHORITY_REMOVED, new Object[] {1, "subject", 20});
+ }, TAG_CERT_AUTHORITY_REMOVED);
+ assertNotNull(event.redact(20));
+ assertNull(event.redact(0));
+
+ // TAG_KEY_GENERATED will be fully redacted if user does not match
+ event = createEvent(() -> {
+ EventLog.writeEvent(TAG_KEY_GENERATED,
+ new Object[] {1, "alias", UserHandle.getUid(0, 123)});
+ }, TAG_KEY_GENERATED);
+ assertNotNull(event.redact(0));
+ assertNull(event.redact(10));
+
+ // TAG_KEY_IMPORT will be fully redacted if user does not match
+ event = createEvent(() -> {
+ EventLog.writeEvent(TAG_KEY_IMPORT,
+ new Object[] {1, "alias", UserHandle.getUid(1, 123)});
+ }, TAG_KEY_IMPORT);
+ assertNotNull(event.redact(1));
+ assertNull(event.redact(10));
+
+ // TAG_KEY_DESTRUCTION will be fully redacted if user does not match
+ event = createEvent(() -> {
+ EventLog.writeEvent(TAG_KEY_DESTRUCTION,
+ new Object[] {1, "alias", UserHandle.getUid(2, 123)});
+ }, TAG_KEY_DESTRUCTION);
+ assertNotNull(event.redact(2));
+ assertNull(event.redact(10));
+
+ // TAG_KEY_INTEGRITY_VIOLATION will be fully redacted if user does not match
+ event = createEvent(() -> {
+ EventLog.writeEvent(TAG_KEY_INTEGRITY_VIOLATION,
+ new Object[] {"alias", UserHandle.getUid(2, 123)});
+ }, TAG_KEY_INTEGRITY_VIOLATION);
+ assertNotNull(event.redact(2));
+ assertNull(event.redact(10));
+
+ }
+
+ /**
+ * Creates an Event object. Only the native code has the serialization and deserialization logic
+ * so need to actually emit a real log in order to generate the object.
+ */
+ private SecurityEvent createEvent(Runnable generator, int expectedTag) throws Exception {
+ Long markerData = System.currentTimeMillis();
+ EventLog.writeEvent(expectedTag, markerData);
+ generator.run();
+
+ List<Event> events = new ArrayList<>();
+ // Give the message some time to show up in the log
+ Thread.sleep(20);
+ EventLog.readEvents(new int[] {expectedTag}, events);
+
+ for (int i = 0; i < events.size() - 1; i++) {
+ if (markerData.equals(events.get(i).getData())) {
+ return new SecurityEvent(0, events.get(i + 1).getBytes());
+ }
}
- return securityEvents;
+ throw new AssertionFailedError("Unable to locate marker event");
}
}