Skip to content

Commit

Permalink
cmd/snapd-apparmor: only skip WSL without securityfs
Browse files Browse the repository at this point in the history
Normally securityfs is mounted by systemd on startup. This is done on
non-container systems. People that experiment with WSL and apparmor using a
custom kernel would like to continue to use snapd this way, and to allow that
we need to make our checks tighter.

Earlier this code was refactored to do nothing on all WSL systems. With this
change the behavior on default WSL kernels (with WSL2) is exactly the same.
When non-default kernel is used, and when the user explicitly mounts securityfs
by hand, then snapd.apparmor.service will load the profiles into the kernel as
it does on typical systems.

There's a known issue where this is not using apparmor namespaces, so one
distribution container can load a profile that is visible and effective in
another, but this is outside of the scope of snapd to configure.

I've tested this with a locally-built Linux kernel from the Microsoft WSL
kernel tree  https://github.com/microsoft/WSL2-Linux-Kernel, with the following
configuration:

```
CONFIG_SECURITY_APPARMOR=y
CONFIG_DEFAULT_SECURITY_APPARMOR=y
CONFIG_LSM="landlock,lockdown,yama,loadpin,safesetid,integrity,apparmor,selinux,tomoyo"
```

I've copied the resulting image to my Windows home directory and created
`%USERPROFILE%/.wslconfig` file with the following contents:
t
```
[wsl2]
kernel=C:\\Users\\me\\bzImage
```

Then I've shut down and restarted WSL. Note that normally the released kernel
version is 5.15.something. In the test below you can see 6.6.36 from my local
build.

```
zyga@falka:/mnt/c/Users/me$ uname -a
Linux falka 6.6.36.6-microsoft-standard-WSL2+ #2 SMP PREEMPT_DYNAMIC Mon Sep 23 16:06:53 CEST 2024 x86_64 x86_64 x86_64 GNU/Linux
zyga@falka:/mnt/c/Users/me$ sudo mount -t securityfs securityfs /sys/kernel/security/
[sudo] password for zyga:
zyga@falka:/mnt/c/Users/me$ sudo systemctl restart snapd.apparmor.service
zyga@falka:/mnt/c/Users/me$ sudo systemctl restart snapd
zyga@falka:/mnt/c/Users/me$ systemctl status snapd.apparmor.service
● snapd.apparmor.service - Load AppArmor profiles managed internally by snapd
     Loaded: loaded (/usr/lib/systemd/system/snapd.apparmor.service; enabled; preset: enabled)
     Active: active (exited) since Mon 2024-09-23 18:15:54 CEST; 10s ago
    Process: 523 ExecStart=/usr/lib/snapd/snapd-apparmor start (code=exited, status=0/SUCCESS)
   Main PID: 523 (code=exited, status=0/SUCCESS)

Sep 23 18:15:54 falka systemd[1]: Starting snapd.apparmor.service - Load AppArmor profiles managed internally by snapd...
Sep 23 18:15:54 falka snapd-apparmor[523]: main.go:141: Loading profiles [/var/lib/snapd/apparmor/profiles/snap-confine.snapd.x1 /var/lib/snapd/apparmor/pr>
Sep 23 18:15:54 falka systemd[1]: Finished snapd.apparmor.service - Load AppArmor profiles managed internally by snapd.
zyga@falka:/mnt/c/Users/me$ sudo snap install hello-world
hello-world 6.4 from Canonical✓ installed
zyga@falka:/mnt/c/Users/me$ hello-world
Hello World!
zyga@falka:/mnt/c/Users/me$ hello-world.evil
Hello Evil World!
This example demonstrates the app confinement
You should see a permission denied error next
/snap/hello-world/29/bin/evil: 9: /snap/hello-world/29/bin/evil: cannot create /var/tmp/myevil.txt: Permission denied
```

I've also adjusted unit tests to make the various cases clearer to understand.

Fixes: https://bugs.launchpad.net/snapd/+bug/2081371

Signed-off-by: Zygmunt Krynicki <[email protected]>
  • Loading branch information
zyga committed Sep 27, 2024
1 parent 9885d1a commit eb3195d
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 20 deletions.
28 changes: 21 additions & 7 deletions cmd/snapd-apparmor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import (

"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/release"
apparmor_sandbox "github.com/snapcore/snapd/sandbox/apparmor"
"github.com/snapcore/snapd/snapdtool"
Expand All @@ -68,19 +69,32 @@ import (
// any loss of functionality. This is an unsupported configuration that cannot
// be properly handled by this function.
func isContainerWithInternalPolicy() bool {
var securityFSPath = filepath.Join(dirs.GlobalRootDir, "/sys/kernel/security")

if release.OnWSL {
// WSL-1 is an emulated Windows layer that has no support for AppArmor.
// WSL-2 is a virtualised environment with the Linux kernel as
// distributed by Microsoft. In the future, if Microsoft enables
// AppArmor in their kernel configuration and adjusts their special
// init process, which launches individual distributions in
// quasi-containers, to initialize apparmor nesting, we might re-enable
// this and treat WSL-2 like a LXD/Incus container, with nested
// security.
// distributed by Microsoft.
//
// In the future, Microsoft could enable AppArmor in the WSL kernel
// configuration and modify the special init process that launches
// distributions in quasi-containers. This change would initialize
// AppArmor nesting automatically, potentially eliminating the need for
// this WSL-specific logic.
//
// In the meantime, given that people experiment with AppArmor on WSL,
// so we only bail out if the securityfs is not available. When
// securityfs is present we assume everything else is "just right" even
// though that is not really true, and we know apparmor profiles loaded
// in one WSL distribution container are visible in all distribution
// containers.
if release.WSLVersion == 2 && osutil.IsDirectory(securityFSPath) {
return true
}
return false
}

var appArmorSecurityFSPath = filepath.Join(dirs.GlobalRootDir, "/sys/kernel/security/apparmor")
var appArmorSecurityFSPath = filepath.Join(securityFSPath, "apparmor")
var nsStackedPath = filepath.Join(appArmorSecurityFSPath, ".ns_stacked")
var nsNamePath = filepath.Join(appArmorSecurityFSPath, ".ns_name")

Expand Down
48 changes: 35 additions & 13 deletions cmd/snapd-apparmor/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,31 +70,53 @@ func mockWSL(version int) (restore func()) {
}
}

func (s *mainSuite) TestIsContainerWithInternalPolicy(c *C) {
// since "apparmorfs" is not present within our test root dir setup
// we expect this to return false
func (s *mainSuite) TestIsContainerWithInternalPolicy_NotContainer(c *C) {
// since "apparmorfs" is not present within our test root dir setup we
// expect this to return false
restore := mockWSL(0)
defer restore()

c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false)

appArmorSecurityFSPath := filepath.Join(dirs.GlobalRootDir, "/sys/kernel/security/apparmor/")
err := os.MkdirAll(appArmorSecurityFSPath, 0755)
c.Assert(err, IsNil)

c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false)
}

// Simulate being inside WSL 1 and 2.
//
// At present neither version is capable of running AppArmor correctly.
// AppArmor on WSL-1 is just not emulated by the Windows kernel.
// AppArmor on WSL-2 is neither configured in the Microsoft distribution
// of Linux nor properly set up to stack so that each running
// distribution gets an isolated playground.
func (s *mainSuite) TestIsContainerWithInternalPolicy_WSL1(c *C) {
restore := mockWSL(1)
defer restore()

c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false)
restore()
}

func (s *mainSuite) TestIsContainerWithInternalPolicy_WSL2(c *C) {
restore := mockWSL(2)
defer restore()

restore = mockWSL(2)
c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false)
restore()
}

func (s *mainSuite) TestIsContainerWithInternalPolicy_WSL2WithSecurityFS(c *C) {
restore := mockWSL(2)
defer restore()

appArmorSecurityFSPath := filepath.Join(dirs.GlobalRootDir, "/sys/kernel/security/apparmor/")
err := os.MkdirAll(appArmorSecurityFSPath, 0755)
c.Assert(err, IsNil)

c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, true)
}

func (s *mainSuite) TestIsContainerWithInternalPolicy_LinuxContainers(c *C) {
restore := mockWSL(0)
defer restore()

appArmorSecurityFSPath := filepath.Join(dirs.GlobalRootDir, "/sys/kernel/security/apparmor/")
err := os.MkdirAll(appArmorSecurityFSPath, 0755)
c.Assert(err, IsNil)

for _, prefix := range []string{"lxc", "lxd", "incus"} {
// simulate being inside a container environment
Expand Down

0 comments on commit eb3195d

Please sign in to comment.