CVE-2021-21367 (Other, published 2021-03-12T17:15Z)
Description: value: Switchboard Bluetooth Plug for elementary OS from version 2.3.0 and before version version 2.3.5 has an incorrect authorization vulnerability. When the Bluetooth plug is running (in discoverable mode), Bluetooth service requests and pairing requests are automatically accepted, allowing physically proximate attackers to pair with a device running an affected version of switchboard-plug-bluetooth without the active consent of the user. By default, elementary OS doesn't expose any services via Bluetooth that allow information to be extracted by paired Bluetooth devices. However, if such services (i.e. contact list sharing software) have been installed, it's possible that attackers have been able to extract data from such services without authorization. If no such services have been installed, attackers are only able to pair with a device running an affected version without authorization and then play audio out of the device or possibly present a HID device (keyboard, mouse, etc...) to control the device. As such, users should check the list of trusted/paired devices and remove any that are not 100% confirmed to be genuine. This is fixed in version 2.3.5. To reduce the likelihood of this vulnerability on an unpatched version, only open the Bluetooth plug for short intervals when absolutely necessary and preferably not in crowded public areas. To mitigate the risk entirely with unpatched versions, do not open the Bluetooth plug within switchboard at all, and use a different method for pairing devices if necessary (e.g. `bluetoothctl` CLI).
Severity: MEDIUM (CVSS v3 base score: 8.1)
Weakness: CWE-863 \u2014 Incorrect Authorization
The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions.
Repository: https://github.com/elementary/switchboard-plug-bluetooth
Files changed:
src/PairDialog.vala
src/Services/Agent.vala
Commit message: Merge pull request from GHSA-5p3g-j69g-w2mq

* Implement unimplemented methods

* Check the dialog response

* Support cancellation

* Add some clarifying comments

* Couple more comments

Vulnerable code:

        response.connect ((response_id) => {
            switch (response_id) {
                case Gtk.ResponseType.ACCEPT:
                    device.pair.begin ();
                    break;
                case Gtk.ResponseType.CANCEL:
                    destroy ();
                    break;
            }
        });

        ((DBusProxy)device).g_properties_changed.connect ((changed, invalid) => {
            var paired = changed.lookup_value ("Paired", new VariantType ("b"));
            if (paired != null && device.paired) {
                destroy ();
            }
        });
    public string request_pin_code (ObjectPath device) throws Error, BluezError {
        return "";
    public void display_pin_code (ObjectPath device, string pincode) throws Error, BluezError {
        var pair_dialog = new PairDialog.display_pin_code (device, pincode, main_window);
    public uint32 request_passkey (ObjectPath device) throws Error, BluezError {
        return 0;
    public void display_passkey (ObjectPath device, uint32 passkey, uint16 entered) throws Error {
        var pair_dialog = new PairDialog.display_passkey (device, passkey, entered, main_window);
    public void request_confirmation (ObjectPath device, uint32 passkey) throws Error, BluezError {
        var pair_dialog = new PairDialog.request_confirmation (device, passkey, main_window);
        pair_dialog.present ();
    public void request_authorization (ObjectPath device) throws Error, BluezError {
        var pair_dialog = new PairDialog.request_authorization (device, main_window);
        pair_dialog.present ();
    public void authorize_service (ObjectPath device, string uuid) throws Error, BluezError {

Fixed code:
    public bool cancelled { get; set; }

                var confirm_button = add_button (_("Pair"), Gtk.ResponseType.ACCEPT);
                confirm_button.get_style_context ().add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION);
                break;
    private PairDialog pair_dialog;

    public async string request_pin_code (ObjectPath device) throws Error, BluezError {
        throw new BluezError.REJECTED ("Pairing method not supported");
    // Called to display a pin code on-screen that needs to be entered on the other device. Can return
    // instantly
    public async void display_pin_code (ObjectPath device, string pincode) throws Error, BluezError {
        pair_dialog = new PairDialog.display_pin_code (device, pincode, main_window);
    public async uint32 request_passkey (ObjectPath device) throws Error, BluezError {
        throw new BluezError.REJECTED ("Pairing method not supported");
    // Called to display a passkey on-screen that needs to be entered on the other device. Can return
    // instantly
    public async void display_passkey (ObjectPath device, uint32 passkey, uint16 entered) throws Error {
        pair_dialog = new PairDialog.display_passkey (device, passkey, entered, main_window);
    // Called to request confirmation from the user that they want to pair with the given device and that
    // the passkey matches. **MUST** throw BluezError if pairing is to be rejected. This is handled in
    // `check_pairing_response`. If the method returns without an error, pairing is authorized
    public async void request_confirmation (ObjectPath device, uint32 passkey) throws Error, BluezError {
        pair_dialog = new PairDialog.request_confirmation (device, passkey, main_window);
        yield check_pairing_response (pair_dialog);
    // Called to request confirmation from the user that they want to pair with the given device
    // **MUST** throw BluezError if pairing is to be rejected. This is handled in `check_pairing_response`
    // If the method returns without an error, pairing is authorized
    public async void request_authorization (ObjectPath device) throws Error, BluezError {
        pair_dialog = new PairDialog.request_authorization (device, main_window);
        yield check_pairing_response (pair_dialog);
    // Called to authorize the use of a specific service (Audio/HID/etc), so we restrict this to paired
    // devices only
    public void authorize_service (ObjectPath device_path, string uuid) throws Error, BluezError {
        var device = get_device_from_object_path (device_path);

        bool paired = device.paired;
        bool trusted = device.trusted;

        // Shouldn't really happen as trusted devices should be automatically authorized, but lets handle it anyway
        if (paired && trusted) {
            // allow
            return;
        }

        // A device that has been paired, but not yet trusted, trust it and allow it to access
        // services
        if (paired && !trusted) {
            device.trusted = true;
            // allow
            return;
        }

        // Reject everything else
        throw new BluezError.REJECTED ("Rejecting service auth, not paired or trusted");
        if (pair_dialog != null) {
            pair_dialog.cancelled = true;
            pair_dialog.destroy ();
        }
    }

    private async void check_pairing_response (PairDialog dialog) throws BluezError {
        SourceFunc callback = check_pairing_response.callback;
        BluezError? error = null;

        dialog.response.connect ((response) => {
            if (response != Gtk.ResponseType.ACCEPT || dialog.cancelled) {
                if (dialog.cancelled) {
                    error = new BluezError.CANCELED ("Pairing cancelled");
                } else {
                    error = new BluezError.REJECTED ("Pairing rejected");
                }
            }

            Idle.add ((owned)callback);
            dialog.destroy ();
        });

        dialog.present ();

        yield;

        if (error != null) {
            throw error;
        }
    }

    private Device get_device_from_object_path (ObjectPath object_path) throws GLib.Error {
        return Bus.get_proxy_sync<Device> (BusType.SYSTEM, "org.bluez", object_path, DBusProxyFlags.GET_INVALIDATED_PROPERTIES);