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

Commit 0d57fc43 authored by Liz Prucka's avatar Liz Prucka
Browse files

[IntrusionDetection] Refactor the NetworkLogSource to register

callbacks directly to the system service.

Callback may only be created from the system service, so ignoring
unit tests without system service permissions for now.

DataAggregator initialize() is no longer called by the system service,
so added a check in enable().

Bug: 369313906
Test: atest IntrusionDetectionServiceTests
Flag: android.security.afl_api
Change-Id: I6140133a072242ea965ff55e1781074543b4dccb
parent 3c2e67e3
Loading
Loading
Loading
Loading
+10 −23
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import com.android.server.ServiceThread;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

public class DataAggregator {
    private static final String TAG = "IntrusionDetection DataAggregator";
@@ -36,11 +37,10 @@ public class DataAggregator {
    private static final int MSG_DISABLE = 2;

    private static final int STORED_EVENTS_SIZE_LIMIT = 1024;
    private static final IntrusionDetectionAdminReceiver ADMIN_RECEIVER =
            new IntrusionDetectionAdminReceiver();

    private final IntrusionDetectionService mIntrusionDetectionService;
    private final ArrayList<DataSource> mDataSources;
    private final AtomicBoolean mIsLoggingInitialized = new AtomicBoolean(false);

    private Context mContext;
    private List<IntrusionDetectionEvent> mStoredEvents = new ArrayList<>();
@@ -59,30 +59,20 @@ public class DataAggregator {
        mHandler = new EventHandler(looper, this);
    }

    /**
     * Initialize DataSources
     * @return Whether the initialization succeeds.
     */
    public boolean initialize() {
        SecurityLogSource securityLogSource = new SecurityLogSource(mContext, this);
        mDataSources.add(securityLogSource);

        NetworkLogSource networkLogSource = new NetworkLogSource(mContext, this);
        ADMIN_RECEIVER.setNetworkLogEventCallback(networkLogSource);
        mDataSources.add(networkLogSource);

        for (DataSource ds : mDataSources) {
            if (!ds.initialize()) {
                return false;
            }
        }
        return true;
    /** Initialize DataSources */
    private void initialize() {
        mDataSources.add(new SecurityLogSource(mContext, this));
        mDataSources.add(new NetworkLogSource(mContext, this));
    }

    /**
     * Enable the data collection of all DataSources.
     */
    public void enable() {
        if (!mIsLoggingInitialized.get()) {
            initialize();
            mIsLoggingInitialized.set(true);
        }
        mHandlerThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_BACKGROUND,
                /* allowIo */ false);
        mHandlerThread.start();
@@ -111,9 +101,6 @@ public class DataAggregator {
     */
    public void disable() {
        mHandler.obtainMessage(MSG_DISABLE).sendToTarget();
        for (DataSource ds : mDataSources) {
            ds.disable();
        }
    }

    private void onNewSingleData(IntrusionDetectionEvent event) {
+0 −5
Original line number Diff line number Diff line
@@ -17,11 +17,6 @@
package com.android.server.security.intrusiondetection;

public interface DataSource {
    /**
     * Initialize the data source.
     */
    boolean initialize();

    /**
     * Enable the data collection.
     */
+0 −42
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.security.intrusiondetection;

import android.app.admin.DeviceAdminReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Slog;

public class IntrusionDetectionAdminReceiver extends DeviceAdminReceiver {
    private static final String TAG = "IntrusionDetectionAdminReceiver";

    private static NetworkLogSource sNetworkLogSource;

    @Override
    public void onNetworkLogsAvailable(
            Context context, Intent intent, long batchToken, int networkLogsCount) {
        if (sNetworkLogSource != null) {
            sNetworkLogSource.onNetworkLogsAvailable(batchToken);
        } else {
            Slog.w(TAG, "Network log receiver is not initialized");
        }
    }

    public void setNetworkLogEventCallback(NetworkLogSource networkLogSource) {
        sNetworkLogSource = networkLogSource;
    }
}
+95 −82
Original line number Diff line number Diff line
@@ -17,118 +17,131 @@
package com.android.server.security.intrusiondetection;

import android.app.admin.ConnectEvent;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DnsEvent;
import android.app.admin.NetworkEvent;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.net.IIpConnectivityMetrics;
import android.net.INetdEventCallback;
import android.net.metrics.IpConnectivityLog;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.security.intrusiondetection.IntrusionDetectionEvent;
import android.util.Slog;

import java.util.List;
import java.util.stream.Collectors;
import com.android.server.LocalServices;
import com.android.server.net.BaseNetdEventCallback;

import java.util.concurrent.atomic.AtomicBoolean;

public class NetworkLogSource implements DataSource {

    private static final String TAG = "IntrusionDetectionEvent NetworkLogSource";
    private final AtomicBoolean mIsNetworkLoggingEnabled = new AtomicBoolean(false);
    private final PackageManagerInternal mPm;

    private DevicePolicyManager mDpm;
    private ComponentName mAdmin;
    private DataAggregator mDataAggregator;

    public NetworkLogSource(Context context, DataAggregator dataAggregator) {
    private IIpConnectivityMetrics mIpConnectivityMetrics;
    private long mId;

    public NetworkLogSource(Context context, DataAggregator dataAggregator)
            throws SecurityException {
        mDataAggregator = dataAggregator;
        mDpm = context.getSystemService(DevicePolicyManager.class);
        mAdmin = new ComponentName(context, IntrusionDetectionAdminReceiver.class);
        mPm = LocalServices.getService(PackageManagerInternal.class);
        mId = 0;
        initIpConnectivityMetrics();
    }

    @Override
    public boolean initialize() {
        try {
            if (!mDpm.isAdminActive(mAdmin)) {
                Slog.e(TAG, "Admin " + mAdmin.flattenToString() + "is not active admin");
                return false;
            }
        } catch (SecurityException e) {
            Slog.e(TAG, "Security exception in initialize: ", e);
            return false;
        }
        return true;
    private void initIpConnectivityMetrics() {
        mIpConnectivityMetrics =
                (IIpConnectivityMetrics)
                        IIpConnectivityMetrics.Stub.asInterface(
                                ServiceManager.getService(IpConnectivityLog.SERVICE_NAME));
    }

    @Override
    public void enable() {
        enableNetworkLog();
        if (mIsNetworkLoggingEnabled.get()) {
            Slog.w(TAG, "Network logging is already enabled");
            return;
        }
        try {
            if (mIpConnectivityMetrics.addNetdEventCallback(
                    INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY, mNetdEventCallback)) {
                mIsNetworkLoggingEnabled.set(true);
            } else {
                Slog.e(TAG, "Failed to enable network logging; invalid callback");
            }
        } catch (RemoteException e) {
            Slog.e(TAG, "Failed to enable network logging; ", e);
        }
    }

    @Override
    public void disable() {
        disableNetworkLog();
    }

    private void enableNetworkLog() {
        if (!isNetworkLogEnabled()) {
            mDpm.setNetworkLoggingEnabled(mAdmin, true);
        }
        if (!mIsNetworkLoggingEnabled.get()) {
            Slog.w(TAG, "Network logging is already disabled");
            return;
        }
        try {
            if (!mIpConnectivityMetrics.removeNetdEventCallback(
                    INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST)) {

    private void disableNetworkLog() {
        if (isNetworkLogEnabled()) {
            mDpm.setNetworkLoggingEnabled(mAdmin, false);
                mIsNetworkLoggingEnabled.set(false);
            } else {
                Slog.e(TAG, "Failed to enable network logging; invalid callback");
            }
        } catch (RemoteException e) {
            Slog.e(TAG, "Failed to disable network logging; ", e);
        }

    private boolean isNetworkLogEnabled() {
        return mDpm.isNetworkLoggingEnabled(mAdmin);
    }

    /**
     * Retrieve network logs when onNetworkLogsAvailable callback is received.
     *
     * @param batchToken The token representing the current batch of network logs.
     */
    public void onNetworkLogsAvailable(long batchToken) {
        List<NetworkEvent> events;
        try {
            events = mDpm.retrieveNetworkLogs(mAdmin, batchToken);
        } catch (SecurityException e) {
            Slog.e(
                    TAG,
                    "Admin "
                            + mAdmin.flattenToString()
                            + "does not have permission to retrieve network logs",
                    e);
            return;
        }
        if (events == null) {
            if (!isNetworkLogEnabled()) {
                Slog.w(TAG, "Network logging is disabled");
    private void incrementEventID() {
        if (mId == Long.MAX_VALUE) {
            Slog.i(TAG, "Reached maximum id value; wrapping around.");
            mId = 0;
        } else {
                Slog.e(TAG, "Invalid batch token: " + batchToken);
            mId++;
        }
            return;
    }

        List<IntrusionDetectionEvent> intrusionDetectionEvents =
                events.stream()
                        .filter(event -> event != null)
                        .map(event -> toIntrusionDetectionEvent(event))
                        .collect(Collectors.toList());
        mDataAggregator.addBatchData(intrusionDetectionEvents);
    private final INetdEventCallback mNetdEventCallback =
            new BaseNetdEventCallback() {
                @Override
                public void onDnsEvent(
                        int netId,
                        int eventType,
                        int returnCode,
                        String hostname,
                        String[] ipAddresses,
                        int ipAddressesCount,
                        long timestamp,
                        int uid) {
                    if (!mIsNetworkLoggingEnabled.get()) {
                        return;
                    }
                    DnsEvent dnsEvent =
                            new DnsEvent(
                                    hostname,
                                    ipAddresses,
                                    ipAddressesCount,
                                    mPm.getNameForUid(uid),
                                    timestamp);
                    dnsEvent.setId(mId);
                    incrementEventID();
                    mDataAggregator.addSingleData(new IntrusionDetectionEvent(dnsEvent));
                }

    private IntrusionDetectionEvent toIntrusionDetectionEvent(NetworkEvent event) {
        if (event instanceof DnsEvent) {
            DnsEvent dnsEvent = (DnsEvent) event;
            return new IntrusionDetectionEvent(dnsEvent);
        } else if (event instanceof ConnectEvent) {
            ConnectEvent connectEvent = (ConnectEvent) event;
            return new IntrusionDetectionEvent(connectEvent);
                @Override
                public void onConnectEvent(String ipAddr, int port, long timestamp, int uid) {
                    if (!mIsNetworkLoggingEnabled.get()) {
                        return;
                    }
        throw new IllegalArgumentException(
                "Invalid event type with ID: "
                        + event.getId()
                        + "from package: "
                        + event.getPackageName());
                    ConnectEvent connectEvent =
                            new ConnectEvent(ipAddr, port, mPm.getNameForUid(uid), timestamp);
                    connectEvent.setId(mId);
                    incrementEventID();
                    mDataAggregator.addSingleData(new IntrusionDetectionEvent(connectEvent));
                }
            };
}
+4 −17
Original line number Diff line number Diff line
@@ -43,26 +43,9 @@ public class SecurityLogSource implements DataSource {
        mDataAggregator = dataAggregator;
        mDpm = context.getSystemService(DevicePolicyManager.class);
        mExecutor = Executors.newSingleThreadExecutor();
    }

    @Override
    public boolean initialize() {
        // Confirm caller is system and the device is managed. Otherwise logs will
        // be redacted.
        try {
            if (!mDpm.isDeviceManaged()) {
                Slog.e(TAG, "Caller does not have device owner permissions");
                return false;
            }
        } catch (SecurityException e) {
            Slog.e(TAG, "Security exception in initialize: ", e);
            return false;
        }
        mEventCallback = new SecurityEventCallback();
        return true;
    }


    @Override
    @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
    public void enable() {
@@ -99,6 +82,10 @@ public class SecurityLogSource implements DataSource {

        @Override
        public void accept(List<SecurityEvent> events) {
            if (events.size() == 0) {
                Slog.w(TAG, "No events received; caller may not be authorized");
                return;
            }
            List<IntrusionDetectionEvent> intrusionDetectionEvents =
                    events.stream()
                            .filter(event -> event != null)
Loading