From ba1c57030f10ece60678a848e04375e4cd2c843d Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <giuseppe@scrivano.org>
Date: Thu, 29 Aug 2019 22:47:15 +0200
Subject: rootless: bind mount devices instead of creating them

when running in rootless mode, --device creates a bind mount from the
host instead of specifying the device in the OCI configuration.  This
is required as an unprivileged user cannot use mknod, even when root
in a user namespace.

Closes: https://github.com/containers/libpod/issues/3905

Signed-off-by: Giuseppe Scrivano <giuseppe@scrivano.org>
---
 pkg/spec/config_linux.go    | 20 ++++++++++++++++++++
 test/e2e/run_device_test.go | 16 ++++++----------
 2 files changed, 26 insertions(+), 10 deletions(-)

diff --git a/pkg/spec/config_linux.go b/pkg/spec/config_linux.go
index 60d31d78e..dea9b393c 100644
--- a/pkg/spec/config_linux.go
+++ b/pkg/spec/config_linux.go
@@ -98,6 +98,26 @@ func addDevice(g *generate.Generator, device string) error {
 	if err != nil {
 		return errors.Wrapf(err, "%s is not a valid device", src)
 	}
+	if rootless.IsRootless() {
+		if _, err := os.Stat(src); err != nil {
+			if os.IsNotExist(err) {
+				return errors.Wrapf(err, "the specified device %s doesn't exist", src)
+			}
+			return errors.Wrapf(err, "stat device %s exist", src)
+		}
+		perm := "ro"
+		if strings.Contains(permissions, "w") {
+			perm = "rw"
+		}
+		devMnt := spec.Mount{
+			Destination: dst,
+			Type:        TypeBind,
+			Source:      src,
+			Options:     []string{"slave", "nosuid", "noexec", perm, "rbind"},
+		}
+		g.Config.Mounts = append(g.Config.Mounts, devMnt)
+		return nil
+	}
 	dev.Path = dst
 	linuxdev := spec.LinuxDevice{
 		Path:     dev.Path,
diff --git a/test/e2e/run_device_test.go b/test/e2e/run_device_test.go
index cf7ce9cdf..d3b4b0e32 100644
--- a/test/e2e/run_device_test.go
+++ b/test/e2e/run_device_test.go
@@ -35,44 +35,40 @@ var _ = Describe("Podman run device", func() {
 	})
 
 	It("podman run bad device test", func() {
-		session := podmanTest.Podman([]string{"run", "-q", "--device", "/dev/baddevice", ALPINE, "ls", "/dev/kmsg"})
+		session := podmanTest.Podman([]string{"run", "-q", "--device", "/dev/baddevice", ALPINE, "true"})
 		session.WaitWithDefaultTimeout()
 		Expect(session.ExitCode()).To(Not(Equal(0)))
 	})
 
 	It("podman run device test", func() {
-		SkipIfRootless()
-		session := podmanTest.Podman([]string{"run", "-q", "--device", "/dev/kmsg", ALPINE, "ls", "--color=never", "/dev/kmsg"})
+		session := podmanTest.Podman([]string{"run", "-q", "--security-opt", "label=disable", "--device", "/dev/kmsg", ALPINE, "ls", "--color=never", "/dev/kmsg"})
 		session.WaitWithDefaultTimeout()
 		Expect(session.ExitCode()).To(Equal(0))
 		Expect(session.OutputToString()).To(Equal("/dev/kmsg"))
 	})
 
 	It("podman run device rename test", func() {
-		SkipIfRootless()
-		session := podmanTest.Podman([]string{"run", "-q", "--device", "/dev/kmsg:/dev/kmsg1", ALPINE, "ls", "--color=never", "/dev/kmsg1"})
+		session := podmanTest.Podman([]string{"run", "-q", "--security-opt", "label=disable", "--device", "/dev/kmsg:/dev/kmsg1", ALPINE, "ls", "--color=never", "/dev/kmsg1"})
 		session.WaitWithDefaultTimeout()
 		Expect(session.ExitCode()).To(Equal(0))
 		Expect(session.OutputToString()).To(Equal("/dev/kmsg1"))
 	})
 
 	It("podman run device permission test", func() {
-		SkipIfRootless()
-		session := podmanTest.Podman([]string{"run", "-q", "--device", "/dev/kmsg:r", ALPINE, "ls", "--color=never", "/dev/kmsg"})
+		session := podmanTest.Podman([]string{"run", "-q", "--security-opt", "label=disable", "--device", "/dev/kmsg:r", ALPINE, "ls", "--color=never", "/dev/kmsg"})
 		session.WaitWithDefaultTimeout()
 		Expect(session.ExitCode()).To(Equal(0))
 		Expect(session.OutputToString()).To(Equal("/dev/kmsg"))
 	})
 
 	It("podman run device rename and permission test", func() {
-		SkipIfRootless()
-		session := podmanTest.Podman([]string{"run", "-q", "--device", "/dev/kmsg:/dev/kmsg1:r", ALPINE, "ls", "--color=never", "/dev/kmsg1"})
+		session := podmanTest.Podman([]string{"run", "-q", "--security-opt", "label=disable", "--device", "/dev/kmsg:/dev/kmsg1:r", ALPINE, "ls", "--color=never", "/dev/kmsg1"})
 		session.WaitWithDefaultTimeout()
 		Expect(session.ExitCode()).To(Equal(0))
 		Expect(session.OutputToString()).To(Equal("/dev/kmsg1"))
 	})
 	It("podman run device rename and bad permission test", func() {
-		session := podmanTest.Podman([]string{"run", "-q", "--device", "/dev/kmsg:/dev/kmsg1:rd", ALPINE, "ls", "--color=never", "/dev/kmsg1"})
+		session := podmanTest.Podman([]string{"run", "-q", "--security-opt", "label=disable", "--device", "/dev/kmsg:/dev/kmsg1:rd", ALPINE, "ls", "--color=never", "/dev/kmsg1"})
 		session.WaitWithDefaultTimeout()
 		Expect(session.ExitCode()).To(Not(Equal(0)))
 	})
-- 
cgit v1.2.3-54-g00ecf


From b101a8d3664f054157a9e3f08a6bf8db0144041c Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <giuseppe@scrivano.org>
Date: Sun, 1 Sep 2019 00:30:55 +0200
Subject: spec: do not set devices cgroup when rootless

eBPF requires to be root in the init namespace.

Signed-off-by: Giuseppe Scrivano <giuseppe@scrivano.org>
---
 pkg/spec/config_linux.go | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/pkg/spec/config_linux.go b/pkg/spec/config_linux.go
index dea9b393c..9636d7a11 100644
--- a/pkg/spec/config_linux.go
+++ b/pkg/spec/config_linux.go
@@ -173,15 +173,16 @@ func (c *CreateConfig) addPrivilegedDevices(g *generate.Generator) error {
 			newMounts = append(newMounts, devMnt)
 		}
 		g.Config.Mounts = append(newMounts, g.Config.Mounts...)
+		g.Config.Linux.Resources.Devices = nil
 	} else {
 		for _, d := range hostDevices {
 			g.AddDevice(Device(d))
 		}
+		// Add resources device - need to clear the existing one first.
+		g.Config.Linux.Resources.Devices = nil
+		g.AddLinuxResourcesDevice(true, "", nil, nil, "rwm")
 	}
 
-	// Add resources device - need to clear the existing one first.
-	g.Config.Linux.Resources.Devices = nil
-	g.AddLinuxResourcesDevice(true, "", nil, nil, "rwm")
 	return nil
 }
 
-- 
cgit v1.2.3-54-g00ecf


From 759ca2cfc66c372dd197b4a2b6cf2b454b497d00 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <giuseppe@scrivano.org>
Date: Sun, 1 Sep 2019 00:27:29 +0200
Subject: spec: provide custom implementation for getDevices

provide an implementation for getDevices that skip unreadable
directories for the current user.

Based on the implementation from runc/libcontainer.

Closes: https://github.com/containers/libpod/issues/3919

Signed-off-by: Giuseppe Scrivano <giuseppe@scrivano.org>
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
---
 pkg/spec/config_linux.go | 48 +++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 47 insertions(+), 1 deletion(-)

diff --git a/pkg/spec/config_linux.go b/pkg/spec/config_linux.go
index 9636d7a11..32d8cb4de 100644
--- a/pkg/spec/config_linux.go
+++ b/pkg/spec/config_linux.go
@@ -4,6 +4,7 @@ package createconfig
 
 import (
 	"fmt"
+	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
@@ -133,8 +134,53 @@ func addDevice(g *generate.Generator, device string) error {
 	return nil
 }
 
+// based on getDevices from runc (libcontainer/devices/devices.go)
+func getDevices(path string) ([]*configs.Device, error) {
+	files, err := ioutil.ReadDir(path)
+	if err != nil {
+		if rootless.IsRootless() && os.IsPermission(err) {
+			return nil, nil
+		}
+		return nil, err
+	}
+	out := []*configs.Device{}
+	for _, f := range files {
+		switch {
+		case f.IsDir():
+			switch f.Name() {
+			// ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825
+			case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts":
+				continue
+			default:
+				sub, err := getDevices(filepath.Join(path, f.Name()))
+				if err != nil {
+					return nil, err
+				}
+				if sub != nil {
+					out = append(out, sub...)
+				}
+				continue
+			}
+		case f.Name() == "console":
+			continue
+		}
+		device, err := devices.DeviceFromPath(filepath.Join(path, f.Name()), "rwm")
+		if err != nil {
+			if err == devices.ErrNotADevice {
+				continue
+			}
+			if os.IsNotExist(err) {
+				continue
+			}
+			return nil, err
+		}
+		out = append(out, device)
+	}
+	return out, nil
+}
+
 func (c *CreateConfig) addPrivilegedDevices(g *generate.Generator) error {
-	hostDevices, err := devices.HostDevices()
+	hostDevices, err := getDevices("/dev")
 	if err != nil {
 		return err
 	}
-- 
cgit v1.2.3-54-g00ecf