Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1480ce7b authored by Rubin Xu's avatar Rubin Xu
Browse files

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
parent 1ef019d3
Loading
Loading
Loading
Loading
+33 −23
Original line number Diff line number Diff line
@@ -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
     */
+136 −4
Original line number Diff line number Diff line
@@ -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
+2 −2
Original line number Diff line number Diff line
@@ -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)
+97 −13
Original line number Diff line number Diff line
@@ -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);
+74 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading