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

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) ===
This branch has the eventual goal of running `forkproxy` under an apparmor profile as well as running it as an unprivileged user.

To achieve this I needed a few extra things:
 - Ability to run a subprocess as another user/group
 - An unprivileged group to use

Additionally because `lxd forkdns` is a sub-command of LXD, a lot of code gets loaded just because it's LXD.
Part of that was loading the whole USB database which we only need for `lxd/resource` so I've changed that code a bit.

We still are accessing a bunch of files on startup that we shouldn't be. The cgroup and apparmor bits come from us loading liblxc. No idea where the hugepage one comes from.
From dd4e97f3bb84cd4919b48e207dc5796abc68dc14 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Mon, 10 Aug 2020 17:34:35 -0400
Subject: [PATCH 01/10] shared/usbid: Don't auto-load
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/usbid/load.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/shared/usbid/load.go b/shared/usbid/load.go
index 39f6c4d831..65ef8f067f 100644
--- a/shared/usbid/load.go
+++ b/shared/usbid/load.go
@@ -28,7 +28,8 @@ var (
        Classes map[ClassCode]*Class
 )
 
-func init() {
+// Load reads the USB database from disk.
+func Load() {
        usbids, err := os.Open("/usr/share/misc/usb.ids")
        if err != nil {
                if !os.IsNotExist(err) {

From 3699560ddca30d453d9950225fa25e5a0550cb77 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Mon, 10 Aug 2020 17:34:44 -0400
Subject: [PATCH 02/10] lxd/resources: Load USB database
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/resources/usb.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/lxd/resources/usb.go b/lxd/resources/usb.go
index b848711a3c..6479d252fc 100644
--- a/lxd/resources/usb.go
+++ b/lxd/resources/usb.go
@@ -17,6 +17,9 @@ var sysBusUSB = "/sys/bus/usb/devices"
 
 // GetUSB returns a filled api.ResourcesUSB struct ready for use by LXD
 func GetUSB() (*api.ResourcesUSB, error) {
+       // Load the USB database.
+       usbid.Load()
+
        usb := api.ResourcesUSB{}
 
        if !sysfsExists(sysBusUSB) {

From 50dc6c718a586555d852deee7104c539ca43fcab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Mon, 10 Aug 2020 15:46:44 -0400
Subject: [PATCH 03/10] lxd/apparmor: Move dnsmasq functions
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/network.go         | 57 -------------------------------
 lxd/apparmor/network_dnsmasq.go | 59 +++++++++++++++++++++++++++++++++
 2 files changed, 59 insertions(+), 57 deletions(-)

diff --git a/lxd/apparmor/network.go b/lxd/apparmor/network.go
index e3615c6812..15094083b1 100644
--- a/lxd/apparmor/network.go
+++ b/lxd/apparmor/network.go
@@ -1,16 +1,11 @@
 package apparmor
 
 import (
-       "crypto/sha256"
-       "fmt"
-       "io"
        "io/ioutil"
        "os"
        "path/filepath"
-       "strings"
 
        "github.com/lxc/lxd/lxd/state"
-       "github.com/lxc/lxd/shared"
 )
 
 // Internal copy of the network interface.
@@ -18,35 +13,6 @@ type network interface {
        Name() string
 }
 
-// DnsmasqProfileName returns the AppArmor profile name.
-func DnsmasqProfileName(n network) string {
-       path := shared.VarPath("")
-       name := fmt.Sprintf("%s_<%s>", n.Name(), path)
-
-       // Max length in AppArmor is 253 chars.
-       if len(name)+12 >= 253 {
-               hash := sha256.New()
-               io.WriteString(hash, name)
-               name = fmt.Sprintf("%x", hash.Sum(nil))
-       }
-
-       return fmt.Sprintf("lxd_dnsmasq-%s", name)
-}
-
-// dnsmasqProfileFilename returns the name of the on-disk profile name.
-func dnsmasqProfileFilename(n network) string {
-       name := n.Name()
-
-       // Max length in AppArmor is 253 chars.
-       if len(name)+12 >= 253 {
-               hash := sha256.New()
-               io.WriteString(hash, name)
-               name = fmt.Sprintf("%x", hash.Sum(nil))
-       }
-
-       return fmt.Sprintf("lxd_dnsmasq-%s", name)
-}
-
 // NetworkLoad ensures that the network's profiles are loaded into the kernel.
 func NetworkLoad(state *state.State, n network) error {
        /* In order to avoid forcing a profile parse (potentially slow) on
@@ -101,26 +67,3 @@ func NetworkUnload(state *state.State, n network) error {
 func NetworkDelete(state *state.State, n network) error {
        return deleteProfile(state, dnsmasqProfileFilename(n))
 }
-
-// dnsmasqProfile generates the AppArmor profile template from the given 
network.
-func dnsmasqProfile(state *state.State, n network) (string, error) {
-       rootPath := ""
-       if shared.InSnap() {
-               rootPath = "/var/lib/snapd/hostfs"
-       }
-
-       // Render the profile.
-       var sb *strings.Builder = &strings.Builder{}
-       err := dnsmasqProfileTpl.Execute(sb, map[string]interface{}{
-               "name":        DnsmasqProfileName(n),
-               "networkName": n.Name(),
-               "varPath":     shared.VarPath(""),
-               "rootPath":    rootPath,
-               "snap":        shared.InSnap(),
-       })
-       if err != nil {
-               return "", err
-       }
-
-       return sb.String(), nil
-}
diff --git a/lxd/apparmor/network_dnsmasq.go b/lxd/apparmor/network_dnsmasq.go
index ea37daf863..0b63dc6048 100644
--- a/lxd/apparmor/network_dnsmasq.go
+++ b/lxd/apparmor/network_dnsmasq.go
@@ -1,7 +1,14 @@
 package apparmor
 
 import (
+       "crypto/sha256"
+       "fmt"
+       "io"
+       "strings"
        "text/template"
+
+       "github.com/lxc/lxd/lxd/state"
+       "github.com/lxc/lxd/shared"
 )
 
 var dnsmasqProfileTpl = 
template.Must(template.New("dnsmasqProfile").Parse(`#include <tunables/global>
@@ -59,3 +66,55 @@ profile "{{ .name }}" 
flags=(attach_disconnected,mediate_deleted) {
 {{- end }}
 }
 `))
+
+// dnsmasqProfile generates the AppArmor profile template from the given 
network.
+func dnsmasqProfile(state *state.State, n network) (string, error) {
+       rootPath := ""
+       if shared.InSnap() {
+               rootPath = "/var/lib/snapd/hostfs"
+       }
+
+       // Render the profile.
+       var sb *strings.Builder = &strings.Builder{}
+       err := dnsmasqProfileTpl.Execute(sb, map[string]interface{}{
+               "name":        DnsmasqProfileName(n),
+               "networkName": n.Name(),
+               "varPath":     shared.VarPath(""),
+               "rootPath":    rootPath,
+               "snap":        shared.InSnap(),
+       })
+       if err != nil {
+               return "", err
+       }
+
+       return sb.String(), nil
+}
+
+// DnsmasqProfileName returns the AppArmor profile name.
+func DnsmasqProfileName(n network) string {
+       path := shared.VarPath("")
+       name := fmt.Sprintf("%s_<%s>", n.Name(), path)
+
+       // Max length in AppArmor is 253 chars.
+       if len(name)+12 >= 253 {
+               hash := sha256.New()
+               io.WriteString(hash, name)
+               name = fmt.Sprintf("%x", hash.Sum(nil))
+       }
+
+       return fmt.Sprintf("lxd_dnsmasq-%s", name)
+}
+
+// dnsmasqProfileFilename returns the name of the on-disk profile name.
+func dnsmasqProfileFilename(n network) string {
+       name := n.Name()
+
+       // Max length in AppArmor is 253 chars.
+       if len(name)+12 >= 253 {
+               hash := sha256.New()
+               io.WriteString(hash, name)
+               name = fmt.Sprintf("%x", hash.Sum(nil))
+       }
+
+       return fmt.Sprintf("lxd_dnsmasq-%s", name)
+}

From 6488290da7a97b62925e7f73fbe86a8a0621c10c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Mon, 10 Aug 2020 15:47:41 -0400
Subject: [PATCH 04/10] lxd/apparmor: forkdns 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/network.go         |  52 +++++++++++++++-
 lxd/apparmor/network_forkdns.go | 101 ++++++++++++++++++++++++++++++++
 2 files changed, 152 insertions(+), 1 deletion(-)
 create mode 100644 lxd/apparmor/network_forkdns.go

diff --git a/lxd/apparmor/network.go b/lxd/apparmor/network.go
index 15094083b1..f3efe516e5 100644
--- a/lxd/apparmor/network.go
+++ b/lxd/apparmor/network.go
@@ -10,6 +10,7 @@ import (
 
 // Internal copy of the network interface.
 type network interface {
+       Config() map[string]string
        Name() string
 }
 
@@ -26,6 +27,8 @@ func NetworkLoad(state *state.State, n network) error {
         * version out so that the new changes are reflected and we definitely
         * force a recompile.
         */
+
+       // dnsmasq
        profile := filepath.Join(aaPath, "profiles", dnsmasqProfileFilename(n))
        content, err := ioutil.ReadFile(profile)
        if err != nil && !os.IsNotExist(err) {
@@ -49,21 +52,68 @@ func NetworkLoad(state *state.State, n network) error {
                return err
        }
 
+       // forkdns
+       if n.Config()["bridge.mode"] == "fan" {
+               profile := filepath.Join(aaPath, "profiles", 
forkdnsProfileFilename(n))
+               content, err := ioutil.ReadFile(profile)
+               if err != nil && !os.IsNotExist(err) {
+                       return err
+               }
+
+               updated, err := forkdnsProfile(state, n)
+               if err != nil {
+                       return err
+               }
+
+               if string(content) != string(updated) {
+                       err = ioutil.WriteFile(profile, []byte(updated), 0600)
+                       if err != nil {
+                               return err
+                       }
+               }
+
+               err = loadProfile(state, forkdnsProfileFilename(n))
+               if err != nil {
+                       return err
+               }
+       }
+
        return nil
 }
 
 // NetworkUnload ensures that the network's profiles are unloaded to free 
kernel memory.
 // This does not delete the policy from disk or cache.
 func NetworkUnload(state *state.State, n network) error {
+       // dnsmasq
        err := unloadProfile(state, dnsmasqProfileFilename(n))
        if err != nil {
                return err
        }
 
+       // forkdns
+       if n.Config()["bridge.mode"] == "fan" {
+               err := unloadProfile(state, forkdnsProfileFilename(n))
+               if err != nil {
+                       return err
+               }
+       }
+
        return nil
 }
 
 // NetworkDelete removes the profiles from cache/disk.
 func NetworkDelete(state *state.State, n network) error {
-       return deleteProfile(state, dnsmasqProfileFilename(n))
+       err := deleteProfile(state, dnsmasqProfileFilename(n))
+       if err != nil {
+               return err
+       }
+
+       if n.Config()["bridge.mode"] == "fan" {
+               err := deleteProfile(state, forkdnsProfileFilename(n))
+               if err != nil {
+                       return err
+               }
+       }
+
+       return nil
 }
diff --git a/lxd/apparmor/network_forkdns.go b/lxd/apparmor/network_forkdns.go
new file mode 100644
index 0000000000..396e87be67
--- /dev/null
+++ b/lxd/apparmor/network_forkdns.go
@@ -0,0 +1,101 @@
+package apparmor
+
+import (
+       "crypto/sha256"
+       "fmt"
+       "io"
+       "strings"
+       "text/template"
+
+       "github.com/lxc/lxd/lxd/state"
+       "github.com/lxc/lxd/shared"
+)
+
+var forkdnsProfileTpl = 
template.Must(template.New("forkdnsProfile").Parse(`#include <tunables/global>
+profile "{{ .name }}" flags=(attach_disconnected,mediate_deleted) {
+  #include <abstractions/base>
+
+  # Capabilities
+  capability net_bind_service,
+
+  # Network access
+  network inet dgram,
+  network inet6 dgram,
+
+  # Network-specific paths
+  {{ .varPath }}/networks/{{ .networkName }}/dnsmasq.leases r,
+  {{ .varPath }}/networks/{{ .networkName }}/forkdns.servers/servers.conf r,
+
+  # Needed for lxd fork commands
+  @{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/current/bin/lxd           mr,
+  /snap/lxd/*/bin/lxd                 mr,
+
+  # Snap-specific libraries
+  /snap/lxd/current/lib/**.so*            mr,
+  /snap/lxd/*/lib/**.so*                  mr,
+{{- end }}
+}
+`))
+
+// forkdnsProfile generates the AppArmor profile template from the given 
network.
+func forkdnsProfile(state *state.State, n network) (string, error) {
+       rootPath := ""
+       if shared.InSnap() {
+               rootPath = "/var/lib/snapd/hostfs"
+       }
+
+       // Render the profile.
+       var sb *strings.Builder = &strings.Builder{}
+       err := forkdnsProfileTpl.Execute(sb, map[string]interface{}{
+               "name":        ForkdnsProfileName(n),
+               "networkName": n.Name(),
+               "varPath":     shared.VarPath(""),
+               "rootPath":    rootPath,
+               "snap":        shared.InSnap(),
+       })
+       if err != nil {
+               return "", err
+       }
+
+       return sb.String(), nil
+}
+
+// ForkdnsProfileName returns the AppArmor profile name.
+func ForkdnsProfileName(n network) string {
+       path := shared.VarPath("")
+       name := fmt.Sprintf("%s_<%s>", n.Name(), path)
+
+       // Max length in AppArmor is 253 chars.
+       if len(name)+12 >= 253 {
+               hash := sha256.New()
+               io.WriteString(hash, name)
+               name = fmt.Sprintf("%x", hash.Sum(nil))
+       }
+
+       return fmt.Sprintf("lxd_forkdns-%s", name)
+}
+
+// forkdnsProfileFilename returns the name of the on-disk profile name.
+func forkdnsProfileFilename(n network) string {
+       name := n.Name()
+
+       // Max length in AppArmor is 253 chars.
+       if len(name)+12 >= 253 {
+               hash := sha256.New()
+               io.WriteString(hash, name)
+               name = fmt.Sprintf("%x", hash.Sum(nil))
+       }
+
+       return fmt.Sprintf("lxd_forkdns-%s", name)
+}

From 47b47dae003c1e72bd959710f2d585c481944749 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Mon, 10 Aug 2020 16:02:04 -0400
Subject: [PATCH 05/10] lxd/sys: Add unpriv uid/group
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/sys/os.go | 23 +++++++++++++++++++----
 1 file changed, 19 insertions(+), 4 deletions(-)

diff --git a/lxd/sys/os.go b/lxd/sys/os.go
index e18d917ed1..5dc6d1df61 100644
--- a/lxd/sys/os.go
+++ b/lxd/sys/os.go
@@ -49,8 +49,12 @@ type OS struct {
        MockMode        bool   // If true some APIs will be mocked (for testing)
        Nodev           bool
        RunningInUserNS bool
-       UnprivUser      string
-       UnprivUID       int
+
+       // Privilege dropping
+       UnprivUser  string
+       UnprivUID   uint32
+       UnprivGroup string
+       UnprivGID   uint32
 
        // Apparmor features
        AppArmorAdmin     bool
@@ -109,7 +113,7 @@ func (s *OS) Init() error {
                logger.Error("Error detecting backing fs", log.Ctx{"err": err})
        }
 
-       // Detect if it is possible to run daemons as an unprivileged user.
+       // Detect if it is possible to run daemons as an unprivileged user and 
group.
        for _, user := range []string{"lxd", "nobody"} {
                uid, err := shared.UserId(user)
                if err != nil {
@@ -117,7 +121,18 @@ func (s *OS) Init() error {
                }
 
                s.UnprivUser = user
-               s.UnprivUID = uid
+               s.UnprivUID = uint32(uid)
+               break
+       }
+
+       for _, group := range []string{"lxd", "nogroup"} {
+               gid, err := shared.GroupId(group)
+               if err != nil {
+                       continue
+               }
+
+               s.UnprivGroup = group
+               s.UnprivGID = uint32(gid)
                break
        }
 

From e38968777992e09519345aa51b3ca3bc6db6ae2a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Mon, 10 Aug 2020 16:16:26 -0400
Subject: [PATCH 06/10] lxd/instances: Update for OS type change
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 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/instance/drivers/driver_qemu.go 
b/lxd/instance/drivers/driver_qemu.go
index cc12ed6d14..793d3668a3 100644
--- a/lxd/instance/drivers/driver_qemu.go
+++ b/lxd/instance/drivers/driver_qemu.go
@@ -766,7 +766,7 @@ func (vm *qemu) Start(stateful bool) error {
                                        return err
                                }
 
-                               err = os.Chown(path, vm.state.OS.UnprivUID, -1)
+                               err = os.Chown(path, 
int(vm.state.OS.UnprivUID), -1)
                                if err != nil {
                                        op.Done(err)
                                        return err

From ea00a24332dfb01836cd49007db5cc8bb0811ae2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Mon, 10 Aug 2020 16:17:44 -0400
Subject: [PATCH 07/10] shared/subprocess: s/Pid/PID/
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 | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/shared/subprocess/proc.go b/shared/subprocess/proc.go
index 45a02cddf3..9dc70abf56 100644
--- a/shared/subprocess/proc.go
+++ b/shared/subprocess/proc.go
@@ -25,7 +25,7 @@ type Process struct {
        Name     string   `yaml:"name"`
        Args     []string `yaml:"args,flow"`
        Apparmor string   `yaml:"apparmor"`
-       Pid      int64    `yaml:"pid"`
+       PID      int64    `yaml:"pid"`
        Stdout   string   `yaml:"stdout"`
        Stderr   string   `yaml:"stderr"`
 }
@@ -45,10 +45,10 @@ func (p *Process) hasApparmor() bool {
 
 // GetPid returns the pid for the given process object
 func (p *Process) GetPid() (int64, error) {
-       pr, _ := os.FindProcess(int(p.Pid))
+       pr, _ := os.FindProcess(int(p.PID))
        err := pr.Signal(syscall.Signal(0))
        if err == nil {
-               return p.Pid, nil
+               return p.PID, nil
        }
 
        return 0, ErrNotRunning
@@ -61,7 +61,7 @@ func (p *Process) SetApparmor(profile string) {
 
 // Stop will stop the given process object
 func (p *Process) Stop() error {
-       pr, _ := os.FindProcess(int(p.Pid))
+       pr, _ := os.FindProcess(int(p.PID))
 
        // Check if process exists.
        err := pr.Signal(syscall.Signal(0))
@@ -128,7 +128,7 @@ func (p *Process) Start() error {
                return errors.Wrapf(err, "Unable to start process")
        }
 
-       p.Pid = int64(cmd.Process.Pid)
+       p.PID = int64(cmd.Process.Pid)
 
        // Reset exitCode/exitErr
        p.exitCode = 0
@@ -171,7 +171,7 @@ func (p *Process) Restart() error {
 
 // Reload sends the SIGHUP signal to the given process object
 func (p *Process) Reload() error {
-       pr, _ := os.FindProcess(int(p.Pid))
+       pr, _ := os.FindProcess(int(p.PID))
        err := pr.Signal(syscall.Signal(0))
        if err == nil {
                err = pr.Signal(syscall.SIGHUP)
@@ -203,7 +203,7 @@ func (p *Process) Save(path string) error {
 
 // Signal will send a signal to the given process object given a signal value
 func (p *Process) Signal(signal int64) error {
-       pr, _ := os.FindProcess(int(p.Pid))
+       pr, _ := os.FindProcess(int(p.PID))
        err := pr.Signal(syscall.Signal(0))
        if err == nil {
                err = pr.Signal(syscall.Signal(signal))

From 6a0cd60068ef6472df22d73000de518d9d1c7eaf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Mon, 10 Aug 2020 16:18:57 -0400
Subject: [PATCH 08/10] shared/subprocess: Add credentials
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 | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/shared/subprocess/proc.go b/shared/subprocess/proc.go
index 9dc70abf56..060637e681 100644
--- a/shared/subprocess/proc.go
+++ b/shared/subprocess/proc.go
@@ -28,6 +28,10 @@ type Process struct {
        PID      int64    `yaml:"pid"`
        Stdout   string   `yaml:"stdout"`
        Stderr   string   `yaml:"stderr"`
+
+       UID       uint32 `yaml:"uid"`
+       GID       uint32 `yaml:"gid"`
+       SetGroups bool   `yaml:"set_groups"`
 }
 
 func (p *Process) hasApparmor() bool {
@@ -59,6 +63,12 @@ func (p *Process) SetApparmor(profile string) {
        p.Apparmor = profile
 }
 
+// SetCreds allows setting process credentials.
+func (p *Process) SetCreds(uid uint32, gid uint32) {
+       p.UID = uid
+       p.GID = gid
+}
+
 // Stop will stop the given process object
 func (p *Process) Stop() error {
        pr, _ := os.FindProcess(int(p.PID))
@@ -101,6 +111,12 @@ func (p *Process) Start() error {
        cmd.SysProcAttr = &syscall.SysProcAttr{}
        cmd.SysProcAttr.Setsid = true
 
+       if p.UID != 0 || p.GID != 0 {
+               cmd.SysProcAttr.Credential = &syscall.Credential{}
+               cmd.SysProcAttr.Credential.Uid = p.UID
+               cmd.SysProcAttr.Credential.Gid = p.GID
+       }
+
        // Setup output capture.
        if p.Stdout != "" {
                out, err := os.Create(p.Stdout)

From 7d13669ca6abb80c791c617dceaadca7e289e3d3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Mon, 10 Aug 2020 16:19:14 -0400
Subject: [PATCH 09/10] lxd/network: forkdns and creds drop for forkdns
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/network/driver_bridge.go | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/lxd/network/driver_bridge.go b/lxd/network/driver_bridge.go
index cb098e4b88..99542a2580 100644
--- a/lxd/network/driver_bridge.go
+++ b/lxd/network/driver_bridge.go
@@ -1544,6 +1544,12 @@ func (n *bridge) spawnForkDNS(listenAddress string) 
error {
                return fmt.Errorf("Failed to create subprocess: %s", err)
        }
 
+       // Drop privileges.
+       p.SetCreds(n.state.OS.UnprivUID, n.state.OS.UnprivGID)
+
+       // Apply AppArmor profile.
+       p.SetApparmor(apparmor.ForkdnsProfileName(n))
+
        err = p.Start()
        if err != nil {
                return fmt.Errorf("Failed to run: %s %s: %v", command, 
strings.Join(forkdnsargs, " "), err)

From e008641ea17315147f1c3585dc1b23c58479a1ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com>
Date: Mon, 10 Aug 2020 16:26:54 -0400
Subject: [PATCH 10/10] lxd/network: Run dnsmasq as unpriv group
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/network/driver_bridge.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/lxd/network/driver_bridge.go b/lxd/network/driver_bridge.go
index 99542a2580..6891af0033 100644
--- a/lxd/network/driver_bridge.go
+++ b/lxd/network/driver_bridge.go
@@ -1269,6 +1269,9 @@ func (n *bridge) setup(oldConfig map[string]string) error 
{
                if n.state.OS.UnprivUser != "" {
                        dnsmasqCmd = append(dnsmasqCmd, []string{"-u", 
n.state.OS.UnprivUser}...)
                }
+               if n.state.OS.UnprivGroup != "" {
+                       dnsmasqCmd = append(dnsmasqCmd, []string{"-g", 
n.state.OS.UnprivGroup}...)
+               }
 
                // Create DHCP hosts directory.
                if !shared.PathExists(shared.VarPath("networks", n.name, 
"dnsmasq.hosts")) {
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to