import runc-1.0.0-74.rc95.module+el8.4.0+11822+6cc1e7d7
This commit is contained in:
parent
8f09df8e07
commit
9cae674ff5
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1 @@
|
||||
SOURCES/v1.0.0-rc93.tar.gz
|
||||
SOURCES/v1.0.0-rc95.tar.gz
|
||||
|
@ -1 +1 @@
|
||||
e8693109441696536710e5751e0fee6e6fa32590 SOURCES/v1.0.0-rc93.tar.gz
|
||||
23f05a9f1ae2907117385a48f1a464608fd75d30 SOURCES/v1.0.0-rc95.tar.gz
|
||||
|
@ -1,40 +0,0 @@
|
||||
From 28daf0653d324fad545a7031e64b6891f399969b Mon Sep 17 00:00:00 2001
|
||||
From: Kir Kolyshkin <kolyshkin@gmail.com>
|
||||
Date: Tue, 23 Feb 2021 17:58:07 -0800
|
||||
Subject: [PATCH 1/5] libct/newInitConfig: nit
|
||||
|
||||
Move the initialization of Console* fields as they are unconditional.
|
||||
|
||||
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
|
||||
---
|
||||
libcontainer/container_linux.go | 7 ++++---
|
||||
1 file changed, 4 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go
|
||||
index 3dca29e4c3f2..b6100aae9d5a 100644
|
||||
--- a/libcontainer/container_linux.go
|
||||
+++ b/libcontainer/container_linux.go
|
||||
@@ -594,6 +594,9 @@ func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
|
||||
AppArmorProfile: c.config.AppArmorProfile,
|
||||
ProcessLabel: c.config.ProcessLabel,
|
||||
Rlimits: c.config.Rlimits,
|
||||
+ CreateConsole: process.ConsoleSocket != nil,
|
||||
+ ConsoleWidth: process.ConsoleWidth,
|
||||
+ ConsoleHeight: process.ConsoleHeight,
|
||||
}
|
||||
if process.NoNewPrivileges != nil {
|
||||
cfg.NoNewPrivileges = *process.NoNewPrivileges
|
||||
@@ -607,9 +610,7 @@ func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
|
||||
if len(process.Rlimits) > 0 {
|
||||
cfg.Rlimits = process.Rlimits
|
||||
}
|
||||
- cfg.CreateConsole = process.ConsoleSocket != nil
|
||||
- cfg.ConsoleWidth = process.ConsoleWidth
|
||||
- cfg.ConsoleHeight = process.ConsoleHeight
|
||||
+
|
||||
return cfg
|
||||
}
|
||||
|
||||
--
|
||||
2.31.1
|
||||
|
@ -1,139 +0,0 @@
|
||||
From 46ec7b5a94d370c4963ca361e9d96cb78d75d118 Mon Sep 17 00:00:00 2001
|
||||
From: Kir Kolyshkin <kolyshkin@gmail.com>
|
||||
Date: Tue, 23 Feb 2021 18:14:37 -0800
|
||||
Subject: [PATCH 2/5] libct/rootfs: introduce and use mountConfig
|
||||
|
||||
The code is already passing three parameters around from
|
||||
mountToRootfs to mountCgroupV* to mountToRootfs again.
|
||||
|
||||
I am about to add another parameter, so let's introduce and
|
||||
use struct mountConfig to pass around.
|
||||
|
||||
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
|
||||
---
|
||||
libcontainer/rootfs_linux.go | 42 ++++++++++++++++++++++--------------
|
||||
1 file changed, 26 insertions(+), 16 deletions(-)
|
||||
|
||||
diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go
|
||||
index 411496ab7c6d..a384abb7e8a5 100644
|
||||
--- a/libcontainer/rootfs_linux.go
|
||||
+++ b/libcontainer/rootfs_linux.go
|
||||
@@ -29,6 +29,12 @@ import (
|
||||
|
||||
const defaultMountFlags = unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV
|
||||
|
||||
+type mountConfig struct {
|
||||
+ root string
|
||||
+ label string
|
||||
+ cgroupns bool
|
||||
+}
|
||||
+
|
||||
// needsSetupDev returns true if /dev needs to be set up.
|
||||
func needsSetupDev(config *configs.Config) bool {
|
||||
for _, m := range config.Mounts {
|
||||
@@ -48,7 +54,11 @@ func prepareRootfs(pipe io.ReadWriter, iConfig *initConfig) (err error) {
|
||||
return newSystemErrorWithCause(err, "preparing rootfs")
|
||||
}
|
||||
|
||||
- hasCgroupns := config.Namespaces.Contains(configs.NEWCGROUP)
|
||||
+ mountConfig := &mountConfig{
|
||||
+ root: config.Rootfs,
|
||||
+ label: config.MountLabel,
|
||||
+ cgroupns: config.Namespaces.Contains(configs.NEWCGROUP),
|
||||
+ }
|
||||
setupDev := needsSetupDev(config)
|
||||
for _, m := range config.Mounts {
|
||||
for _, precmd := range m.PremountCmds {
|
||||
@@ -56,7 +66,7 @@ func prepareRootfs(pipe io.ReadWriter, iConfig *initConfig) (err error) {
|
||||
return newSystemErrorWithCause(err, "running premount command")
|
||||
}
|
||||
}
|
||||
- if err := mountToRootfs(m, config.Rootfs, config.MountLabel, hasCgroupns); err != nil {
|
||||
+ if err := mountToRootfs(m, mountConfig); err != nil {
|
||||
return newSystemErrorWithCausef(err, "mounting %q to rootfs at %q", m.Source, m.Destination)
|
||||
}
|
||||
|
||||
@@ -222,7 +232,7 @@ func prepareBindMount(m *configs.Mount, rootfs string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
-func mountCgroupV1(m *configs.Mount, rootfs, mountLabel string, enableCgroupns bool) error {
|
||||
+func mountCgroupV1(m *configs.Mount, c *mountConfig) error {
|
||||
binds, err := getCgroupMounts(m)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -242,12 +252,12 @@ func mountCgroupV1(m *configs.Mount, rootfs, mountLabel string, enableCgroupns b
|
||||
Data: "mode=755",
|
||||
PropagationFlags: m.PropagationFlags,
|
||||
}
|
||||
- if err := mountToRootfs(tmpfs, rootfs, mountLabel, enableCgroupns); err != nil {
|
||||
+ if err := mountToRootfs(tmpfs, c); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, b := range binds {
|
||||
- if enableCgroupns {
|
||||
- subsystemPath := filepath.Join(rootfs, b.Destination)
|
||||
+ if c.cgroupns {
|
||||
+ subsystemPath := filepath.Join(c.root, b.Destination)
|
||||
if err := os.MkdirAll(subsystemPath, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -266,7 +276,7 @@ func mountCgroupV1(m *configs.Mount, rootfs, mountLabel string, enableCgroupns b
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
- if err := mountToRootfs(b, rootfs, mountLabel, enableCgroupns); err != nil {
|
||||
+ if err := mountToRootfs(b, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -276,7 +286,7 @@ func mountCgroupV1(m *configs.Mount, rootfs, mountLabel string, enableCgroupns b
|
||||
// symlink(2) is very dumb, it will just shove the path into
|
||||
// the link and doesn't do any checks or relative path
|
||||
// conversion. Also, don't error out if the cgroup already exists.
|
||||
- if err := os.Symlink(mc, filepath.Join(rootfs, m.Destination, ss)); err != nil && !os.IsExist(err) {
|
||||
+ if err := os.Symlink(mc, filepath.Join(c.root, m.Destination, ss)); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -284,8 +294,8 @@ func mountCgroupV1(m *configs.Mount, rootfs, mountLabel string, enableCgroupns b
|
||||
return nil
|
||||
}
|
||||
|
||||
-func mountCgroupV2(m *configs.Mount, rootfs, mountLabel string, enableCgroupns bool) error {
|
||||
- cgroupPath, err := securejoin.SecureJoin(rootfs, m.Destination)
|
||||
+func mountCgroupV2(m *configs.Mount, c *mountConfig) error {
|
||||
+ cgroupPath, err := securejoin.SecureJoin(c.root, m.Destination)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -302,10 +312,10 @@ func mountCgroupV2(m *configs.Mount, rootfs, mountLabel string, enableCgroupns b
|
||||
return nil
|
||||
}
|
||||
|
||||
-func mountToRootfs(m *configs.Mount, rootfs, mountLabel string, enableCgroupns bool) error {
|
||||
- var (
|
||||
- dest = m.Destination
|
||||
- )
|
||||
+func mountToRootfs(m *configs.Mount, c *mountConfig) error {
|
||||
+ rootfs := c.root
|
||||
+ mountLabel := c.label
|
||||
+ dest := m.Destination
|
||||
if !strings.HasPrefix(dest, rootfs) {
|
||||
dest = filepath.Join(rootfs, dest)
|
||||
}
|
||||
@@ -424,9 +434,9 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string, enableCgroupns b
|
||||
}
|
||||
case "cgroup":
|
||||
if cgroups.IsCgroup2UnifiedMode() {
|
||||
- return mountCgroupV2(m, rootfs, mountLabel, enableCgroupns)
|
||||
+ return mountCgroupV2(m, c)
|
||||
}
|
||||
- return mountCgroupV1(m, rootfs, mountLabel, enableCgroupns)
|
||||
+ return mountCgroupV1(m, c)
|
||||
default:
|
||||
// ensure that the destination of the mount is resolved of symlinks at mount time because
|
||||
// any previous mounts can invalidate the next mount's destination.
|
||||
--
|
||||
2.31.1
|
||||
|
@ -1,52 +0,0 @@
|
||||
From 198a2806b0b5522cff1c53bf4671cfee85e45608 Mon Sep 17 00:00:00 2001
|
||||
From: Kir Kolyshkin <kolyshkin@gmail.com>
|
||||
Date: Tue, 23 Feb 2021 18:25:56 -0800
|
||||
Subject: [PATCH 3/5] libct/rootfs/mountCgroupV2: minor refactor
|
||||
|
||||
1. s/cgroupPath/dest/
|
||||
|
||||
2. don't hardcode /sys/fs/cgroup
|
||||
|
||||
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
|
||||
---
|
||||
libcontainer/rootfs_linux.go | 10 ++++++----
|
||||
1 file changed, 6 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go
|
||||
index a384abb7e8a5..0f0495b93b3e 100644
|
||||
--- a/libcontainer/rootfs_linux.go
|
||||
+++ b/libcontainer/rootfs_linux.go
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/moby/sys/mountinfo"
|
||||
"github.com/mrunalp/fileutils"
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups"
|
||||
+ "github.com/opencontainers/runc/libcontainer/cgroups/fs2"
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
"github.com/opencontainers/runc/libcontainer/devices"
|
||||
"github.com/opencontainers/runc/libcontainer/system"
|
||||
@@ -295,17 +296,18 @@ func mountCgroupV1(m *configs.Mount, c *mountConfig) error {
|
||||
}
|
||||
|
||||
func mountCgroupV2(m *configs.Mount, c *mountConfig) error {
|
||||
- cgroupPath, err := securejoin.SecureJoin(c.root, m.Destination)
|
||||
+ dest, err := securejoin.SecureJoin(c.root, m.Destination)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
- if err := os.MkdirAll(cgroupPath, 0755); err != nil {
|
||||
+ if err := os.MkdirAll(dest, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
- if err := unix.Mount(m.Source, cgroupPath, "cgroup2", uintptr(m.Flags), m.Data); err != nil {
|
||||
+ if err := unix.Mount(m.Source, dest, "cgroup2", uintptr(m.Flags), m.Data); err != nil {
|
||||
// when we are in UserNS but CgroupNS is not unshared, we cannot mount cgroup2 (#2158)
|
||||
if err == unix.EPERM || err == unix.EBUSY {
|
||||
- return unix.Mount("/sys/fs/cgroup", cgroupPath, "", uintptr(m.Flags)|unix.MS_BIND, "")
|
||||
+ src := fs2.UnifiedMountpoint
|
||||
+ return unix.Mount(src, dest, "", uintptr(m.Flags)|unix.MS_BIND, "")
|
||||
}
|
||||
return err
|
||||
}
|
||||
--
|
||||
2.31.1
|
||||
|
@ -1,180 +0,0 @@
|
||||
From ce352accdfb07a91b5527e70ec8bce658a8b68de Mon Sep 17 00:00:00 2001
|
||||
From: Kir Kolyshkin <kolyshkin@gmail.com>
|
||||
Date: Tue, 23 Feb 2021 18:27:42 -0800
|
||||
Subject: [PATCH 4/5] Fix cgroup2 mount for rootless case
|
||||
|
||||
In case of rootless, cgroup2 mount is not possible (see [1] for more
|
||||
details), so since commit 9c81440fb5a7 runc bind-mounts the whole
|
||||
/sys/fs/cgroup into container.
|
||||
|
||||
Problem is, if cgroupns is enabled, /sys/fs/cgroup inside the container
|
||||
is supposed to show the cgroup files for this cgroup, not the root one.
|
||||
|
||||
The fix is to pass through and use the cgroup path in case cgroup2
|
||||
mount failed, cgroupns is enabled, and the path is non-empty.
|
||||
|
||||
Surely this requires the /sys/fs/cgroup mount in the spec, so modify
|
||||
runc spec --rootless to keep it.
|
||||
|
||||
Before:
|
||||
|
||||
$ ./runc run aaa
|
||||
# find /sys/fs/cgroup/ -type d
|
||||
/sys/fs/cgroup
|
||||
/sys/fs/cgroup/user.slice
|
||||
/sys/fs/cgroup/user.slice/user-1000.slice
|
||||
/sys/fs/cgroup/user.slice/user-1000.slice/user@1000.service
|
||||
...
|
||||
# ls -l /sys/fs/cgroup/cgroup.controllers
|
||||
-r--r--r-- 1 nobody nogroup 0 Feb 24 02:22 /sys/fs/cgroup/cgroup.controllers
|
||||
# wc -w /sys/fs/cgroup/cgroup.procs
|
||||
142 /sys/fs/cgroup/cgroup.procs
|
||||
# cat /sys/fs/cgroup/memory.current
|
||||
cat: can't open '/sys/fs/cgroup/memory.current': No such file or directory
|
||||
|
||||
After:
|
||||
|
||||
# find /sys/fs/cgroup/ -type d
|
||||
/sys/fs/cgroup/
|
||||
# ls -l /sys/fs/cgroup/cgroup.controllers
|
||||
-r--r--r-- 1 root root 0 Feb 24 02:43 /sys/fs/cgroup/cgroup.controllers
|
||||
# wc -w /sys/fs/cgroup/cgroup.procs
|
||||
2 /sys/fs/cgroup/cgroup.procs
|
||||
# cat /sys/fs/cgroup/memory.current
|
||||
577536
|
||||
|
||||
[1] https://github.com/opencontainers/runc/issues/2158
|
||||
|
||||
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
|
||||
---
|
||||
libcontainer/container_linux.go | 3 +++
|
||||
libcontainer/init_linux.go | 1 +
|
||||
libcontainer/rootfs_linux.go | 28 +++++++++++++++++++++-------
|
||||
libcontainer/specconv/example.go | 18 +++++++++---------
|
||||
4 files changed, 34 insertions(+), 16 deletions(-)
|
||||
|
||||
diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go
|
||||
index b6100aae9d5a..1cbc734172d0 100644
|
||||
--- a/libcontainer/container_linux.go
|
||||
+++ b/libcontainer/container_linux.go
|
||||
@@ -610,6 +610,9 @@ func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
|
||||
if len(process.Rlimits) > 0 {
|
||||
cfg.Rlimits = process.Rlimits
|
||||
}
|
||||
+ if cgroups.IsCgroup2UnifiedMode() {
|
||||
+ cfg.Cgroup2Path = c.cgroupManager.Path("")
|
||||
+ }
|
||||
|
||||
return cfg
|
||||
}
|
||||
diff --git a/libcontainer/init_linux.go b/libcontainer/init_linux.go
|
||||
index c57af0eebb8b..681797099f46 100644
|
||||
--- a/libcontainer/init_linux.go
|
||||
+++ b/libcontainer/init_linux.go
|
||||
@@ -70,6 +70,7 @@ type initConfig struct {
|
||||
RootlessEUID bool `json:"rootless_euid,omitempty"`
|
||||
RootlessCgroups bool `json:"rootless_cgroups,omitempty"`
|
||||
SpecState *specs.State `json:"spec_state,omitempty"`
|
||||
+ Cgroup2Path string `json:"cgroup2_path,omitempty"`
|
||||
}
|
||||
|
||||
type initer interface {
|
||||
diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go
|
||||
index 0f0495b93b3e..5d2d74cf924b 100644
|
||||
--- a/libcontainer/rootfs_linux.go
|
||||
+++ b/libcontainer/rootfs_linux.go
|
||||
@@ -31,9 +31,11 @@ import (
|
||||
const defaultMountFlags = unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV
|
||||
|
||||
type mountConfig struct {
|
||||
- root string
|
||||
- label string
|
||||
- cgroupns bool
|
||||
+ root string
|
||||
+ label string
|
||||
+ cgroup2Path string
|
||||
+ rootlessCgroups bool
|
||||
+ cgroupns bool
|
||||
}
|
||||
|
||||
// needsSetupDev returns true if /dev needs to be set up.
|
||||
@@ -56,9 +58,11 @@ func prepareRootfs(pipe io.ReadWriter, iConfig *initConfig) (err error) {
|
||||
}
|
||||
|
||||
mountConfig := &mountConfig{
|
||||
- root: config.Rootfs,
|
||||
- label: config.MountLabel,
|
||||
- cgroupns: config.Namespaces.Contains(configs.NEWCGROUP),
|
||||
+ root: config.Rootfs,
|
||||
+ label: config.MountLabel,
|
||||
+ cgroup2Path: iConfig.Cgroup2Path,
|
||||
+ rootlessCgroups: iConfig.RootlessCgroups,
|
||||
+ cgroupns: config.Namespaces.Contains(configs.NEWCGROUP),
|
||||
}
|
||||
setupDev := needsSetupDev(config)
|
||||
for _, m := range config.Mounts {
|
||||
@@ -307,7 +311,17 @@ func mountCgroupV2(m *configs.Mount, c *mountConfig) error {
|
||||
// when we are in UserNS but CgroupNS is not unshared, we cannot mount cgroup2 (#2158)
|
||||
if err == unix.EPERM || err == unix.EBUSY {
|
||||
src := fs2.UnifiedMountpoint
|
||||
- return unix.Mount(src, dest, "", uintptr(m.Flags)|unix.MS_BIND, "")
|
||||
+ if c.cgroupns && c.cgroup2Path != "" {
|
||||
+ // Emulate cgroupns by bind-mounting
|
||||
+ // the container cgroup path rather than
|
||||
+ // the whole /sys/fs/cgroup.
|
||||
+ src = c.cgroup2Path
|
||||
+ }
|
||||
+ err = unix.Mount(src, dest, "", uintptr(m.Flags)|unix.MS_BIND, "")
|
||||
+ if err == unix.ENOENT && c.rootlessCgroups {
|
||||
+ err = nil
|
||||
+ }
|
||||
+ return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
diff --git a/libcontainer/specconv/example.go b/libcontainer/specconv/example.go
|
||||
index 8a201bc78dd9..56bab3bfbfa5 100644
|
||||
--- a/libcontainer/specconv/example.go
|
||||
+++ b/libcontainer/specconv/example.go
|
||||
@@ -2,6 +2,7 @@ package specconv
|
||||
|
||||
import (
|
||||
"os"
|
||||
+ "path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups"
|
||||
@@ -200,8 +201,14 @@ func ToRootless(spec *specs.Spec) {
|
||||
// Fix up mounts.
|
||||
var mounts []specs.Mount
|
||||
for _, mount := range spec.Mounts {
|
||||
- // Ignore all mounts that are under /sys.
|
||||
- if strings.HasPrefix(mount.Destination, "/sys") {
|
||||
+ // Replace the /sys mount with an rbind.
|
||||
+ if filepath.Clean(mount.Destination) == "/sys" {
|
||||
+ mounts = append(mounts, specs.Mount{
|
||||
+ Source: "/sys",
|
||||
+ Destination: "/sys",
|
||||
+ Type: "none",
|
||||
+ Options: []string{"rbind", "nosuid", "noexec", "nodev", "ro"},
|
||||
+ })
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -216,13 +223,6 @@ func ToRootless(spec *specs.Spec) {
|
||||
mount.Options = options
|
||||
mounts = append(mounts, mount)
|
||||
}
|
||||
- // Add the sysfs mount as an rbind.
|
||||
- mounts = append(mounts, specs.Mount{
|
||||
- Source: "/sys",
|
||||
- Destination: "/sys",
|
||||
- Type: "none",
|
||||
- Options: []string{"rbind", "nosuid", "noexec", "nodev", "ro"},
|
||||
- })
|
||||
spec.Mounts = mounts
|
||||
|
||||
// Remove cgroup settings.
|
||||
--
|
||||
2.31.1
|
||||
|
@ -1,562 +0,0 @@
|
||||
From 14faf1c20948688a48edb9b41367ab07ac11ca91 Mon Sep 17 00:00:00 2001
|
||||
From: Aleksa Sarai <cyphar@cyphar.com>
|
||||
Date: Thu, 1 Apr 2021 12:00:31 -0700
|
||||
Subject: [PATCH 5/5] rootfs: add mount destination validation
|
||||
|
||||
Because the target of a mount is inside a container (which may be a
|
||||
volume that is shared with another container), there exists a race
|
||||
condition where the target of the mount may change to a path containing
|
||||
a symlink after we have sanitised the path -- resulting in us
|
||||
inadvertently mounting the path outside of the container.
|
||||
|
||||
This is not immediately useful because we are in a mount namespace with
|
||||
MS_SLAVE mount propagation applied to "/", so we cannot mount on top of
|
||||
host paths in the host namespace. However, if any subsequent mountpoints
|
||||
in the configuration use a subdirectory of that host path as a source,
|
||||
those subsequent mounts will use an attacker-controlled source path
|
||||
(resolved within the host rootfs) -- allowing the bind-mounting of "/"
|
||||
into the container.
|
||||
|
||||
While arguably configuration issues like this are not entirely within
|
||||
runc's threat model, within the context of Kubernetes (and possibly
|
||||
other container managers that provide semi-arbitrary container creation
|
||||
privileges to untrusted users) this is a legitimate issue. Since we
|
||||
cannot block mounting from the host into the container, we need to block
|
||||
the first stage of this attack (mounting onto a path outside the
|
||||
container).
|
||||
|
||||
The long-term plan to solve this would be to migrate to libpathrs, but
|
||||
as a stop-gap we implement libpathrs-like path verification through
|
||||
readlink(/proc/self/fd/$n) and then do mount operations through the
|
||||
procfd once it's been verified to be inside the container. The target
|
||||
could move after we've checked it, but if it is inside the container
|
||||
then we can assume that it is safe for the same reason that libpathrs
|
||||
operations would be safe.
|
||||
|
||||
A slight wrinkle is the "copyup" functionality we provide for tmpfs,
|
||||
which is the only case where we want to do a mount on the host
|
||||
filesystem. To facilitate this, I split out the copy-up functionality
|
||||
entirely so that the logic isn't interspersed with the regular tmpfs
|
||||
logic. In addition, all dependencies on m.Destination being overwritten
|
||||
have been removed since that pattern was just begging to be a source of
|
||||
more mount-target bugs (we do still have to modify m.Destination for
|
||||
tmpfs-copyup but we only do it temporarily).
|
||||
|
||||
Fixes: CVE-2021-30465
|
||||
Reported-by: Etienne Champetier <champetier.etienne@gmail.com>
|
||||
Co-authored-by: Noah Meyerhans <nmeyerha@amazon.com>
|
||||
Reviewed-by: Samuel Karp <skarp@amazon.com>
|
||||
Reviewed-by: Kir Kolyshkin <kolyshkin@gmail.com> (@kolyshkin)
|
||||
Reviewed-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
|
||||
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
|
||||
---
|
||||
libcontainer/container_linux.go | 1 -
|
||||
libcontainer/rootfs_linux.go | 251 +++++++++++++++----------------
|
||||
libcontainer/utils/utils.go | 54 +++++++
|
||||
libcontainer/utils/utils_test.go | 35 +++++
|
||||
4 files changed, 213 insertions(+), 128 deletions(-)
|
||||
|
||||
diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go
|
||||
index 1cbc734172d0..70b388b1252e 100644
|
||||
--- a/libcontainer/container_linux.go
|
||||
+++ b/libcontainer/container_linux.go
|
||||
@@ -1202,7 +1202,6 @@ func (c *linuxContainer) makeCriuRestoreMountpoints(m *configs.Mount) error {
|
||||
if err := checkProcMount(c.config.Rootfs, dest, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
- m.Destination = dest
|
||||
if err := os.MkdirAll(dest, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go
|
||||
index 5d2d74cf924b..96be669c365e 100644
|
||||
--- a/libcontainer/rootfs_linux.go
|
||||
+++ b/libcontainer/rootfs_linux.go
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils"
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/opencontainers/selinux/go-selinux/label"
|
||||
+ "github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
@@ -228,8 +229,6 @@ func prepareBindMount(m *configs.Mount, rootfs string) error {
|
||||
if err := checkProcMount(rootfs, dest, m.Source); err != nil {
|
||||
return err
|
||||
}
|
||||
- // update the mount with the correct dest after symlinks are resolved.
|
||||
- m.Destination = dest
|
||||
if err := createIfNotExists(dest, stat.IsDir()); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -266,18 +265,21 @@ func mountCgroupV1(m *configs.Mount, c *mountConfig) error {
|
||||
if err := os.MkdirAll(subsystemPath, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
- flags := defaultMountFlags
|
||||
- if m.Flags&unix.MS_RDONLY != 0 {
|
||||
- flags = flags | unix.MS_RDONLY
|
||||
- }
|
||||
- cgroupmount := &configs.Mount{
|
||||
- Source: "cgroup",
|
||||
- Device: "cgroup", // this is actually fstype
|
||||
- Destination: subsystemPath,
|
||||
- Flags: flags,
|
||||
- Data: filepath.Base(subsystemPath),
|
||||
- }
|
||||
- if err := mountNewCgroup(cgroupmount); err != nil {
|
||||
+ if err := utils.WithProcfd(c.root, b.Destination, func(procfd string) error {
|
||||
+ flags := defaultMountFlags
|
||||
+ if m.Flags&unix.MS_RDONLY != 0 {
|
||||
+ flags = flags | unix.MS_RDONLY
|
||||
+ }
|
||||
+ var (
|
||||
+ source = "cgroup"
|
||||
+ data = filepath.Base(subsystemPath)
|
||||
+ )
|
||||
+ if data == "systemd" {
|
||||
+ data = cgroups.CgroupNamePrefix + data
|
||||
+ source = "systemd"
|
||||
+ }
|
||||
+ return unix.Mount(source, procfd, "cgroup", uintptr(flags), data)
|
||||
+ }); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@@ -307,33 +309,79 @@ func mountCgroupV2(m *configs.Mount, c *mountConfig) error {
|
||||
if err := os.MkdirAll(dest, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
- if err := unix.Mount(m.Source, dest, "cgroup2", uintptr(m.Flags), m.Data); err != nil {
|
||||
- // when we are in UserNS but CgroupNS is not unshared, we cannot mount cgroup2 (#2158)
|
||||
- if err == unix.EPERM || err == unix.EBUSY {
|
||||
- src := fs2.UnifiedMountpoint
|
||||
- if c.cgroupns && c.cgroup2Path != "" {
|
||||
- // Emulate cgroupns by bind-mounting
|
||||
- // the container cgroup path rather than
|
||||
- // the whole /sys/fs/cgroup.
|
||||
- src = c.cgroup2Path
|
||||
- }
|
||||
- err = unix.Mount(src, dest, "", uintptr(m.Flags)|unix.MS_BIND, "")
|
||||
- if err == unix.ENOENT && c.rootlessCgroups {
|
||||
- err = nil
|
||||
+ return utils.WithProcfd(c.root, m.Destination, func(procfd string) error {
|
||||
+ if err := unix.Mount(m.Source, procfd, "cgroup2", uintptr(m.Flags), m.Data); err != nil {
|
||||
+ // when we are in UserNS but CgroupNS is not unshared, we cannot mount cgroup2 (#2158)
|
||||
+ if err == unix.EPERM || err == unix.EBUSY {
|
||||
+ src := fs2.UnifiedMountpoint
|
||||
+ if c.cgroupns && c.cgroup2Path != "" {
|
||||
+ // Emulate cgroupns by bind-mounting
|
||||
+ // the container cgroup path rather than
|
||||
+ // the whole /sys/fs/cgroup.
|
||||
+ src = c.cgroup2Path
|
||||
+ }
|
||||
+ err = unix.Mount(src, procfd, "", uintptr(m.Flags)|unix.MS_BIND, "")
|
||||
+ if err == unix.ENOENT && c.rootlessCgroups {
|
||||
+ err = nil
|
||||
+ }
|
||||
}
|
||||
return err
|
||||
}
|
||||
+ return nil
|
||||
+ })
|
||||
+}
|
||||
+
|
||||
+func doTmpfsCopyUp(m *configs.Mount, rootfs, mountLabel string) (Err error) {
|
||||
+ // Set up a scratch dir for the tmpfs on the host.
|
||||
+ tmpdir, err := prepareTmp("/tmp")
|
||||
+ if err != nil {
|
||||
+ return newSystemErrorWithCause(err, "tmpcopyup: failed to setup tmpdir")
|
||||
+ }
|
||||
+ defer cleanupTmp(tmpdir)
|
||||
+ tmpDir, err := ioutil.TempDir(tmpdir, "runctmpdir")
|
||||
+ if err != nil {
|
||||
+ return newSystemErrorWithCause(err, "tmpcopyup: failed to create tmpdir")
|
||||
+ }
|
||||
+ defer os.RemoveAll(tmpDir)
|
||||
+
|
||||
+ // Configure the *host* tmpdir as if it's the container mount. We change
|
||||
+ // m.Destination since we are going to mount *on the host*.
|
||||
+ oldDest := m.Destination
|
||||
+ m.Destination = tmpDir
|
||||
+ err = mountPropagate(m, "/", mountLabel)
|
||||
+ m.Destination = oldDest
|
||||
+ if err != nil {
|
||||
return err
|
||||
}
|
||||
- return nil
|
||||
+ defer func() {
|
||||
+ if Err != nil {
|
||||
+ if err := unix.Unmount(tmpDir, unix.MNT_DETACH); err != nil {
|
||||
+ logrus.Warnf("tmpcopyup: failed to unmount tmpdir on error: %v", err)
|
||||
+ }
|
||||
+ }
|
||||
+ }()
|
||||
+
|
||||
+ return utils.WithProcfd(rootfs, m.Destination, func(procfd string) (Err error) {
|
||||
+ // Copy the container data to the host tmpdir. We append "/" to force
|
||||
+ // CopyDirectory to resolve the symlink rather than trying to copy the
|
||||
+ // symlink itself.
|
||||
+ if err := fileutils.CopyDirectory(procfd+"/", tmpDir); err != nil {
|
||||
+ return fmt.Errorf("tmpcopyup: failed to copy %s to %s (%s): %w", m.Destination, procfd, tmpDir, err)
|
||||
+ }
|
||||
+ // Now move the mount into the container.
|
||||
+ if err := unix.Mount(tmpDir, procfd, "", unix.MS_MOVE, ""); err != nil {
|
||||
+ return fmt.Errorf("tmpcopyup: failed to move mount %s to %s (%s): %w", tmpDir, procfd, m.Destination, err)
|
||||
+ }
|
||||
+ return nil
|
||||
+ })
|
||||
}
|
||||
|
||||
func mountToRootfs(m *configs.Mount, c *mountConfig) error {
|
||||
rootfs := c.root
|
||||
mountLabel := c.label
|
||||
- dest := m.Destination
|
||||
- if !strings.HasPrefix(dest, rootfs) {
|
||||
- dest = filepath.Join(rootfs, dest)
|
||||
+ dest, err := securejoin.SecureJoin(rootfs, m.Destination)
|
||||
+ if err != nil {
|
||||
+ return err
|
||||
}
|
||||
|
||||
switch m.Device {
|
||||
@@ -364,53 +412,21 @@ func mountToRootfs(m *configs.Mount, c *mountConfig) error {
|
||||
}
|
||||
return label.SetFileLabel(dest, mountLabel)
|
||||
case "tmpfs":
|
||||
- copyUp := m.Extensions&configs.EXT_COPYUP == configs.EXT_COPYUP
|
||||
- tmpDir := ""
|
||||
- // dest might be an absolute symlink, so it needs
|
||||
- // to be resolved under rootfs.
|
||||
- dest, err := securejoin.SecureJoin(rootfs, m.Destination)
|
||||
- if err != nil {
|
||||
- return err
|
||||
- }
|
||||
- m.Destination = dest
|
||||
stat, err := os.Stat(dest)
|
||||
if err != nil {
|
||||
if err := os.MkdirAll(dest, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
- if copyUp {
|
||||
- tmpdir, err := prepareTmp("/tmp")
|
||||
- if err != nil {
|
||||
- return newSystemErrorWithCause(err, "tmpcopyup: failed to setup tmpdir")
|
||||
- }
|
||||
- defer cleanupTmp(tmpdir)
|
||||
- tmpDir, err = ioutil.TempDir(tmpdir, "runctmpdir")
|
||||
- if err != nil {
|
||||
- return newSystemErrorWithCause(err, "tmpcopyup: failed to create tmpdir")
|
||||
- }
|
||||
- defer os.RemoveAll(tmpDir)
|
||||
- m.Destination = tmpDir
|
||||
+
|
||||
+ if m.Extensions&configs.EXT_COPYUP == configs.EXT_COPYUP {
|
||||
+ err = doTmpfsCopyUp(m, rootfs, mountLabel)
|
||||
+ } else {
|
||||
+ err = mountPropagate(m, rootfs, mountLabel)
|
||||
}
|
||||
- if err := mountPropagate(m, rootfs, mountLabel); err != nil {
|
||||
+ if err != nil {
|
||||
return err
|
||||
}
|
||||
- if copyUp {
|
||||
- if err := fileutils.CopyDirectory(dest, tmpDir); err != nil {
|
||||
- errMsg := fmt.Errorf("tmpcopyup: failed to copy %s to %s: %v", dest, tmpDir, err)
|
||||
- if err1 := unix.Unmount(tmpDir, unix.MNT_DETACH); err1 != nil {
|
||||
- return newSystemErrorWithCausef(err1, "tmpcopyup: %v: failed to unmount", errMsg)
|
||||
- }
|
||||
- return errMsg
|
||||
- }
|
||||
- if err := unix.Mount(tmpDir, dest, "", unix.MS_MOVE, ""); err != nil {
|
||||
- errMsg := fmt.Errorf("tmpcopyup: failed to move mount %s to %s: %v", tmpDir, dest, err)
|
||||
- if err1 := unix.Unmount(tmpDir, unix.MNT_DETACH); err1 != nil {
|
||||
- return newSystemErrorWithCausef(err1, "tmpcopyup: %v: failed to unmount", errMsg)
|
||||
- }
|
||||
- return errMsg
|
||||
- }
|
||||
- }
|
||||
if stat != nil {
|
||||
if err = os.Chmod(dest, stat.Mode()); err != nil {
|
||||
return err
|
||||
@@ -454,19 +470,9 @@ func mountToRootfs(m *configs.Mount, c *mountConfig) error {
|
||||
}
|
||||
return mountCgroupV1(m, c)
|
||||
default:
|
||||
- // ensure that the destination of the mount is resolved of symlinks at mount time because
|
||||
- // any previous mounts can invalidate the next mount's destination.
|
||||
- // this can happen when a user specifies mounts within other mounts to cause breakouts or other
|
||||
- // evil stuff to try to escape the container's rootfs.
|
||||
- var err error
|
||||
- if dest, err = securejoin.SecureJoin(rootfs, m.Destination); err != nil {
|
||||
- return err
|
||||
- }
|
||||
if err := checkProcMount(rootfs, dest, m.Source); err != nil {
|
||||
return err
|
||||
}
|
||||
- // update the mount with the correct dest after symlinks are resolved.
|
||||
- m.Destination = dest
|
||||
if err := os.MkdirAll(dest, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -649,7 +655,7 @@ func createDevices(config *configs.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
-func bindMountDeviceNode(dest string, node *devices.Device) error {
|
||||
+func bindMountDeviceNode(rootfs, dest string, node *devices.Device) error {
|
||||
f, err := os.Create(dest)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
@@ -657,7 +663,9 @@ func bindMountDeviceNode(dest string, node *devices.Device) error {
|
||||
if f != nil {
|
||||
f.Close()
|
||||
}
|
||||
- return unix.Mount(node.Path, dest, "bind", unix.MS_BIND, "")
|
||||
+ return utils.WithProcfd(rootfs, dest, func(procfd string) error {
|
||||
+ return unix.Mount(node.Path, procfd, "bind", unix.MS_BIND, "")
|
||||
+ })
|
||||
}
|
||||
|
||||
// Creates the device node in the rootfs of the container.
|
||||
@@ -666,18 +674,21 @@ func createDeviceNode(rootfs string, node *devices.Device, bind bool) error {
|
||||
// The node only exists for cgroup reasons, ignore it here.
|
||||
return nil
|
||||
}
|
||||
- dest := filepath.Join(rootfs, node.Path)
|
||||
+ dest, err := securejoin.SecureJoin(rootfs, node.Path)
|
||||
+ if err != nil {
|
||||
+ return err
|
||||
+ }
|
||||
if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if bind {
|
||||
- return bindMountDeviceNode(dest, node)
|
||||
+ return bindMountDeviceNode(rootfs, dest, node)
|
||||
}
|
||||
if err := mknodDevice(dest, node); err != nil {
|
||||
if os.IsExist(err) {
|
||||
return nil
|
||||
} else if os.IsPermission(err) {
|
||||
- return bindMountDeviceNode(dest, node)
|
||||
+ return bindMountDeviceNode(rootfs, dest, node)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -1013,61 +1024,47 @@ func writeSystemProperty(key, value string) error {
|
||||
}
|
||||
|
||||
func remount(m *configs.Mount, rootfs string) error {
|
||||
- var (
|
||||
- dest = m.Destination
|
||||
- )
|
||||
- if !strings.HasPrefix(dest, rootfs) {
|
||||
- dest = filepath.Join(rootfs, dest)
|
||||
- }
|
||||
- return unix.Mount(m.Source, dest, m.Device, uintptr(m.Flags|unix.MS_REMOUNT), "")
|
||||
+ return utils.WithProcfd(rootfs, m.Destination, func(procfd string) error {
|
||||
+ return unix.Mount(m.Source, procfd, m.Device, uintptr(m.Flags|unix.MS_REMOUNT), "")
|
||||
+ })
|
||||
}
|
||||
|
||||
// Do the mount operation followed by additional mounts required to take care
|
||||
-// of propagation flags.
|
||||
+// of propagation flags. This will always be scoped inside the container rootfs.
|
||||
func mountPropagate(m *configs.Mount, rootfs string, mountLabel string) error {
|
||||
var (
|
||||
- dest = m.Destination
|
||||
data = label.FormatMountLabel(m.Data, mountLabel)
|
||||
flags = m.Flags
|
||||
)
|
||||
- if libcontainerUtils.CleanPath(dest) == "/dev" {
|
||||
- flags &= ^unix.MS_RDONLY
|
||||
- }
|
||||
-
|
||||
- // Mount it rw to allow chmod operation. A remount will be performed
|
||||
- // later to make it ro if set.
|
||||
- if m.Device == "tmpfs" {
|
||||
+ // Delay mounting the filesystem read-only if we need to do further
|
||||
+ // operations on it. We need to set up files in "/dev" and tmpfs mounts may
|
||||
+ // need to be chmod-ed after mounting. The mount will be remounted ro later
|
||||
+ // in finalizeRootfs() if necessary.
|
||||
+ if libcontainerUtils.CleanPath(m.Destination) == "/dev" || m.Device == "tmpfs" {
|
||||
flags &= ^unix.MS_RDONLY
|
||||
}
|
||||
|
||||
- copyUp := m.Extensions&configs.EXT_COPYUP == configs.EXT_COPYUP
|
||||
- if !(copyUp || strings.HasPrefix(dest, rootfs)) {
|
||||
- dest = filepath.Join(rootfs, dest)
|
||||
- }
|
||||
-
|
||||
- if err := unix.Mount(m.Source, dest, m.Device, uintptr(flags), data); err != nil {
|
||||
- return err
|
||||
- }
|
||||
-
|
||||
- for _, pflag := range m.PropagationFlags {
|
||||
- if err := unix.Mount("", dest, "", uintptr(pflag), ""); err != nil {
|
||||
- return err
|
||||
+ // Because the destination is inside a container path which might be
|
||||
+ // mutating underneath us, we verify that we are actually going to mount
|
||||
+ // inside the container with WithProcfd() -- mounting through a procfd
|
||||
+ // mounts on the target.
|
||||
+ if err := utils.WithProcfd(rootfs, m.Destination, func(procfd string) error {
|
||||
+ return unix.Mount(m.Source, procfd, m.Device, uintptr(flags), data)
|
||||
+ }); err != nil {
|
||||
+ return fmt.Errorf("mount through procfd: %w", err)
|
||||
+ }
|
||||
+ // We have to apply mount propagation flags in a separate WithProcfd() call
|
||||
+ // because the previous call invalidates the passed procfd -- the mount
|
||||
+ // target needs to be re-opened.
|
||||
+ if err := utils.WithProcfd(rootfs, m.Destination, func(procfd string) error {
|
||||
+ for _, pflag := range m.PropagationFlags {
|
||||
+ if err := unix.Mount("", procfd, "", uintptr(pflag), ""); err != nil {
|
||||
+ return err
|
||||
+ }
|
||||
}
|
||||
- }
|
||||
- return nil
|
||||
-}
|
||||
-
|
||||
-func mountNewCgroup(m *configs.Mount) error {
|
||||
- var (
|
||||
- data = m.Data
|
||||
- source = m.Source
|
||||
- )
|
||||
- if data == "systemd" {
|
||||
- data = cgroups.CgroupNamePrefix + data
|
||||
- source = "systemd"
|
||||
- }
|
||||
- if err := unix.Mount(source, m.Destination, m.Device, uintptr(m.Flags), data); err != nil {
|
||||
- return err
|
||||
+ return nil
|
||||
+ }); err != nil {
|
||||
+ return fmt.Errorf("change mount propagation through procfd: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
diff --git a/libcontainer/utils/utils.go b/libcontainer/utils/utils.go
|
||||
index 1b72b7a1c1ba..cd78f23e1bd0 100644
|
||||
--- a/libcontainer/utils/utils.go
|
||||
+++ b/libcontainer/utils/utils.go
|
||||
@@ -3,12 +3,15 @@ package utils
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
+ "fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
+ "strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
+ "github.com/cyphar/filepath-securejoin"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
@@ -88,6 +91,57 @@ func CleanPath(path string) string {
|
||||
return filepath.Clean(path)
|
||||
}
|
||||
|
||||
+// stripRoot returns the passed path, stripping the root path if it was
|
||||
+// (lexicially) inside it. Note that both passed paths will always be treated
|
||||
+// as absolute, and the returned path will also always be absolute. In
|
||||
+// addition, the paths are cleaned before stripping the root.
|
||||
+func stripRoot(root, path string) string {
|
||||
+ // Make the paths clean and absolute.
|
||||
+ root, path = CleanPath("/"+root), CleanPath("/"+path)
|
||||
+ switch {
|
||||
+ case path == root:
|
||||
+ path = "/"
|
||||
+ case root == "/":
|
||||
+ // do nothing
|
||||
+ case strings.HasPrefix(path, root+"/"):
|
||||
+ path = strings.TrimPrefix(path, root+"/")
|
||||
+ }
|
||||
+ return CleanPath("/" + path)
|
||||
+}
|
||||
+
|
||||
+// WithProcfd runs the passed closure with a procfd path (/proc/self/fd/...)
|
||||
+// corresponding to the unsafePath resolved within the root. Before passing the
|
||||
+// fd, this path is verified to have been inside the root -- so operating on it
|
||||
+// through the passed fdpath should be safe. Do not access this path through
|
||||
+// the original path strings, and do not attempt to use the pathname outside of
|
||||
+// the passed closure (the file handle will be freed once the closure returns).
|
||||
+func WithProcfd(root, unsafePath string, fn func(procfd string) error) error {
|
||||
+ // Remove the root then forcefully resolve inside the root.
|
||||
+ unsafePath = stripRoot(root, unsafePath)
|
||||
+ path, err := securejoin.SecureJoin(root, unsafePath)
|
||||
+ if err != nil {
|
||||
+ return fmt.Errorf("resolving path inside rootfs failed: %v", err)
|
||||
+ }
|
||||
+
|
||||
+ // Open the target path.
|
||||
+ fh, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC, 0)
|
||||
+ if err != nil {
|
||||
+ return fmt.Errorf("open o_path procfd: %w", err)
|
||||
+ }
|
||||
+ defer fh.Close()
|
||||
+
|
||||
+ // Double-check the path is the one we expected.
|
||||
+ procfd := "/proc/self/fd/" + strconv.Itoa(int(fh.Fd()))
|
||||
+ if realpath, err := os.Readlink(procfd); err != nil {
|
||||
+ return fmt.Errorf("procfd verification failed: %w", err)
|
||||
+ } else if realpath != path {
|
||||
+ return fmt.Errorf("possibly malicious path detected -- refusing to operate on %s", realpath)
|
||||
+ }
|
||||
+
|
||||
+ // Run the closure.
|
||||
+ return fn(procfd)
|
||||
+}
|
||||
+
|
||||
// SearchLabels searches a list of key-value pairs for the provided key and
|
||||
// returns the corresponding value. The pairs must be separated with '='.
|
||||
func SearchLabels(labels []string, query string) string {
|
||||
diff --git a/libcontainer/utils/utils_test.go b/libcontainer/utils/utils_test.go
|
||||
index 7f38ed169a6b..d33662238d36 100644
|
||||
--- a/libcontainer/utils/utils_test.go
|
||||
+++ b/libcontainer/utils/utils_test.go
|
||||
@@ -143,3 +143,38 @@ func TestCleanPath(t *testing.T) {
|
||||
t.Errorf("expected to receive '/foo' and received %s", path)
|
||||
}
|
||||
}
|
||||
+
|
||||
+func TestStripRoot(t *testing.T) {
|
||||
+ for _, test := range []struct {
|
||||
+ root, path, out string
|
||||
+ }{
|
||||
+ // Works with multiple components.
|
||||
+ {"/a/b", "/a/b/c", "/c"},
|
||||
+ {"/hello/world", "/hello/world/the/quick-brown/fox", "/the/quick-brown/fox"},
|
||||
+ // '/' must be a no-op.
|
||||
+ {"/", "/a/b/c", "/a/b/c"},
|
||||
+ // Must be the correct order.
|
||||
+ {"/a/b", "/a/c/b", "/a/c/b"},
|
||||
+ // Must be at start.
|
||||
+ {"/abc/def", "/foo/abc/def/bar", "/foo/abc/def/bar"},
|
||||
+ // Must be a lexical parent.
|
||||
+ {"/foo/bar", "/foo/barSAMECOMPONENT", "/foo/barSAMECOMPONENT"},
|
||||
+ // Must only strip the root once.
|
||||
+ {"/foo/bar", "/foo/bar/foo/bar/baz", "/foo/bar/baz"},
|
||||
+ // Deal with .. in a fairly sane way.
|
||||
+ {"/foo/bar", "/foo/bar/../baz", "/foo/baz"},
|
||||
+ {"/foo/bar", "../../../../../../foo/bar/baz", "/baz"},
|
||||
+ {"/foo/bar", "/../../../../../../foo/bar/baz", "/baz"},
|
||||
+ {"/foo/bar/../baz", "/foo/baz/bar", "/bar"},
|
||||
+ {"/foo/bar/../baz", "/foo/baz/../bar/../baz/./foo", "/foo"},
|
||||
+ // All paths are made absolute before stripping.
|
||||
+ {"foo/bar", "/foo/bar/baz/bee", "/baz/bee"},
|
||||
+ {"/foo/bar", "foo/bar/baz/beef", "/baz/beef"},
|
||||
+ {"foo/bar", "foo/bar/baz/beets", "/baz/beets"},
|
||||
+ } {
|
||||
+ got := stripRoot(test.root, test.path)
|
||||
+ if got != test.out {
|
||||
+ t.Errorf("stripRoot(%q, %q) -- got %q, expected %q", test.root, test.path, got, test.out)
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
--
|
||||
2.31.1
|
||||
|
@ -1,102 +0,0 @@
|
||||
From a2050ea471b0eb3c7240282219773c0f1d7ec554 Mon Sep 17 00:00:00 2001
|
||||
From: Kir Kolyshkin <kolyshkin@gmail.com>
|
||||
Date: Wed, 7 Apr 2021 16:45:39 -0700
|
||||
Subject: [PATCH 1/2] runc run: fix start for rootless + host pidns
|
||||
|
||||
Currently, runc fails like this when used from rootless podman
|
||||
with host PID namespace:
|
||||
|
||||
> $ podman --runtime=runc run --pid=host --rm -it busybox sh
|
||||
> WARN[0000] additional gid=10 is not present in the user namespace, skip setting it
|
||||
> Error: container_linux.go:380: starting container process caused:
|
||||
> process_linux.go:545: container init caused: readonly path /proc/asound:
|
||||
> operation not permitted: OCI permission denied
|
||||
|
||||
(Here /proc/asound is the first path from OCI spec's readonlyPaths).
|
||||
|
||||
The code uses MS_BIND|MS_REMOUNT flags that have a special meaning in
|
||||
the kernel ("keep the flags like nodev, nosuid, noexec as is").
|
||||
For some reason, this "special meaning" trick is not working for the
|
||||
above use case (rootless podman + no PID namespace), and I don't know
|
||||
how to reproduce this without podman.
|
||||
|
||||
Instead of relying on the kernel feature, let's just get the current
|
||||
mount flags using fstatfs(2) and add those that needs to be preserved.
|
||||
|
||||
While at it, wrap errors from unix.Mount into os.PathError to make
|
||||
errors a bit less cryptic.
|
||||
|
||||
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
|
||||
---
|
||||
libcontainer/rootfs_linux.go | 15 +++++++++++++--
|
||||
1 file changed, 13 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go
|
||||
index ed38e77219..3d67287926 100644
|
||||
--- a/libcontainer/rootfs_linux.go
|
||||
+++ b/libcontainer/rootfs_linux.go
|
||||
@@ -931,9 +931,20 @@ func readonlyPath(path string) error {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
- return err
|
||||
+ return &os.PathError{Op: "bind-mount", Path: path, Err: err}
|
||||
+ }
|
||||
+
|
||||
+ var s unix.Statfs_t
|
||||
+ if err := unix.Statfs(path, &s); err != nil {
|
||||
+ return &os.PathError{Op: "statfs", Path: path, Err: err}
|
||||
}
|
||||
- return unix.Mount(path, path, "", unix.MS_BIND|unix.MS_REMOUNT|unix.MS_RDONLY|unix.MS_REC, "")
|
||||
+ flags := uintptr(s.Flags) & (unix.MS_NOSUID | unix.MS_NODEV | unix.MS_NOEXEC)
|
||||
+
|
||||
+ if err := unix.Mount(path, path, "", flags|unix.MS_BIND|unix.MS_REMOUNT|unix.MS_RDONLY, ""); err != nil {
|
||||
+ return &os.PathError{Op: "bind-mount-ro", Path: path, Err: err}
|
||||
+ }
|
||||
+
|
||||
+ return nil
|
||||
}
|
||||
|
||||
// remountReadonly will remount an existing mount point and ensure that it is read-only.
|
||||
|
||||
From 31dd1e499b2f590cd3bcf59153491967ea2a8e1f Mon Sep 17 00:00:00 2001
|
||||
From: Kir Kolyshkin <kolyshkin@gmail.com>
|
||||
Date: Wed, 14 Apr 2021 10:58:42 -0700
|
||||
Subject: [PATCH 2/2] tests/int: add rootless + host pidns test case
|
||||
|
||||
For the fix, see previous commit. Without the fix, this test case fails:
|
||||
|
||||
> container_linux.go:380: starting container process caused:
|
||||
> process_linux.go:545: container init caused: readonly path /proc/bus:
|
||||
> operation not permitted
|
||||
|
||||
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
|
||||
---
|
||||
tests/integration/start_hello.bats | 17 +++++++++++++++++
|
||||
1 file changed, 17 insertions(+)
|
||||
|
||||
diff --git a/tests/integration/start_hello.bats b/tests/integration/start_hello.bats
|
||||
index 5c2a66fdb1..858847032c 100644
|
||||
--- a/tests/integration/start_hello.bats
|
||||
+++ b/tests/integration/start_hello.bats
|
||||
@@ -59,3 +59,20 @@ function teardown() {
|
||||
|
||||
[[ "$(cat pid.txt)" =~ [0-9]+ ]]
|
||||
}
|
||||
+
|
||||
+# https://github.com/opencontainers/runc/pull/2897
|
||||
+@test "runc run [rootless with host pidns]" {
|
||||
+ requires rootless_no_features
|
||||
+
|
||||
+ # Remove pid namespace, and replace /proc mount
|
||||
+ # with a bind mount from the host.
|
||||
+ update_config ' .linux.namespaces -= [{"type": "pid"}]
|
||||
+ | .mounts |= map((select(.type == "proc")
|
||||
+ | .type = "none"
|
||||
+ | .source = "/proc"
|
||||
+ | .options = ["rbind", "nosuid", "nodev", "noexec"]
|
||||
+ ) // .)'
|
||||
+
|
||||
+ runc run test_hello
|
||||
+ [ "$status" -eq 0 ]
|
||||
+}
|
@ -19,11 +19,11 @@ go build -buildmode pie -compiler gc -tags="rpm_crashtraceback libtrust_openssl
|
||||
# https://github.com/opencontainers/runc
|
||||
%global import_path %{provider}.%{provider_tld}/%{project}/%{repo}
|
||||
%global git0 https://%{import_path}
|
||||
%global release_candidate rc93
|
||||
%global release_candidate rc95
|
||||
|
||||
Name: %{repo}
|
||||
Version: 1.0.0
|
||||
Release: 73.%{release_candidate}%{?dist}
|
||||
Release: 74.%{release_candidate}%{?dist}
|
||||
Summary: CLI for running Open Containers
|
||||
# https://fedoraproject.org/wiki/PackagingDrafts/Go#Go_Language_Architectures
|
||||
#ExclusiveArch: %%{go_arches}
|
||||
@ -33,14 +33,6 @@ ExcludeArch: %{ix86}
|
||||
License: ASL 2.0
|
||||
URL: %{git0}
|
||||
Source0: %{git0}/archive/v1.0.0-%{release_candidate}.tar.gz
|
||||
Patch1: rc93-0001-libct-newInitConfig-nit.patch
|
||||
Patch2: rc93-0002-libct-rootfs-introduce-and-use-mountConfig.patch
|
||||
Patch3: rc93-0003-libct-rootfs-mountCgroupV2-minor-refactor.patch
|
||||
Patch4: rc93-0004-Fix-cgroup2-mount-for-rootless-case.patch
|
||||
Patch5: rc93-0005-rootfs-add-mount-destination-validation.patch
|
||||
# related bug: https://bugzilla.redhat.com/show_bug.cgi?id=1947432
|
||||
# patch: https://github.com/opencontainers/runc/pull/2897.patch
|
||||
Patch6: runc-1947432.patch
|
||||
Provides: oci-runtime = 1
|
||||
BuildRequires: golang >= 1.12.12-4
|
||||
BuildRequires: git
|
||||
@ -65,6 +57,7 @@ pushd GOPATH
|
||||
popd
|
||||
|
||||
pushd GOPATH/src/%{import_path}
|
||||
export GO111MODULE=off
|
||||
export GOPATH=%{gopath}:$(pwd)/GOPATH
|
||||
export CGO_CFLAGS="%{optflags} -D_GNU_SOURCE -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64"
|
||||
export BUILDTAGS="selinux seccomp"
|
||||
@ -98,6 +91,10 @@ install -p -m 0644 contrib/completions/bash/%{name} %{buildroot}%{_datadir}/bash
|
||||
%{_datadir}/bash-completion/completions/%{name}
|
||||
|
||||
%changelog
|
||||
* Thu Jul 01 2021 Jindrich Novy <jnovy@redhat.com> - 1.0.0-74.rc95
|
||||
- updated to rc95 to fix CVE-2021-30465
|
||||
- Related: #1954702
|
||||
|
||||
* Thu May 13 2021 Jindrich Novy <jnovy@redhat.com> - 1.0.0-73.rc93
|
||||
- fix "podman run --pid=host command causes OCI permission error"
|
||||
- Related: #1954702
|
||||
|
Loading…
Reference in New Issue
Block a user