-
Notifications
You must be signed in to change notification settings - Fork 610
interfaces: add a steam-support interface #11708
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d4ebee5
bbda62d
bad1e75
1cdd307
57cf149
a1e2623
7fe5326
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | |
---|---|---|---|
@@ -0,0 +1,214 @@ | |||
// -*- Mode: Go; indent-tabs-mode: t -*- | |||
|
|||
/* | |||
* Copyright (C) 2022 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 | |||
|
|||
const steamSupportSummary = `allow Steam to configure pressure-vessel containers` | |||
|
|||
const steamSupportBaseDeclarationPlugs = ` | |||
steam-support: | |||
allow-installation: false | |||
deny-auto-connection: true | |||
` | |||
|
|||
const steamSupportBaseDeclarationSlots = ` | |||
steam-support: | |||
allow-installation: | |||
slot-snap-type: | |||
- core | |||
deny-auto-connection: true | |||
` | |||
|
|||
const steamSupportConnectedPlugAppArmor = ` | |||
# Allow pressure-vessel to set up its Bubblewrap sandbox. | |||
/sys/kernel/ r, | |||
@{PROC}/sys/kernel/overflowuid r, | |||
@{PROC}/sys/kernel/overflowgid r, | |||
@{PROC}/sys/kernel/sched_autogroup_enabled r, | |||
@{PROC}/pressure/io r, | |||
owner @{PROC}/@{pid}/uid_map rw, | |||
owner @{PROC}/@{pid}/gid_map rw, | |||
owner @{PROC}/@{pid}/setgroups rw, | |||
owner @{PROC}/@{pid}/mounts r, | |||
owner @{PROC}/@{pid}/mountinfo r, | |||
|
|||
# Create and pivot to the intermediate root | |||
mount options=(rw, rslave) -> /, | |||
mount options=(rw, silent, rslave) -> /, | |||
mount fstype=tmpfs options=(rw, nosuid, nodev) tmpfs -> /tmp/, | |||
mount options=(rw, rbind) /tmp/newroot/ -> /tmp/newroot/, | |||
pivot_root oldroot=/tmp/oldroot/ /tmp/, | |||
|
|||
# Set up sandbox in /newroot | |||
mount options=(rw, rbind) /oldroot/ -> /newroot/, | |||
mount options=(rw, rbind) /oldroot/dev/ -> /newroot/dev/, | |||
mount options=(rw, rbind) /oldroot/etc/ -> /newroot/etc/, | |||
mount options=(rw, rbind) /oldroot/proc/ -> /newroot/proc/, | |||
mount options=(rw, rbind) /oldroot/sys/ -> /newroot/sys/, | |||
mount options=(rw, rbind) /oldroot/tmp/ -> /newroot/tmp/, | |||
mount options=(rw, rbind) /oldroot/var/ -> /newroot/var/, | |||
mount options=(rw, rbind) /oldroot/var/tmp/ -> /newroot/var/tmp/, | |||
mount options=(rw, rbind) /oldroot/usr/ -> /newroot/run/host/usr/, | |||
mount options=(rw, rbind) /oldroot/etc/ -> /newroot/run/host/etc/, | |||
mount options=(rw, rbind) /oldroot/usr/lib/os-release -> /newroot/run/host/os-release, | |||
|
|||
# Bubblewrap performs remounts on directories it binds under /newroot | |||
# to fix up the options (since options other than MS_REC are ignored | |||
# when performing a bind mount). Ideally we could do something like: | |||
# remount options=(bind, silent, nosuid, *) /newroot/{,**}, | |||
# | |||
# But that is not supported by AppArmor. So we enumerate the possible | |||
# combinations of options Bubblewrap might use. | |||
remount options=(bind, silent, nosuid, rw) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, nodev) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, noexec) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, nodev, noexec) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, noatime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, nodev, noatime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, noexec, noatime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, nodev, noexec, noatime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, relatime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, nodev, relatime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, noexec, relatime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, nodev, noexec, relatime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, nodev, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, noexec, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, nodev, noexec, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, noatime, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, nodev, noatime, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, noexec, noatime, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, nodev, noexec, noatime, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, relatime, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, nodev, relatime, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, noexec, relatime, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, rw, nodev, noexec, relatime, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, nodev) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, noexec) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, nodev, noexec) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, noatime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, nodev, noatime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, noexec, noatime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, nodev, noexec, noatime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, relatime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, nodev, relatime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, noexec, relatime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, nodev, noexec, relatime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, nodev, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, noexec, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, nodev, noexec, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, noatime, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, nodev, noatime, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, noexec, noatime, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, nodev, noexec, noatime, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, relatime, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, nodev, relatime, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, noexec, relatime, nodiratime) /newroot/{,**}, | |||
remount options=(bind, silent, nosuid, ro, nodev, noexec, relatime, nodiratime) /newroot/{,**}, | |||
|
|||
/newroot/** rwkl, | |||
/bindfile* rw, | |||
mount options=(rw, rbind) /oldroot/home/** -> /newroot/home/**, | |||
mount options=(rw, rbind) /oldroot/snap/** -> /newroot/snap/**, | |||
mount options=(rw, rbind) /oldroot/home/**/usr/ -> /newroot/usr/, | |||
mount options=(rw, rbind) /oldroot/home/**/usr/etc/** -> /newroot/etc/**, | |||
mount options=(rw, rbind) /oldroot/home/**/usr/etc/ld.so.cache -> /newroot/run/pressure-vessel/ldso/runtime-ld.so.cache, | |||
mount options=(rw, rbind) /oldroot/home/**/usr/etc/ld.so.conf -> /newroot/run/pressure-vessel/ldso/runtime-ld.so.conf, | |||
|
|||
mount options=(rw, rbind) /oldroot/etc/machine-id -> /newroot/etc/machine-id, | |||
mount options=(rw, rbind) /oldroot/etc/group -> /newroot/etc/group, | |||
mount options=(rw, rbind) /oldroot/etc/passwd -> /newroot/etc/passwd, | |||
mount options=(rw, rbind) /oldroot/etc/host.conf -> /newroot/etc/host.conf, | |||
mount options=(rw, rbind) /oldroot/etc/hosts -> /newroot/etc/hosts, | |||
mount options=(rw, rbind) /oldroot/**/*resolv.conf -> /newroot/etc/resolv.conf, | |||
mount options=(rw, rbind) /bindfile* -> /newroot/etc/timezone, | |||
|
|||
mount options=(rw, rbind) /oldroot/run/systemd/journal/socket -> /newroot/run/systemd/journal/socket, | |||
mount options=(rw, rbind) /oldroot/run/systemd/journal/stdout -> /newroot/run/systemd/journal/stdout, | |||
|
|||
mount options=(rw, rbind) /oldroot/usr/share/fonts/ -> /newroot/run/host/fonts/, | |||
mount options=(rw, rbind) /oldroot/usr/local/share/fonts/ -> /newroot/run/host/local-fonts/, | |||
mount options=(rw, rbind) /oldroot/{var/cache/fontconfig,usr/lib/fontconfig/cache}/ -> /newroot/run/host/fonts-cache/, | |||
mount options=(rw, rbind) /oldroot/home/**/.cache/fontconfig/ -> /newroot/run/host/user-fonts-cache/, | |||
mount options=(rw, rbind) /bindfile* -> /newroot/run/host/font-dirs.xml, | |||
|
|||
mount options=(rw, rbind) /oldroot/usr/share/icons/ -> /newroot/run/host/share/icons/, | |||
mount options=(rw, rbind) /oldroot/home/**/.local/share/icons/ -> /newroot/run/host/user-share/icons/, | |||
|
|||
mount options=(rw, rbind) /oldroot/run/user/[0-9]*/wayland-* -> /newroot/run/pressure-vessel/wayland-*, | |||
mount options=(rw, rbind) /oldroot/tmp/.X11-unix/X* -> /newroot/tmp/.X11-unix/X99, | |||
mount options=(rw, rbind) /bindfile* -> /newroot/run/pressure-vessel/Xauthority, | |||
|
|||
mount options=(rw, rbind) /bindfile* -> /newroot/run/pressure-vessel/pulse/config, | |||
mount options=(rw, rbind) /oldroot/run/user/[0-9]*/pulse/native -> /newroot/run/pressure-vessel/pulse/native, | |||
mount options=(rw, rbind) /oldroot/dev/snd/ -> /newroot/dev/snd/, | |||
mount options=(rw, rbind) /bindfile* -> /newroot/etc/asound.conf, | |||
mount options=(rw, rbind) /oldroot/run/user/[0-9]*/bus -> /newroot/run/pressure-vessel/bus, | |||
|
|||
mount options=(rw, rbind) /oldroot/run/dbus/system_bus_socket -> /newroot/run/dbus/system_bus_socket, | |||
mount options=(rw, rbind) /oldroot/run/systemd/resolve/io.systemd.Resolve -> /newroot/run/systemd/resolve/io.systemd.Resolve, | |||
mount options=(rw, rbind) /bindfile* -> /newroot/run/host/container-manager, | |||
|
|||
# Allow masking of certain directories in the sandbox | |||
mount fstype=tmpfs options=(rw, nosuid, nodev) tmpfs -> /newroot/home/*/snap/steam/common/.local/share/vulkan/implicit_layer.d/, | |||
mount fstype=tmpfs options=(rw, nosuid, nodev) tmpfs -> /newroot/run/pressure-vessel/ldso/, | |||
mount fstype=tmpfs options=(rw, nosuid, nodev) tmpfs -> /newroot/tmp/.X11-unix/, | |||
|
|||
# Pivot from the intermediate root to sandbox root | |||
mount options in (rw, silent, rprivate) -> /oldroot/, | |||
umount /oldroot/, | |||
pivot_root oldroot=/newroot/ /newroot/, | |||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I learnt something new today! Nice! :-) |
|||
umount /, | |||
|
|||
# Permissions needed within sandbox root | |||
/usr/lib/pressure-vessel/** ixr, | |||
/run/host/** mr, | |||
/run/pressure-vessel/** mrw, | |||
/run/host/usr/sbin/ldconfig* ixr, | |||
/run/host/usr/bin/localedef ixr, | |||
/var/cache/ldconfig/** rw, | |||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does the snap does a layout on this one, as it comes from the base afaict? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we followup with that outside of this PR? I'd like @jhenstridge to respond, but we won't get that as timely as we'd like. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah, these are in the pivoted root? but is still mount /var from the snap root, so still not entirely sure how that is writable There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added these rules to handle accesses made by the process within the mount namespace created by pressure-vessel/bubblewrap. Maybe in future this could be separated out into a sub-profile, but that's complicated by the fact that the executables we'd perform the transitions on are downloaded by Steam and may have varying paths. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm still a bit confused how that dir gets writable because the underlying dir comes from the base that is read-only There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we can land this but I would like to understand how that gets writable by Monday. I'm probably missing something but I'm guessing what happens just looking at the rules here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The root of the bubblewrap sandbox is a tmpfs, so any path not mounted over is potentially writeable. It isn't exposing the whole host system |
|||
|
|||
capability sys_admin, | |||
capability sys_ptrace, | |||
capability setpcap, | |||
` | |||
|
|||
const steamSupportConnectedPlugSecComp = ` | |||
# Description: additional permissions needed by Steam | |||
|
|||
# Allow Steam to set up "pressure-vessel" containers to run games in. | |||
mount | |||
umount2 | |||
pivot_root | |||
` | |||
|
|||
func init() { | |||
registerIface(&commonInterface{ | |||
name: "steam-support", | |||
summary: steamSupportSummary, | |||
implicitOnCore: false, | |||
implicitOnClassic: true, | |||
baseDeclarationSlots: steamSupportBaseDeclarationSlots, | |||
baseDeclarationPlugs: steamSupportBaseDeclarationPlugs, | |||
connectedPlugAppArmor: steamSupportConnectedPlugAppArmor, | |||
connectedPlugSecComp: steamSupportConnectedPlugSecComp, | |||
}) | |||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// -*- Mode: Go; indent-tabs-mode: t -*- | ||
|
||
/* | ||
* Copyright (C) 2022 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_test | ||
|
||
import ( | ||
. "gopkg.in/check.v1" | ||
|
||
"github.com/snapcore/snapd/interfaces" | ||
"github.com/snapcore/snapd/interfaces/apparmor" | ||
"github.com/snapcore/snapd/interfaces/builtin" | ||
"github.com/snapcore/snapd/interfaces/seccomp" | ||
"github.com/snapcore/snapd/snap" | ||
"github.com/snapcore/snapd/testutil" | ||
) | ||
|
||
type SteamSupportInterfaceSuite struct { | ||
iface interfaces.Interface | ||
slotInfo *snap.SlotInfo | ||
slot *interfaces.ConnectedSlot | ||
plugInfo *snap.PlugInfo | ||
plug *interfaces.ConnectedPlug | ||
} | ||
|
||
const steamSupportCoreYaml = `name: core | ||
version: 0 | ||
type: os | ||
slots: | ||
steam-support: | ||
` | ||
|
||
const steamSupportConsumerYaml = `name: consumer | ||
version: 0 | ||
apps: | ||
app: | ||
plugs: [steam-support] | ||
` | ||
|
||
var _ = Suite(&SteamSupportInterfaceSuite{ | ||
iface: builtin.MustInterface("steam-support"), | ||
}) | ||
|
||
func (s *SteamSupportInterfaceSuite) SetUpTest(c *C) { | ||
s.plug, s.plugInfo = MockConnectedPlug(c, steamSupportConsumerYaml, nil, "steam-support") | ||
s.slot, s.slotInfo = MockConnectedSlot(c, steamSupportCoreYaml, nil, "steam-support") | ||
} | ||
|
||
func (s *SteamSupportInterfaceSuite) TestName(c *C) { | ||
c.Assert(s.iface.Name(), Equals, "steam-support") | ||
} | ||
|
||
func (s *SteamSupportInterfaceSuite) TestSanitizeSlot(c *C) { | ||
c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) | ||
} | ||
|
||
func (s *SteamSupportInterfaceSuite) TestSanitizePlug(c *C) { | ||
c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) | ||
} | ||
|
||
func (s *SteamSupportInterfaceSuite) TestAppArmorSpec(c *C) { | ||
|
||
spec := &apparmor.Specification{} | ||
c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) | ||
c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) | ||
c.Check(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "mount options=(rw, rbind) /tmp/newroot/ -> /tmp/newroot/,\n") | ||
} | ||
|
||
func (s *SteamSupportInterfaceSuite) TestSecCompSpec(c *C) { | ||
spec := &seccomp.Specification{} | ||
c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) | ||
c.Check(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "# Allow Steam to set up \"pressure-vessel\" containers to run games in.\nmount\numount2\npivot_root\n") | ||
} | ||
|
||
func (s *SteamSupportInterfaceSuite) TestInterfaces(c *C) { | ||
c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -147,6 +147,7 @@ fork | |
ftime | ||
futex | ||
futex_time64 | ||
futex_waitv | ||
get_mempolicy | ||
get_robust_list | ||
get_thread_area | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might save some bytes if we generated this list programmatically, but it's probably not worth the effort (and if even, it should be a follow-up).