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

Commit 521c6409 authored by Garfield Tan's avatar Garfield Tan
Browse files

Add infrastructure for serial port access management

This CL introduces an in-memory access manager that automatically grants
accesses to serial ports listed in the internal config to apps with
the SERIAL_PORT permission for backward compatibility.

This CL also passed package names from the calling apps. This CL only
uses it for exception messages, but they are needed to pull localized
app names for the access request dialog.

Bug: 415125779
Test: atest SerialPortTest
Flag: android.hardware.serial.flags.enable_wired_serial_api
Change-Id: If58741471040f373574758481d9b2f7ae27f02fa
parent af2ef928
Loading
Loading
Loading
Loading
+7 −5
Original line number Diff line number Diff line
@@ -37,8 +37,10 @@ interface ISerialManager {
     * @param flags       open flags {@code SerialPort.OPEN_FLAG_*} that define read/write mode and
     *                    other options.
     * @param exclusive   whether the app needs exclusive access with TIOCEXCL(2const)
     * @param packageName the package name of the calling application
     * @param callback    the receiver of the operation result.
     * @throws IllegalArgumentException if the set of flags is not correct.
     */
    void requestOpen(in String portName, in int flags, in boolean exclusive, in ISerialPortResponseCallback callback);
    void requestOpen(in String portName, in int flags, in boolean exclusive, in String packageName,
            in ISerialPortResponseCallback callback);
}
+3 −1
Original line number Diff line number Diff line
@@ -32,8 +32,10 @@ oneway interface ISerialPortResponseCallback {
        ERROR_READING_DRIVERS = 0,
        // Serial port with the given name does not exist.
        ERROR_PORT_NOT_FOUND = 1,
        // SecurityException due to access denied.
        ERROR_ACCESS_DENIED = 2,
        // ErrnoException while opening the serial port.
        ERROR_OPENING_PORT = 2,
        ERROR_OPENING_PORT = 3,
    }

    /**
+3 −4
Original line number Diff line number Diff line
@@ -41,7 +41,6 @@ import java.util.concurrent.Executor;
public final class SerialManager {
    private static final String TAG = "SerialManager";

    @SuppressWarnings("unused")
    private final @NonNull Context mContext;
    private final @NonNull ISerialManager mService;

@@ -68,7 +67,7 @@ public final class SerialManager {
            List<SerialPortInfo> infos = mService.getSerialPorts();
            List<SerialPort> ports = new ArrayList<>(infos.size());
            for (int i = 0; i < infos.size(); i++) {
                ports.add(new SerialPort(infos.get(i), mService));
                ports.add(new SerialPort(mContext, infos.get(i), mService));
            }
            return Collections.unmodifiableList(ports);
        } catch (RemoteException e) {
@@ -131,7 +130,7 @@ public final class SerialManager {
    private class SerialPortServiceListener extends ISerialPortListener.Stub {
        @Override
        public void onSerialPortConnected(SerialPortInfo info) {
            SerialPort port = new SerialPort(info, mService);
            SerialPort port = new SerialPort(mContext, info, mService);
            synchronized (mLock) {
                for (Map.Entry<SerialPortListener, Executor> e : mListeners.entrySet()) {
                    Executor executor = e.getValue();
@@ -147,7 +146,7 @@ public final class SerialManager {

        @Override
        public void onSerialPortDisconnected(SerialPortInfo info) {
            SerialPort port = new SerialPort(info, mService);
            SerialPort port = new SerialPort(mContext, info, mService);
            synchronized (mLock) {
                for (Map.Entry<SerialPortListener, Executor> e : mListeners.entrySet()) {
                    Executor executor = e.getValue();
+7 −5
Original line number Diff line number Diff line
@@ -23,13 +23,12 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.content.Context;
import android.os.OutcomeReceiver;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.system.ErrnoException;

import com.android.internal.annotations.VisibleForTesting;

import java.io.IOException;
import java.lang.annotation.Retention;
import java.util.Objects;
@@ -92,12 +91,14 @@ public final class SerialPort {
     */
    public static final int OPEN_FLAG_SYNC = 1 << 20;

    private final @NonNull Context mContext;
    private final @NonNull SerialPortInfo mInfo;
    private final @NonNull ISerialManager mService;

    /** @hide */
    @VisibleForTesting
    public SerialPort(@NonNull SerialPortInfo info, @NonNull ISerialManager service) {
    public SerialPort(@NonNull Context context, @NonNull SerialPortInfo info,
            @NonNull ISerialManager service) {
        mContext = context;
        mInfo = info;
        mService = service;
    }
@@ -148,7 +149,7 @@ public final class SerialPort {
        Objects.requireNonNull(executor, "Executor must not be null");
        Objects.requireNonNull(receiver, "Receiver must not be null");
        try {
            mService.requestOpen(mInfo.getName(), flags, exclusive,
            mService.requestOpen(mInfo.getName(), flags, exclusive, mContext.getPackageName(),
                    new SerialPortResponseCallback(executor, receiver));
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
@@ -182,6 +183,7 @@ public final class SerialPort {
            return switch (errorCode) {
                case ErrorCode.ERROR_READING_DRIVERS -> new IOException(message);
                case ErrorCode.ERROR_PORT_NOT_FOUND -> new ErrnoException(message, ENOENT);
                case ErrorCode.ERROR_ACCESS_DENIED -> new SecurityException(message);
                case ErrorCode.ERROR_OPENING_PORT -> new ErrnoException(message, errno);
                default -> new IllegalStateException("Unexpected errorCode " + errorCode);
            };
+97 −29
Original line number Diff line number Diff line
@@ -28,21 +28,26 @@ import static com.android.server.serial.SerialConstants.DEV_DIR;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
import android.hardware.serial.ISerialManager;
import android.hardware.serial.ISerialPortListener;
import android.hardware.serial.ISerialPortResponseCallback;
import android.hardware.serial.ISerialPortResponseCallback.ErrorCode;
import android.hardware.serial.SerialPortInfo;
import android.os.Binder;
import android.os.FileObserver;
import android.os.ParcelFileDescriptor;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserHandle;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.util.Slog;
import android.util.SparseArray;

import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemService;
@@ -68,18 +73,47 @@ public class SerialManagerService extends ISerialManager.Stub {
    @GuardedBy("mLock")
    private final HashMap<String, SerialPortInfo> mSerialPorts = new HashMap<>();

    @GuardedBy("mLock")
    private boolean mIsStarted;

    private final Object mLock = new Object();

    private final Context mContext;

    private final String[] mPortsInConfig;

    @GuardedBy("mLock")
    private final SparseArray<SerialUserAccessManager> mAccessManagerPerUser = new SparseArray<>();

    private final RemoteCallbackList<ISerialPortListener> mListeners = new RemoteCallbackList<>();

    private final SerialDriversDiscovery mSerialDriversDiscovery = new SerialDriversDiscovery();

    private final SerialDevicesEnumerator mSerialDevicesEnumerator = new SerialDevicesEnumerator();

    private SerialManagerService() {}
    @GuardedBy("mLock")
    private boolean mIsStarted;

    private SerialManagerService(Context context) {
        mContext = context;
        mPortsInConfig =
                stripDevPrefix(mContext.getResources().getStringArray(R.array.config_serialPorts));
    }

    private static String[] stripDevPrefix(String[] portPaths) {
        if (portPaths.length == 0) {
            return portPaths;
        }

        final String devDirPrefix = DEV_DIR + "/";
        ArrayList<String> portNames = new ArrayList<>();
        for (int i = 0; i < portPaths.length; ++i) {
            String portPath = portPaths[i];
            if (portPath.startsWith(devDirPrefix)) {
                portNames.add(portPath.substring(devDirPrefix.length()));
            } else {
                Slog.w(TAG, "Skipping port path not under /dev: " + portPath);
            }
        }
        return portNames.toArray(new String[0]);
    }

    @Override
    public List<SerialPortInfo> getSerialPorts() throws RemoteException {
@@ -110,27 +144,56 @@ public class SerialManagerService extends ISerialManager.Stub {

    @Override
    public void requestOpen(@NonNull String portName, int flags, boolean exclusive,
            @NonNull ISerialPortResponseCallback callback) throws RemoteException {
            @NonNull String packageName, @NonNull ISerialPortResponseCallback callback)
            throws RemoteException {
        final int callingPid = Binder.getCallingPid();
        final int callingUid = Binder.getCallingUid();
        final @UserIdInt int userId = UserHandle.getUserId(callingUid);

        synchronized (mLock) {
            try {
                startIfNeeded();
            } catch (IOException e) {
                Slog.e(TAG, "Error reading the list of serial drivers", e);
                callback.onError(ErrorCode.ERROR_READING_DRIVERS, 0, e.getMessage());
                deliverErrorToCallback(
                        callback, ErrorCode.ERROR_READING_DRIVERS, /* errno */ 0, e.getMessage());
                return;
            }
            SerialPortInfo port = mSerialPorts.get(portName);
            if (port == null) {
                try {
                    callback.onError(ErrorCode.ERROR_PORT_NOT_FOUND, 0, portName);
                } catch (RemoteException e) {
                    Slog.e(TAG, "Error sending error to callback", e);
                deliverErrorToCallback(
                        callback, ErrorCode.ERROR_PORT_NOT_FOUND, /* errno */ 0, portName);
                return;
            }
            if (!mAccessManagerPerUser.contains(userId)) {
                mAccessManagerPerUser.put(
                        userId, new SerialUserAccessManager(mContext, mPortsInConfig));
            }
            final SerialUserAccessManager accessManager = mAccessManagerPerUser.get(userId);
            accessManager.requestAccess(portName, callingPid, callingUid,
                    (resultPort, pid, uid, granted) -> {
                        if (!granted) {
                            deliverErrorToCallback(
                                    callback, ErrorCode.ERROR_ACCESS_DENIED, /* errno */ 0,
                                    "User denied " + packageName + " access to " + portName);
                            return;
                        }

                        String path = DEV_DIR + "/" + portName;
                        try {
                FileDescriptor fd = Os.open(path, toOsConstants(flags), /* mode= */ 0);
                            deliverResultToCallback(callback, port,
                                    Os.open(path, toOsConstants(flags), /* mode */ 0));
                        } catch (ErrnoException e) {
                            Slog.e(TAG, "Failed to open " + path, e);
                            deliverErrorToCallback(
                                    callback, ErrorCode.ERROR_OPENING_PORT, e.errno, "open");
                        }
                    });
        }
    }

    private void deliverResultToCallback(
            @NonNull ISerialPortResponseCallback callback, SerialPortInfo port, FileDescriptor fd) {
        try (var pfd = new ParcelFileDescriptor(fd)) {
            callback.onResult(port, pfd);
        } catch (RemoteException | RuntimeException e) {
@@ -138,14 +201,14 @@ public class SerialManagerService extends ISerialManager.Stub {
        } catch (IOException e) {
            Slog.w(TAG, "Error closing the file descriptor", e);
        }
            } catch (ErrnoException e) {
                Slog.e(TAG, "Failed to open " + path, e);
                try {
                    callback.onError(ErrorCode.ERROR_OPENING_PORT, e.errno, "open");
                } catch (RemoteException e2) {
                    Slog.e(TAG, "Error sending error to callback", e2);
                }
    }

    private void deliverErrorToCallback(@NonNull ISerialPortResponseCallback callback,
            @ErrorCode int errorCode, int errno, String message) {
        try {
            callback.onError(errorCode, errno, message);
        } catch (RemoteException e) {
            Slog.e(TAG, "Error sending error to callback", e);
        }
    }

@@ -259,6 +322,9 @@ public class SerialManagerService extends ISerialManager.Stub {
                return;
            }
        }
        for (int i = mAccessManagerPerUser.size() - 1; i >= 0; --i) {
            mAccessManagerPerUser.valueAt(i).onPortRemoved(name);
        }
        Slog.d(TAG, "Removed serial device " + name);
        int n = mListeners.beginBroadcast();
        for (int i = 0; i < n; i++) {
@@ -272,15 +338,17 @@ public class SerialManagerService extends ISerialManager.Stub {
    }

    public static class Lifecycle extends SystemService {
        private final Context mContext;

        public Lifecycle(@NonNull Context context) {
            super(context);
            mContext = context;
        }

        @Override
        public void onStart() {
            if (enableWiredSerialApi()) {
                publishBinderService(Context.SERIAL_SERVICE, new SerialManagerService());
                publishBinderService(Context.SERIAL_SERVICE, new SerialManagerService(mContext));
            }
        }
    }
Loading