[lxc-devel] [lxd/master] Reduce privileges and confine forkdns

stgraber on Github lxc-bot at linuxcontainers.org
Mon Aug 10 21:39:25 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 974 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200810/0e214d5c/attachment.bin>
-------------- next part --------------
From dd4e97f3bb84cd4919b48e207dc5796abc68dc14 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at 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 <stgraber at 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?= <stgraber at 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 <stgraber at 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?= <stgraber at 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 <stgraber at 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?= <stgraber at 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 <stgraber at 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?= <stgraber at 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 <stgraber at 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?= <stgraber at 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 <stgraber at 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?= <stgraber at 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 <stgraber at 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?= <stgraber at 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 <stgraber at 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?= <stgraber at 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 <stgraber at 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?= <stgraber at 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 <stgraber at 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")) {


More information about the lxc-devel mailing list