The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/7866

This e-mail was sent by the LXC bot, direct replies will not reach the author
unless they happen to be subscribed to this list.

=== Description (from pull-request) ===

From ec0e232d3fef873b7441df2c75249e73bec397f2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 15 Sep 2020 15:41:25 -0400
Subject: [PATCH 1/7] shared/subprocess: Set err on non-zero
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 shared/subprocess/proc.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/shared/subprocess/proc.go b/shared/subprocess/proc.go
index f78b1a1f70..27ee69d7e8 100644
--- a/shared/subprocess/proc.go
+++ b/shared/subprocess/proc.go
@@ -177,6 +177,9 @@ func (p *Process) start(fds []*os.File) error {
 
                exitcode := int64(procstate.ExitCode())
                p.exitCode = exitcode
+               if p.exitCode != 0 {
+                       p.exitErr = fmt.Errorf("Process exited with a non-zero 
value")
+               }
                close(p.chExit)
        }()
 

From 2bc7219b6078710382c8a8526bc7828737729a49 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 15 Sep 2020 15:25:13 -0400
Subject: [PATCH 2/7] lxd/instances/qemu: Use subprocess
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/instance/drivers/driver_qemu.go | 29 +++++++++++++++++++++--------
 1 file changed, 21 insertions(+), 8 deletions(-)

diff --git a/lxd/instance/drivers/driver_qemu.go 
b/lxd/instance/drivers/driver_qemu.go
index c3b3d80d35..bab4b84d61 100644
--- a/lxd/instance/drivers/driver_qemu.go
+++ b/lxd/instance/drivers/driver_qemu.go
@@ -57,6 +57,7 @@ import (
        "github.com/lxc/lxd/shared/logger"
        "github.com/lxc/lxd/shared/logging"
        "github.com/lxc/lxd/shared/osarch"
+       "github.com/lxc/lxd/shared/subprocess"
        "github.com/lxc/lxd/shared/termios"
        "github.com/lxc/lxd/shared/units"
 )
@@ -827,13 +828,14 @@ func (vm *qemu) Start(stateful bool) error {
                forkLimitsCmd = append(forkLimitsCmd, fmt.Sprintf("fd=%d", 3+i))
        }
 
-       cmd := exec.Command(vm.state.OS.ExecPath, append(forkLimitsCmd, 
qemuCmd...)...)
-       var stdout bytes.Buffer
-       var stderr bytes.Buffer
-       cmd.Stdout = &stdout
-       cmd.Stderr = &stderr
+       // Setup background process.
+       p, err := subprocess.NewProcess(vm.state.OS.ExecPath, 
append(forkLimitsCmd, qemuCmd...), vm.EarlyLogFilePath(), vm.EarlyLogFilePath())
+       if err != nil {
+               return err
+       }
 
        // Open any extra files and pass their file handles to qemu command.
+       files := []*os.File{}
        for _, file := range fdFiles {
                info, err := os.Stat(file)
                if err != nil {
@@ -870,12 +872,18 @@ func (vm *qemu) Start(stateful bool) error {
                        defer f.Close() // Close file after qemu has started.
                }
 
-               cmd.ExtraFiles = append(cmd.ExtraFiles, f)
+               files = append(files, f)
        }
 
-       err = cmd.Run()
+       err = p.StartWithFiles(files)
        if err != nil {
-               err = errors.Wrapf(err, "Failed to run: %s: %s", 
strings.Join(cmd.Args, " "), strings.TrimSpace(string(stderr.Bytes())))
+               return err
+       }
+
+       _, err = p.Wait()
+       if err != nil {
+               stderr, _ := ioutil.ReadFile(vm.EarlyLogFilePath())
+               err = errors.Wrapf(err, "Failed to run: %s: %s", 
strings.Join(p.Args, " "), string(stderr))
                op.Done(err)
                return err
        }
@@ -4389,6 +4397,11 @@ func (vm *qemu) LogPath() string {
        return shared.LogPath(name)
 }
 
+// EarlyLogFilePath returns the instance's early log path.
+func (vm *qemu) EarlyLogFilePath() string {
+       return filepath.Join(vm.LogPath(), "qemu.early.log")
+}
+
 // LogFilePath returns the instance's log path.
 func (vm *qemu) LogFilePath() string {
        return filepath.Join(vm.LogPath(), "qemu.log")

From 85ddc644cf42b184aeac11d1b47cce11c503cb1c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 15 Sep 2020 17:44:48 -0400
Subject: [PATCH 3/7] lxd/instance: Add DevPaths
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/instance/drivers/driver_common.go | 9 +++++++++
 lxd/instance/drivers/driver_qemu.go   | 7 +++++++
 lxd/instance/instance_interface.go    | 3 +++
 3 files changed, 19 insertions(+)

diff --git a/lxd/instance/drivers/driver_common.go 
b/lxd/instance/drivers/driver_common.go
index 27d0fd1c81..7f3e9da6dd 100644
--- a/lxd/instance/drivers/driver_common.go
+++ b/lxd/instance/drivers/driver_common.go
@@ -11,6 +11,7 @@ import (
 // common provides structure common to all instance types.
 type common struct {
        dbType          instancetype.Type
+       devPaths        []string
        expandedConfig  map[string]string
        expandedDevices deviceConfig.Devices
        localConfig     map[string]string
@@ -77,3 +78,11 @@ func (c *common) expandDevices(profiles []api.Profile) error 
{
 
        return nil
 }
+
+// DevPaths() returns a list of /dev devices which the instance requires 
access to.
+// This is function is only safe to call from within the security
+// packages as called during instance startup, the rest of the time this
+// will likely return nil.
+func (c *common) DevPaths() []string {
+       return c.devPaths
+}
diff --git a/lxd/instance/drivers/driver_qemu.go 
b/lxd/instance/drivers/driver_qemu.go
index bab4b84d61..6a5f7cd2f7 100644
--- a/lxd/instance/drivers/driver_qemu.go
+++ b/lxd/instance/drivers/driver_qemu.go
@@ -640,6 +640,9 @@ func (vm *qemu) Start(stateful bool) error {
        revert := revert.New()
        defer revert.Fail()
 
+       // Start accumulating device paths.
+       vm.devPaths = []string{}
+
        // Mount the instance's config volume.
        _, err = vm.mount()
        if err != nil {
@@ -2059,6 +2062,10 @@ func (vm *qemu) addDriveConfig(sb *strings.Builder, 
bootIndexes map[string]int,
                }
        }
 
+       if !strings.HasPrefix(driveConf.DevPath, "rbd:") {
+               vm.devPaths = append(vm.devPaths, driveConf.DevPath)
+       }
+
        return qemuDrive.Execute(sb, map[string]interface{}{
                "devName":   driveConf.DevName,
                "devPath":   driveConf.DevPath,
diff --git a/lxd/instance/instance_interface.go 
b/lxd/instance/instance_interface.go
index f0c117f6e7..bfe9054a70 100644
--- a/lxd/instance/instance_interface.go
+++ b/lxd/instance/instance_interface.go
@@ -69,6 +69,9 @@ type Instance interface {
        Delete() error
        Export(w io.Writer, properties map[string]string) (api.ImageMetadata, 
error)
 
+       // Used for security.
+       DevPaths() []string
+
        // Live configuration.
        CGroupSet(key string, value string) error
        VolatileSet(changes map[string]string) error

From 20581ad6585878588f3d9827d90a21be6610e0cd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 15 Sep 2020 17:42:02 -0400
Subject: [PATCH 4/7] lxd/apparmor: Fix unload/delete
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/apparmor/apparmor.go           | 14 +++++++-------
 lxd/apparmor/instance.go           |  4 ++--
 lxd/apparmor/instance_forkproxy.go |  4 ++--
 lxd/apparmor/network.go            |  8 ++++----
 4 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/lxd/apparmor/apparmor.go b/lxd/apparmor/apparmor.go
index ffb9491f56..2231964633 100644
--- a/lxd/apparmor/apparmor.go
+++ b/lxd/apparmor/apparmor.go
@@ -83,9 +83,9 @@ func deleteNamespace(state *state.State, name string) error {
 
 // hasProfile checks if the profile is already loaded.
 func hasProfile(state *state.State, name string) (bool, error) {
-       mangled := strings.Replace(name, "/", ".", -1)
+       mangled := strings.Replace(strings.Replace(strings.Replace(name, "/", 
".", -1), "<", "", -1), ">", "", -1)
 
-       profilesPath := "/sys/kernel/security/apaprmor/policy/profiles"
+       profilesPath := "/sys/kernel/security/apparmor/policy/profiles"
        if shared.PathExists(profilesPath) {
                entries, err := ioutil.ReadDir(profilesPath)
                if err != nil {
@@ -94,7 +94,7 @@ func hasProfile(state *state.State, name string) (bool, 
error) {
 
                for _, entry := range entries {
                        fields := strings.Split(entry.Name(), ".")
-                       if mangled == strings.Join(fields[0:len(fields)-2], 
".") {
+                       if mangled == strings.Join(fields[0:len(fields)-1], 
".") {
                                return true, nil
                        }
                }
@@ -122,12 +122,12 @@ func loadProfile(state *state.State, name string) error {
 }
 
 // unloadProfile removes the profile from the kernel.
-func unloadProfile(state *state.State, name string) error {
+func unloadProfile(state *state.State, fullName string, name string) error {
        if !state.OS.AppArmorAvailable {
                return nil
        }
 
-       ok, err := hasProfile(state, name)
+       ok, err := hasProfile(state, fullName)
        if err != nil {
                return err
        }
@@ -140,7 +140,7 @@ func unloadProfile(state *state.State, name string) error {
 }
 
 // deleteProfile unloads and delete profile and cache for a profile.
-func deleteProfile(state *state.State, name string) error {
+func deleteProfile(state *state.State, fullName string, name string) error {
        if !state.OS.AppArmorAdmin {
                return nil
        }
@@ -150,7 +150,7 @@ func deleteProfile(state *state.State, name string) error {
                return err
        }
 
-       err = unloadProfile(state, name)
+       err = unloadProfile(state, fullName, name)
        if err != nil {
                return err
        }
diff --git a/lxd/apparmor/instance.go b/lxd/apparmor/instance.go
index 1ae6f64b80..bccd46bf57 100644
--- a/lxd/apparmor/instance.go
+++ b/lxd/apparmor/instance.go
@@ -93,7 +93,7 @@ func InstanceUnload(state *state.State, inst instance) error {
                return err
        }
 
-       err = unloadProfile(state, instanceProfileFilename(inst))
+       err := unloadProfile(state, InstanceProfileName(inst), 
instanceProfileFilename(inst))
        if err != nil {
                return err
        }
@@ -108,7 +108,7 @@ func InstanceParse(state *state.State, inst instance) error 
{
 
 // InstanceDelete removes the policy from cache/disk.
 func InstanceDelete(state *state.State, inst instance) error {
-       return deleteProfile(state, instanceProfileFilename(inst))
+       return deleteProfile(state, InstanceProfileName(inst), 
instanceProfileFilename(inst))
 }
 
 // instanceProfile generates the AppArmor profile template from the given 
instance.
diff --git a/lxd/apparmor/instance_forkproxy.go 
b/lxd/apparmor/instance_forkproxy.go
index 3bdb79ed11..96215cd77e 100644
--- a/lxd/apparmor/instance_forkproxy.go
+++ b/lxd/apparmor/instance_forkproxy.go
@@ -185,10 +185,10 @@ func ForkproxyLoad(state *state.State, inst instance, dev 
device) error {
 // ForkproxyUnload ensures that the instances's policy namespace is unloaded 
to free kernel memory.
 // This does not delete the policy from disk or cache.
 func ForkproxyUnload(state *state.State, inst instance, dev device) error {
-       return unloadProfile(state, forkproxyProfileFilename(inst, dev))
+       return unloadProfile(state, ForkproxyProfileName(inst, dev), 
forkproxyProfileFilename(inst, dev))
 }
 
 // ForkproxyDelete removes the policy from cache/disk.
 func ForkproxyDelete(state *state.State, inst instance, dev device) error {
-       return deleteProfile(state, forkproxyProfileFilename(inst, dev))
+       return deleteProfile(state, ForkproxyProfileName(inst, dev), 
forkproxyProfileFilename(inst, dev))
 }
diff --git a/lxd/apparmor/network.go b/lxd/apparmor/network.go
index f3efe516e5..e5b2698c79 100644
--- a/lxd/apparmor/network.go
+++ b/lxd/apparmor/network.go
@@ -85,14 +85,14 @@ func NetworkLoad(state *state.State, n network) error {
 // This does not delete the policy from disk or cache.
 func NetworkUnload(state *state.State, n network) error {
        // dnsmasq
-       err := unloadProfile(state, dnsmasqProfileFilename(n))
+       err := unloadProfile(state, DnsmasqProfileName(n), 
dnsmasqProfileFilename(n))
        if err != nil {
                return err
        }
 
        // forkdns
        if n.Config()["bridge.mode"] == "fan" {
-               err := unloadProfile(state, forkdnsProfileFilename(n))
+               err := unloadProfile(state, ForkdnsProfileName(n), 
forkdnsProfileFilename(n))
                if err != nil {
                        return err
                }
@@ -103,13 +103,13 @@ func NetworkUnload(state *state.State, n network) error {
 
 // NetworkDelete removes the profiles from cache/disk.
 func NetworkDelete(state *state.State, n network) error {
-       err := deleteProfile(state, dnsmasqProfileFilename(n))
+       err := deleteProfile(state, DnsmasqProfileName(n), 
dnsmasqProfileFilename(n))
        if err != nil {
                return err
        }
 
        if n.Config()["bridge.mode"] == "fan" {
-               err := deleteProfile(state, forkdnsProfileFilename(n))
+               err := deleteProfile(state, ForkdnsProfileName(n), 
forkdnsProfileFilename(n))
                if err != nil {
                        return err
                }

From 2ece5ff996cf549401dd2871fb2fcb0a5e5f3671 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 15 Sep 2020 17:45:34 -0400
Subject: [PATCH 5/7] lxd/apparmor/instance: Sort context
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/apparmor/instance.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/apparmor/instance.go b/lxd/apparmor/instance.go
index bccd46bf57..d6459e7262 100644
--- a/lxd/apparmor/instance.go
+++ b/lxd/apparmor/instance.go
@@ -131,15 +131,15 @@ func instanceProfile(state *state.State, inst instance) 
(string, error) {
        // Render the profile.
        var sb *strings.Builder = &strings.Builder{}
        err = lxcProfileTpl.Execute(sb, map[string]interface{}{
-               "feature_unix":     unixSupported,
                "feature_cgns":     state.OS.CGInfo.Namespacing,
                "feature_cgroup2":  state.OS.CGInfo.Layout == 
cgroup.CgroupsUnified || state.OS.CGInfo.Layout == cgroup.CgroupsHybrid,
                "feature_stacking": state.OS.AppArmorStacking && 
!state.OS.AppArmorStacked,
+               "feature_unix":     unixSupported,
+               "name":             InstanceProfileName(inst),
                "namespace":        InstanceNamespaceName(inst),
                "nesting":          
shared.IsTrue(inst.ExpandedConfig()["security.nesting"]),
-               "name":             InstanceProfileName(inst),
-               "unprivileged":     
!shared.IsTrue(inst.ExpandedConfig()["security.privileged"]) || 
state.OS.RunningInUserNS,
                "raw":              rawContent,
+               "unprivileged":     
!shared.IsTrue(inst.ExpandedConfig()["security.privileged"]) || 
state.OS.RunningInUserNS,
        })
        if err != nil {
                return "", err

From fd082cf26bf1c220a1a146576fc421b8d54a2783 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 15 Sep 2020 14:04:15 -0400
Subject: [PATCH 6/7] lxd/apparmor: Prepare for qemu
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/apparmor/instance.go | 18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)

diff --git a/lxd/apparmor/instance.go b/lxd/apparmor/instance.go
index d6459e7262..ab9880a26f 100644
--- a/lxd/apparmor/instance.go
+++ b/lxd/apparmor/instance.go
@@ -8,6 +8,7 @@ import (
        "strings"
 
        "github.com/lxc/lxd/lxd/cgroup"
+       "github.com/lxc/lxd/lxd/instance/instancetype"
        "github.com/lxc/lxd/lxd/project"
        "github.com/lxc/lxd/lxd/state"
        "github.com/lxc/lxd/shared"
@@ -18,6 +19,7 @@ type instance interface {
        Project() string
        Name() string
        ExpandedConfig() map[string]string
+       Type() instancetype.Type
 }
 
 // InstanceProfileName returns the instance's AppArmor profile name.
@@ -43,9 +45,11 @@ func instanceProfileFilename(inst instance) string {
 
 // InstanceLoad ensures that the instances's policy is loaded into the kernel 
so the it can boot.
 func InstanceLoad(state *state.State, inst instance) error {
-       err := createNamespace(state, InstanceNamespaceName(inst))
-       if err != nil {
-               return err
+       if inst.Type() == instancetype.Container {
+               err := createNamespace(state, InstanceNamespaceName(inst))
+               if err != nil {
+                       return err
+               }
        }
 
        /* In order to avoid forcing a profile parse (potentially slow) on
@@ -88,9 +92,11 @@ func InstanceLoad(state *state.State, inst instance) error {
 // InstanceUnload ensures that the instances's policy namespace is unloaded to 
free kernel memory.
 // This does not delete the policy from disk or cache.
 func InstanceUnload(state *state.State, inst instance) error {
-       err := deleteNamespace(state, InstanceNamespaceName(inst))
-       if err != nil {
-               return err
+       if inst.Type() == instancetype.Container {
+               err := deleteNamespace(state, InstanceNamespaceName(inst))
+               if err != nil {
+                       return err
+               }
        }
 
        err := unloadProfile(state, InstanceProfileName(inst), 
instanceProfileFilename(inst))

From de3e3680ff59dd1db0b016bf23616d5d13612586 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Tue, 15 Sep 2020 14:04:25 -0400
Subject: [PATCH 7/7] lxd/apparmor: Add qemu profile
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgra...@ubuntu.com>
---
 lxd/apparmor/instance.go            | 66 ++++++++++++++++++++-----
 lxd/apparmor/instance_qemu.go       | 74 +++++++++++++++++++++++++++++
 lxd/instance/drivers/driver_qemu.go | 26 ++++++++++
 3 files changed, 153 insertions(+), 13 deletions(-)
 create mode 100644 lxd/apparmor/instance_qemu.go

diff --git a/lxd/apparmor/instance.go b/lxd/apparmor/instance.go
index ab9880a26f..77d266befd 100644
--- a/lxd/apparmor/instance.go
+++ b/lxd/apparmor/instance.go
@@ -11,6 +11,7 @@ import (
        "github.com/lxc/lxd/lxd/instance/instancetype"
        "github.com/lxc/lxd/lxd/project"
        "github.com/lxc/lxd/lxd/state"
+       "github.com/lxc/lxd/lxd/util"
        "github.com/lxc/lxd/shared"
 )
 
@@ -20,6 +21,9 @@ type instance interface {
        Name() string
        ExpandedConfig() map[string]string
        Type() instancetype.Type
+       LogPath() string
+       Path() string
+       DevPaths() []string
 }
 
 // InstanceProfileName returns the instance's AppArmor profile name.
@@ -136,19 +140,55 @@ func instanceProfile(state *state.State, inst instance) 
(string, error) {
 
        // Render the profile.
        var sb *strings.Builder = &strings.Builder{}
-       err = lxcProfileTpl.Execute(sb, map[string]interface{}{
-               "feature_cgns":     state.OS.CGInfo.Namespacing,
-               "feature_cgroup2":  state.OS.CGInfo.Layout == 
cgroup.CgroupsUnified || state.OS.CGInfo.Layout == cgroup.CgroupsHybrid,
-               "feature_stacking": state.OS.AppArmorStacking && 
!state.OS.AppArmorStacked,
-               "feature_unix":     unixSupported,
-               "name":             InstanceProfileName(inst),
-               "namespace":        InstanceNamespaceName(inst),
-               "nesting":          
shared.IsTrue(inst.ExpandedConfig()["security.nesting"]),
-               "raw":              rawContent,
-               "unprivileged":     
!shared.IsTrue(inst.ExpandedConfig()["security.privileged"]) || 
state.OS.RunningInUserNS,
-       })
-       if err != nil {
-               return "", err
+       if inst.Type() == instancetype.Container {
+               err = lxcProfileTpl.Execute(sb, map[string]interface{}{
+                       "feature_cgns":     state.OS.CGInfo.Namespacing,
+                       "feature_cgroup2":  state.OS.CGInfo.Layout == 
cgroup.CgroupsUnified || state.OS.CGInfo.Layout == cgroup.CgroupsHybrid,
+                       "feature_stacking": state.OS.AppArmorStacking && 
!state.OS.AppArmorStacked,
+                       "feature_unix":     unixSupported,
+                       "name":             InstanceProfileName(inst),
+                       "namespace":        InstanceNamespaceName(inst),
+                       "nesting":          
shared.IsTrue(inst.ExpandedConfig()["security.nesting"]),
+                       "raw":              rawContent,
+                       "unprivileged":     
!shared.IsTrue(inst.ExpandedConfig()["security.privileged"]) || 
state.OS.RunningInUserNS,
+               })
+               if err != nil {
+                       return "", err
+               }
+       } else {
+               rootPath := ""
+               if shared.InSnap() {
+                       rootPath = "/var/lib/snapd/hostfs"
+               }
+
+               // AppArmor requires deref of all paths.
+               path, err := filepath.EvalSymlinks(inst.Path())
+               if err != nil {
+                       return "", err
+               }
+
+               devPaths := inst.DevPaths()
+               for i := range devPaths {
+                       devPaths[i], err = filepath.EvalSymlinks(devPaths[i])
+                       if err != nil {
+                               return "", err
+                       }
+               }
+
+               err = qemuProfileTpl.Execute(sb, map[string]interface{}{
+                       "devPaths":    inst.DevPaths(),
+                       "exePath":     util.GetExecPath(),
+                       "libraryPath": 
strings.Split(os.Getenv("LD_LIBRARY_PATH"), ":"),
+                       "logPath":     inst.LogPath(),
+                       "name":        InstanceProfileName(inst),
+                       "path":        path,
+                       "raw":         rawContent,
+                       "rootPath":    rootPath,
+                       "snap":        shared.InSnap(),
+               })
+               if err != nil {
+                       return "", err
+               }
        }
 
        return sb.String(), nil
diff --git a/lxd/apparmor/instance_qemu.go b/lxd/apparmor/instance_qemu.go
new file mode 100644
index 0000000000..7ceb02dc52
--- /dev/null
+++ b/lxd/apparmor/instance_qemu.go
@@ -0,0 +1,74 @@
+package apparmor
+
+import (
+       "text/template"
+)
+
+var qemuProfileTpl = template.Must(template.New("qemuProfile").Parse(`#include 
<tunables/global>
+profile "{{ .name }}" flags=(attach_disconnected,mediate_deleted) {
+  #include <abstractions/base>
+  #include <abstractions/consoles>
+  #include <abstractions/nameservice>
+
+  capability dac_override,
+  capability dac_read_search,
+  capability setgid,
+  capability setuid,
+  capability sys_chroot,
+  capability sys_resource,
+
+  # Needed by qemu
+  /{,usr/}bin/qemu*                         mrix,
+  /dev/hugepages/**                         w,
+  /dev/kvm                                  w,
+  /dev/net/tun                              w,
+  /dev/ptmx                                 w,
+  /dev/vfio/**                              w,
+  /dev/vhost-net                            w,
+  /dev/vhost-vsock                          w,
+  /etc/ceph/**                              r,
+  /usr/share/OVMF/OVMF_CODE.fd              kr,
+  owner @{PROC}/@{pid}/task/@{tid}/comm     rw,
+
+  # Instance specific paths
+  {{ .logPath }}/** rwk,
+  {{ .path }}/qemu.nvram rwk,
+{{range $index, $element := .devPaths}}
+  {{$element}} rwk,
+{{- end }}
+
+  # Needed for lxd fork commands
+  {{ .exePath }} mr,
+  @{PROC}/@{pid}/cmdline r,
+  {{ .rootPath }}/{etc,lib,usr/lib}/os-release r,
+
+  # Things that we definitely don't need
+  deny @{PROC}/@{pid}/cgroup r,
+  deny /sys/module/apparmor/parameters/enabled r,
+  deny /sys/kernel/mm/transparent_hugepage/hpage_pmd_size r,
+
+{{- if .snap }}
+  # The binary itself (for nesting)
+  /var/snap/lxd/common/lxd.debug            mr,
+  /snap/lxd/*/bin/lxd                       mr,
+  /snap/lxd/*/bin/qemu*                     mrix,
+  /snap/lxd/*/share/qemu/OVMF_CODE.fd       kr,
+
+  # Snap-specific libraries
+  /snap/lxd/*/lib/**.so*            mr,
+{{- end }}
+
+{{if .libraryPath -}}
+  # Entries from LD_LIBRARY_PATH
+{{range $index, $element := .libraryPath}}
+  {{$element}}/** mr,
+{{- end }}
+{{- end }}
+
+{{- if .raw }}
+
+  ### Configuration: raw.apparmor
+{{ .raw }}
+{{- end }}
+}
+`))
diff --git a/lxd/instance/drivers/driver_qemu.go 
b/lxd/instance/drivers/driver_qemu.go
index 6a5f7cd2f7..fc1a8ac5cc 100644
--- a/lxd/instance/drivers/driver_qemu.go
+++ b/lxd/instance/drivers/driver_qemu.go
@@ -27,6 +27,7 @@ import (
        "gopkg.in/yaml.v2"
 
        lxdClient "github.com/lxc/lxd/client"
+       "github.com/lxc/lxd/lxd/apparmor"
        "github.com/lxc/lxd/lxd/backup"
        "github.com/lxc/lxd/lxd/cluster"
        "github.com/lxc/lxd/lxd/db"
@@ -505,6 +506,12 @@ func (vm *qemu) Freeze() error {
 
 // onStop is run when the instance stops.
 func (vm *qemu) onStop(target string) error {
+       ctxMap := log.Ctx{
+               "project":   vm.project,
+               "name":      vm.name,
+               "ephemeral": vm.ephemeral,
+       }
+
        // Pick up the existing stop operation lock created in Stop() function.
        op := operationlock.Get(vm.id)
        if op != nil && op.Action() != "stop" {
@@ -526,6 +533,13 @@ func (vm *qemu) onStop(target string) error {
                return err
        }
 
+       // Unload the apparmor profile
+       err = apparmor.InstanceUnload(vm.state, vm)
+       if err != nil {
+               ctxMap["err"] = err
+               logger.Error("Failed to unload AppArmor profile", ctxMap)
+       }
+
        if target == "reboot" {
                err = vm.Start(false)
        } else if vm.ephemeral {
@@ -837,6 +851,15 @@ func (vm *qemu) Start(stateful bool) error {
                return err
        }
 
+       // Load the AppArmor profile
+       err = apparmor.InstanceLoad(vm.state, vm)
+       if err != nil {
+               op.Done(err)
+               return err
+       }
+
+       p.SetApparmor(apparmor.InstanceProfileName(vm))
+
        // Open any extra files and pass their file handles to qemu command.
        files := []*os.File{}
        for _, file := range fdFiles {
@@ -3230,6 +3253,9 @@ func (vm *qemu) cleanup() {
        vm.removeUnixDevices()
        vm.removeDiskDevices()
 
+       // Remove the security profiles
+       apparmor.InstanceDelete(vm.state, vm)
+
        // Remove the devices path
        os.Remove(vm.DevicesPath())
 
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to