Since the issue is that an fd is opened by the first app running in one profile while transitioning to the snap-confine profile, there is an option that would 'work'.
As a POC, I installed the hello-world snap and also created a test-classic snap (just hello-world renamed with 'confinement: classic' and installed with --classic --dangerous). Do nothing else, I then try to reproduce the issue:
and I observe in the logs there is no file_inherit denial.
This 'works' because the profile that snap-confine is running under is the same as the classic snap and therefore has all the same accesses that the snap does (I could've chosen the special 'unconfined', but snap-confine will fail to run in that case).
Interestingly, if I run 'hello-world' from the classic snap:
Now, I say 'works' because I don't care for how the snap-confine policy is circumvented in the POC since a classic snap could then try to exploit bugs in the setuid snap-confine. While one could argue that a classic snap already has root on the system, many people will install classic snaps that run as the user (ie, no daemons) and feel a bit safer, but with the POC policy the snap could, running as the user, try to exploit bugs in snap-confine to gain privileges.
There is possibly an acceptable way, but it would need to be investigated to verify it works and for acceptable safety:
if apparmor enabled:
if unconfined:
if !change_profile(snap_confine_profile) die("snap-confine has elevated permissions and is not confined but should be...."
The idea is, adjust the classic policy to transition to the unconfined profile when calling snap-confine (the $SNAP_WITH_SNAPD* stuff is to avoid conflicting x modifiers with the parser). This should allow snap-confine to have the open fds without file_inherit denials. Then snap-confine tries as early as possible to transition itself to the snap-confine profile, dying if it can't. The kernel may revalidate the fds at change_profile (needs verifying) and change_profile is not as strong as fork/exec profile change, but might be acceptable for this case. snap-confine would be susceptible to LD_PRELOAD issues, but because it is setuid, secure_exec is in effect and areas in the filesystem that the binary would honor with LD_PRELOAD are writable only by root (and if someone runs the classic snap under sudo to circumvent this, they can simply change the host however he/she desires).
An alternative without modifying snap-confine would be to have two snap-confine profiles, one for strict and one for classic, and adjust the classic template to transition to the classic snap-confine template which has rules allowing 'rw' access to files and 'unix' for sockets.
Since the issue is that an fd is opened by the first app running in one profile while transitioning to the snap-confine profile, there is an option that would 'work'.
As a POC, I installed the hello-world snap and also created a test-classic snap (just hello-world renamed with 'confinement: classic' and installed with --classic --dangerous). Do nothing else, I then try to reproduce the issue:
$ test-classic.sh
bash-5.0$ exec 3<> /run/user/$(id -u)/test.fd
bash-5.0$ test-classic.env > /dev/null
bash-5.0$
I see in the logs the familiar denial:
Oct 30 13:53:24 foo kernel: audit: type=1400 audit(157246160 4.648:3444) : apparmor="DENIED" operation= "file_inherit" profile= "/snap/ core/8039/ usr/lib/ snapd/snap- confine" name="/ run/user/ 1000/test. fd" pid=24957 comm="snap-confine" requested_mask="wr" denied_mask="wr" fsuid=1000 ouid=1000
I then updated /var/lib/ snapd/apparmor/ profiles/ snap.test- classic. sh to have:
/usr/ lib/snapd/ snap-confine ix, core/8039/ usr/lib/ snapd/snap- confine ix, namespace- capture- helper (complain) {
/snap/
^mount-
file,
unix,
signal,
}
and tried again:
$ test-classic.sh
bash-5.0$ exec 3<> /run/user/$(id -u)/test.fd
bash-5.0$ test-classic.env > /dev/null
bash-5.0$
and I observe in the logs there is no file_inherit denial.
This 'works' because the profile that snap-confine is running under is the same as the classic snap and therefore has all the same accesses that the snap does (I could've chosen the special 'unconfined', but snap-confine will fail to run in that case).
Interestingly, if I run 'hello-world' from the classic snap:
$ test-classic.sh
bash-5.0$ exec 3<> /run/user/$(id -u)/test.fd
bash-5.0$ test-classic.env > /dev/null
bash-5.0$ hello-world.sh
bash-4.3$ cat /proc/self/fd/3
cat: /proc/self/fd/3: Permission denied
hello-world correctly gets the denials (first is inherit, 2nd /apparmor/.null is how apparmor handles the access to the failed inherit fd):
Oct 30 14:20:44 foo kernel: audit: type=1400 audit(157246324 4.359:3449) : apparmor="DENIED" operation= "file_inherit" profile= "snap.hello- world.sh" name="/ run/user/ 1000/test. fd" pid=26175 comm="snap-exec" requested_mask="wr" denied_mask="wr" fsuid=1000 ouid=1000 6.344:3451) : apparmor="DENIED" operation="open" profile= "snap.hello- world.sh" name="/ apparmor/ .null" pid=26244 comm="cat" requested_mask="r" denied_mask="r" fsuid=1000 ouid=0
Oct 30 14:21:56 foo kernel: audit: type=1400 audit(157246331
Now, I say 'works' because I don't care for how the snap-confine policy is circumvented in the POC since a classic snap could then try to exploit bugs in the setuid snap-confine. While one could argue that a classic snap already has root on the system, many people will install classic snaps that run as the user (ie, no daemons) and feel a bit safer, but with the POC policy the snap could, running as the user, try to exploit bugs in snap-confine to gain privileges.
There is possibly an acceptable way, but it would need to be investigated to verify it works and for acceptable safety:
1. adjust the classic policy to use:
/usr/ lib/snapd/ snap-confine ix, $SNAP_WITH_ SNAPD/$ SNAP_WITH_ SNAPD_REVISION/ usr/lib/ snapd/snap- confine ix,
/snap/
2. adjust snap-confine to:
if apparmor enabled: profile( snap_confine_ profile)
die("snap- confine has elevated permissions and is not confined but should be...."
if unconfined:
if !change_
The idea is, adjust the classic policy to transition to the unconfined profile when calling snap-confine (the $SNAP_WITH_SNAPD* stuff is to avoid conflicting x modifiers with the parser). This should allow snap-confine to have the open fds without file_inherit denials. Then snap-confine tries as early as possible to transition itself to the snap-confine profile, dying if it can't. The kernel may revalidate the fds at change_profile (needs verifying) and change_profile is not as strong as fork/exec profile change, but might be acceptable for this case. snap-confine would be susceptible to LD_PRELOAD issues, but because it is setuid, secure_exec is in effect and areas in the filesystem that the binary would honor with LD_PRELOAD are writable only by root (and if someone runs the classic snap under sudo to circumvent this, they can simply change the host however he/she desires).
An alternative without modifying snap-confine would be to have two snap-confine profiles, one for strict and one for classic, and adjust the classic template to transition to the classic snap-confine template which has rules allowing 'rw' access to files and 'unix' for sockets.