Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 83 additions & 73 deletions interfaces/builtin/desktop_legacy.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
package builtin

import (
"strings"

"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/apparmor"
)
Expand All @@ -43,13 +45,6 @@ const desktopLegacyConnectedPlugAppArmor = `

# accessibility (a11y)
#include <abstractions/dbus-session-strict>
dbus (send)
bus=session
path=/org/a11y/bus
interface=org.a11y.Bus
member=GetAddress
peer=(label=unconfined),

#include <abstractions/dbus-accessibility-strict>

# Allow access to the non-abstract D-Bus socket used by at-spi > 2.42.0
Expand All @@ -59,72 +54,6 @@ owner /{,var/}run/user/[0-9]*/at-spi/bus* rw,
# Allow access to the socket used by speech-dispatcher
owner /{,var/}run/user/[0-9]*/speech-dispatcher/speechd.sock rw,

# Allow the accessibility services in the user session to send us any events
dbus (receive)
bus=accessibility
peer=(label=unconfined),

# Allow querying for capabilities and registering
dbus (send)
bus=accessibility
path="/org/a11y/atspi/accessible/root"
interface="org.a11y.atspi.Socket"
member="Embed"
peer=(name=org.a11y.atspi.Registry, label=unconfined),
dbus (send)
bus=accessibility
path="/org/a11y/atspi/registry"
interface="org.a11y.atspi.Registry"
member="GetRegisteredEvents"
peer=(name=org.a11y.atspi.Registry, label=unconfined),
dbus (send)
bus=accessibility
path="/org/a11y/atspi/registry/deviceeventcontroller"
interface="org.a11y.atspi.DeviceEventController"
member="Get{DeviceEvent,Keystroke}Listeners"
peer=(name=org.a11y.atspi.Registry, label=unconfined),
dbus (send)
bus=accessibility
path="/org/a11y/atspi/registry/deviceeventcontroller"
interface="org.a11y.atspi.DeviceEventController"
member="NotifyListenersSync"
peer=(name=org.a11y.atspi.Registry, label=unconfined),

# org.a11y.atspi is not designed for application isolation and these rules
# can be used to send change events for other processes.
dbus (send)
bus=accessibility
path="/org/a11y/atspi/accessible/root"
interface="org.a11y.atspi.Event.Object"
member="ChildrenChanged"
peer=(name=org.freedesktop.DBus, label=unconfined),
dbus (send)
bus=accessibility
path="/org/a11y/atspi/accessible/root"
interface="org.a11y.atspi.Accessible"
member="Get*"
peer=(label=unconfined),
dbus (send)
bus=accessibility
path="/org/a11y/atspi/accessible/[0-9]*"
interface="org.a11y.atspi.Event.Object"
member="{ChildrenChanged,PropertyChange,StateChanged,TextCaretMoved}"
peer=(name=org.freedesktop.DBus, label=unconfined),
dbus (send)
bus=accessibility
path="/org/a11y/atspi/accessible/[0-9]*"
interface="org.freedesktop.DBus.Properties"
member="Get{,All}"
peer=(label=unconfined),

dbus (send)
bus=accessibility
path="/org/a11y/atspi/cache"
interface="org.a11y.atspi.Cache"
member="{Add,Remove}Accessible"
peer=(name=org.freedesktop.DBus, label=unconfined),


# ibus
# subset of ibus abstraction
/usr/lib/@{multiarch}/gtk-2.0/[0-9]*/immodules/im-ibus.so mr,
Expand Down Expand Up @@ -404,6 +333,84 @@ dbus (send)
peer=(name=org.freedesktop.DBus, label="{plasmashell,unconfined}"),
`

const accessibilityRules = `
dbus (send)
bus=session
path=/org/a11y/bus
interface=org.a11y.Bus
member=GetAddress
peer=(label=###ACCESSIBILITY-LABEL###),

# Allow the accessibility services in the user session to send us any events
dbus (receive)
bus=accessibility
peer=(label=###ACCESSIBILITY-LABEL###),

# Allow querying for capabilities and registering
dbus (send)
bus=accessibility
path="/org/a11y/atspi/accessible/root"
interface="org.a11y.atspi.Socket"
member="Embed"
peer=(name=org.a11y.atspi.Registry, label=###ACCESSIBILITY-LABEL###),
dbus (send)
bus=accessibility
path="/org/a11y/atspi/registry"
interface="org.a11y.atspi.Registry"
member="GetRegisteredEvents"
peer=(name=org.a11y.atspi.Registry, label=###ACCESSIBILITY-LABEL###),
dbus (send)
bus=accessibility
path="/org/a11y/atspi/registry/deviceeventcontroller"
interface="org.a11y.atspi.DeviceEventController"
member="Get{DeviceEvent,Keystroke}Listeners"
peer=(name=org.a11y.atspi.Registry, label=###ACCESSIBILITY-LABEL###),
dbus (send)
bus=accessibility
path="/org/a11y/atspi/registry/deviceeventcontroller"
interface="org.a11y.atspi.DeviceEventController"
member="NotifyListenersSync"
peer=(name=org.a11y.atspi.Registry, label=###ACCESSIBILITY-LABEL###),

# org.a11y.atspi is not designed for application isolation and these rules
# can be used to send change events for other processes.
dbus (send)
bus=accessibility
path="/org/a11y/atspi/accessible/root"
interface="org.a11y.atspi.Event.Object"
member="ChildrenChanged"
peer=(name=org.freedesktop.DBus, label=###ACCESSIBILITY-LABEL###),
dbus (send)
bus=accessibility
path="/org/a11y/atspi/accessible/root"
interface="org.a11y.atspi.Accessible"
member="Get*"
peer=(label=###ACCESSIBILITY-LABEL###),

# For some reason, this path must be removed to allow snapped applications to work
# with snapped Orca, but not for unconfined Orca.
# path="/org/a11y/atspi/accessible/root"
dbus (send)
bus=accessibility
interface="org.a11y.atspi.Event.Object"
member="{ChildrenChanged,PropertyChange,StateChanged,TextCaretMoved}"
peer=(name=org.freedesktop.DBus, label=###ACCESSIBILITY-LABEL###),

dbus (send)
bus=accessibility
path="/org/a11y/atspi/accessible/[0-9]*"
interface="org.freedesktop.DBus.Properties"
member="Get{,All}"
peer=(label=###ACCESSIBILITY-LABEL###),

dbus (send)
bus=accessibility
path="/org/a11y/atspi/cache"
interface="org.a11y.atspi.Cache"
member="{Add,Remove}Accessible"
peer=(name=org.freedesktop.DBus, label=###ACCESSIBILITY-LABEL###),
`

const desktopLegacyConnectedPlugSecComp = `
# Description: Can access common desktop legacy methods. This gives privileged
# access to the user's input.
Expand All @@ -419,6 +426,9 @@ type desktopLegacyInterface struct {

func (iface *desktopLegacyInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
spec.AddSnippet(desktopLegacyConnectedPlugAppArmor)
// Add accessibility rules both for unconfined and snap.orca.* labels, since the accessibility service may run under either label.
spec.AddSnippet(strings.Replace(accessibilityRules, "###ACCESSIBILITY-LABEL###", "unconfined", -1))
spec.AddSnippet(strings.Replace(accessibilityRules, "###ACCESSIBILITY-LABEL###", "snap.orca.*", -1))

// the DesktopFileRules can conflict with the rules in other, more privileged,
// interfaces (like desktop-launch), so they are added here with the minimum
Expand Down
91 changes: 91 additions & 0 deletions interfaces/builtin/orca_screen_reader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2026 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package builtin

import (
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/apparmor"
"github.com/snapcore/snapd/snap"
)

const orcaScreenReaderSummary = `special permissions for Orca screen reader`

const orcaScreenReaderBaseDeclarationSlots = `
orca-screen-reader:
allow-installation:
slot-snap-type:
- core
deny-connection:
on-classic: false
deny-auto-connection: true
`

const orcaScreenReaderPlugAppArmor = `
#include <abstractions/dbus-session-strict>
#include <abstractions/dbus-accessibility-strict>

network netlink,

# full access to the accessibility bus
dbus (send, receive)
bus=accessibility,

# full access to a11y elements in session bus
dbus (send, receive)
bus=session
path=/org/a11y/bus{,/**},

dbus (bind)
bus=session
name="org.gnome.Orca.Service",

# allow access to the at-spi folder and
# the at-spi1-XXXXX folders
/run/user/[0-9]*/at-spi{,2-[0-9A-Z]*}/ rw,
/run/user/[0-9]*/at-spi{,2-[0-9A-Z]*}/** rwk,
`

type orcaScreenReaderInterface struct{}

func (iface *orcaScreenReaderInterface) Name() string {
return "orca-screen-reader"
}

func (iface *orcaScreenReaderInterface) StaticInfo() interfaces.StaticInfo {
return interfaces.StaticInfo{
Summary: orcaScreenReaderSummary,
ImplicitOnClassic: true,
BaseDeclarationSlots: orcaScreenReaderBaseDeclarationSlots,
}
}

func (iface *orcaScreenReaderInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
spec.AddSnippet(orcaScreenReaderPlugAppArmor)
return nil
}

func (iface *orcaScreenReaderInterface) AutoConnect(*snap.PlugInfo, *snap.SlotInfo) bool {
// allow what declarations allowed
return true
}

func init() {
registerIface(&orcaScreenReaderInterface{})
}
Loading
Loading