Comment 13 for bug 1849753

Revision history for this message
Jamie Strandboge (jdstrand) wrote :

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(1572461604.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,
  /snap/core/8039/usr/lib/snapd/snap-confine ix,
  ^mount-namespace-capture-helper (complain) {
    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(1572463244.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
Oct 30 14:21:56 foo kernel: audit: type=1400 audit(1572463316.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

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/$SNAP_WITH_SNAPD/$SNAP_WITH_SNAPD_REVISION/usr/lib/snapd/snap-confine ix,

2. adjust snap-confine to:

  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.