diff options
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"); } } |