tldr;
This isnât the way to do this IMO. Instead, figure out how to make something akin to âconfinement: classicâ work for shells on Ubuntu Core.
details
Classic mode was specifically designed to address things like shells and multiplexers for âgeneral snapsâ (eg, tmux by itself as opposed to tmux shipped in the snap). In fact, there is a tmux snap in the store right now that uses classic confinement (I realize it isnât available on Core). Also, the premise of having âgeneralized snaps that interact in arbitrary waysâ goes against snappyâs design-- snaps are very intentionally meant to bundle and to only go outside their application area in very controlled ways.
Letting snaps execute other snaps binaries is a difficult proposition to have the expected usability experience (eg, how do you share files? what about IPC? seccomp? cgroups?) and it needs to be very carefully designed because interfaces are a contract between providers (slots) and consumers (plugs). If we allow something to âplugsâ binaries in /snap/bin from another snap, what is that saying? To me with the way weâve currently defined interfaces is that it means that the plugging side can count on the providing side not changing in terms of invocation or output. Iâm not sure that is tractable in the most general sense. This interface could be a special case and the contract redefined, but to what (ie, this needs design)? How will we make sure that this doesnât degrade the experience? (eg, today, snaps are free to change how their /snap/bin binaries work because nothing (by design) depends on them)
Furthermore, I think we are probably going the wrong direction if we let users dictate in arbitrary, uncontrolled ways âthis snap is allowed to execute this (sub)set of binaries from this other snapâ. Presenting options for interface connections (âthis snap wants to use the network-manager interfaceâ) is one thing (and already a difficult proposition for the user to understand the security implications or the trust being given to the snap), it is quite another to have them enter commands for letting snap âfooâ run â/snap/bin/bar.bazâ, or similar.
If we achieved a proper design, there are several concerns with your idea such as:
- âsnap-confine would check if it is invoked from a snapâ - how would it do that securely? A secure design would have to leverage LSM labeling, but that wouldnât be cross-platform atm
- IMO this crosses a line on exec() mediation I donât think we want to cross. Up until now, snap-confine consumes files to setup a sandbox, but your idea puts snap-confine right at the heart of exec() mediation (ie, âshould I allow exec of this or that?â). If the the LSM is already involved for the security label, the snap-confine logic could be greatly simplified to make an LSM query on if the access should be allowed or not (granted, this is still snap-confine performing mediation, but in a more robust manner). For example, if this were going to be implemented when AppArmor is the LSM, the allowed /snap/bin accesses each get a âPx ruleâ in the security policy of the caller, snap-confine uses the libapparmor API to ask if /snap/bin is allowed, if not, EPERM, then exec
- how do snaps transition from one snapâs mount namespace to another? Ie, if foo exec()s bar.baz, bar.baz must execute within the mount namespace of âbarâ, not âfooâ for security and because âbarâ has no knowledge of âfooâ (how to work with it, etc, etc). However, this presents a usability issue because âfooâ may want to give âbarâ a file it owns. How would it do that? To put in more concrete terms, say the tmux snap has the âhomeâ interface and is allowed to execute âbarâ, but âbarâ does not have the âhomeâ interface. tmux tries to give âbarâ a file, but it cannot. /tmp is in a different namespace, âbarâ confinement doesnât allow sharing, etc. I very strongly feel it would be a mistake to expand 'barâs confinement to include whatever 'tmuxâs might allow
- weâd have to consider the usability and security issues surrounding exec transitions for each LSM mediation type (ie, file, capability, dbus, network, mount, ptrace, signal and unix)
- this design just will not work with seccomp because with seccomp you can only go more restrictive. As such, the tmux snap would really need to have unrestricted seccomp to be able to execute arbitrary /snap/bin commands, because the commands in /snap/bin might plugs anything (eg, running docker under tmux)
- this design also wonât work with device cgroups. If tmux plugs something (eg, a serial-port, something quite reasonable for tmux to do), then tries to run something in /snap/bin that plugs something else, then the /snap/bin/binary will fail because the device isnât in the cgroup for tmux
- there are probably other things Iâm missing since this is all OTOH
You could try to fix the seccomp and cgroup issues by trying:
- if the command is in /snap/bin, snap run asks snapd to execute it on its behalf
- snapd takes the security label of snap run and performs an LSM query to see if it is allowed, and if so, executes it
The security implications for getting snapd involved in this way are pretty scary cause it means this API for snapd has to be bullet-proof to not allow privilege escalation or confinement escape. Furthermore, it completely breaks the parent/child relationship of the caller/callee, breaks process groups, breaks process sessions, etc. It doesnât solve the issues surrounding sharing files and would just be a very poor experience.
But classic confinement doesnât suffer from any of these issues (sharing files isnât totally transparent but at least possible). The big problem with classic confinement is that it doesnât exist on Ubuntu Core. IMHO, the best solution would be figuring out how to address that. Perhaps that is simply a âconfinement: shellâ that operates very much like âclassicâ in terms of sandbox setup except that it is allowed on core, and we restrict its use. This allows tmux, screen, byobu, zsh, csh, etc, etc to work.