You've already forked runc
mirror of
https://github.com/opencontainers/runc.git
synced 2026-10-23 19:49:01 +03:00
Merge pull request #5535 from kolyshkin/kir/no-pivot-host-mntns
libct/cfg/validate: disallow some bad configurations
This commit is contained in:
10
CHANGELOG.md
10
CHANGELOG.md
@@ -12,6 +12,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
they are always empty since the removal of veth network strategy in runc
|
||||
1.0.0-rc6. (#5523)
|
||||
|
||||
### Changed ###
|
||||
- A container configuration which uses `--no-pivot` or sets
|
||||
`linux.rootfsPropagation`, but does not have its own mount namespace, is now
|
||||
refused. Previously, `--no-pivot` resulted in host's `/proc` and `/sys` being
|
||||
unmounted, and `rootfsPropagation` changed the propagation of host mounts.
|
||||
(#5535)
|
||||
- Using `/` as container rootfs is now refused. Previously, it failed with a
|
||||
mount namespace, and without one, container mounts (such as `/proc`) were
|
||||
mounted on top of the host ones. (#5535)
|
||||
|
||||
## [1.6.0-rc.1] - 2026-10-06
|
||||
|
||||
> Lo bueno, si breve, dos veces bueno.
|
||||
|
||||
@@ -21,19 +21,20 @@ type check func(config *configs.Config) error
|
||||
func Validate(config *configs.Config) error {
|
||||
checks := []check{
|
||||
cgroupsCheck,
|
||||
rootfs,
|
||||
network,
|
||||
netdevices,
|
||||
uts,
|
||||
security,
|
||||
namespaces,
|
||||
sysctl,
|
||||
intelrdtCheck,
|
||||
rootlessEUIDCheck,
|
||||
mountsStrict,
|
||||
scheduler,
|
||||
ioPriority,
|
||||
memoryPolicy,
|
||||
mountns,
|
||||
mountsStrict,
|
||||
namespaces,
|
||||
netdevices,
|
||||
network,
|
||||
rootfs,
|
||||
rootlessEUIDCheck,
|
||||
scheduler,
|
||||
selinuxCheck,
|
||||
sysctl,
|
||||
uts,
|
||||
}
|
||||
for _, c := range checks {
|
||||
if err := c(config); err != nil {
|
||||
@@ -68,6 +69,13 @@ func rootfs(config *configs.Config) error {
|
||||
if filepath.Clean(config.Rootfs) != cleaned {
|
||||
return errors.New("invalid rootfs: not an absolute path, or a symlink")
|
||||
}
|
||||
// Host's / can't be used as the container rootfs. With a private mount
|
||||
// namespace, pivot_root (or MS_MOVE with no-pivot) fails. Without it,
|
||||
// container mounts (such as proc, dev etc.) are mounted on top of the
|
||||
// host ones.
|
||||
if cleaned == "/" {
|
||||
return errors.New("invalid rootfs: / is not allowed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -127,26 +135,41 @@ func uts(config *configs.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func security(config *configs.Config) error {
|
||||
// restrict sys without mount namespace
|
||||
if (len(config.MaskPaths) > 0 || len(config.ReadonlyPaths) > 0) &&
|
||||
!config.Namespaces.Contains(configs.NEWNS) {
|
||||
// mountns checks the configuration which requires a private mount namespace
|
||||
// (since otherwise it would affect the host).
|
||||
func mountns(config *configs.Config) error {
|
||||
if config.Namespaces.Contains(configs.NEWNS) {
|
||||
return nil
|
||||
}
|
||||
// Restricting sys entries is done by mounting over them.
|
||||
if len(config.MaskPaths) > 0 || len(config.ReadonlyPaths) > 0 {
|
||||
return errors.New("unable to restrict sys entries without a private MNT namespace")
|
||||
}
|
||||
// A read-only rootfs is implemented by remounting / read-only, which can
|
||||
// only be done in a private mount namespace (otherwise it would affect
|
||||
// the host).
|
||||
if config.Readonlyfs && !config.Namespaces.Contains(configs.NEWNS) {
|
||||
// A read-only rootfs is implemented by remounting / read-only.
|
||||
if config.Readonlyfs {
|
||||
return errors.New("unable to make rootfs read-only without a private MNT namespace")
|
||||
}
|
||||
// Same for mounts which can only be made read-only by a remount.
|
||||
if !config.Namespaces.Contains(configs.NEWNS) {
|
||||
for _, m := range config.Mounts {
|
||||
if m.IsReadonlyDeferred() {
|
||||
return fmt.Errorf("unable to make %s read-only without a private MNT namespace", m.Destination)
|
||||
}
|
||||
for _, m := range config.Mounts {
|
||||
if m.IsReadonlyDeferred() {
|
||||
return fmt.Errorf("unable to make %s read-only without a private MNT namespace", m.Destination)
|
||||
}
|
||||
}
|
||||
// Without pivot_root, the container rootfs is moved on top of /
|
||||
// (and host's procfs and sysfs mounts are unmounted).
|
||||
if config.NoPivotRoot {
|
||||
return errors.New("unable to use no-pivot without a private MNT namespace")
|
||||
}
|
||||
// Rootfs propagation is applied to / and to the parent mount of
|
||||
// the container rootfs.
|
||||
if config.RootPropagation != 0 {
|
||||
return errors.New("unable to set rootfs propagation without a private MNT namespace")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func selinuxCheck(config *configs.Config) error {
|
||||
if config.ProcessLabel != "" && !selinux.GetEnabled() {
|
||||
return errors.New("selinux label is specified in config, but selinux is disabled or not supported")
|
||||
}
|
||||
|
||||
@@ -538,7 +538,7 @@ func TestLinuxCgroupsPathNotSpecified(t *testing.T) {
|
||||
|
||||
func TestSpecconvExampleValidate(t *testing.T) {
|
||||
spec := Example()
|
||||
spec.Root.Path = "/"
|
||||
spec.Root.Path = t.TempDir()
|
||||
|
||||
opts := &CreateOpts{
|
||||
CgroupName: "ContainerID",
|
||||
@@ -563,7 +563,7 @@ func TestSpecconvExampleValidate(t *testing.T) {
|
||||
|
||||
func TestSpecconvNoLinuxSection(t *testing.T) {
|
||||
spec := Example()
|
||||
spec.Root.Path = "/"
|
||||
spec.Root.Path = t.TempDir()
|
||||
spec.Root.Readonly = false // Requires a mount namespace.
|
||||
spec.Linux = nil
|
||||
spec.Hostname = ""
|
||||
@@ -650,7 +650,7 @@ func TestNonZeroEUIDCompatibleSpecconvValidate(t *testing.T) {
|
||||
}
|
||||
|
||||
spec := Example()
|
||||
spec.Root.Path = "/"
|
||||
spec.Root.Path = t.TempDir()
|
||||
ToRootless(spec)
|
||||
|
||||
opts := &CreateOpts{
|
||||
|
||||
@@ -19,3 +19,14 @@ function teardown() {
|
||||
run -0 runc run test_shared_rootfs
|
||||
assert_output "shared"
|
||||
}
|
||||
|
||||
@test "runc run [rootfsPropagation, host mount ns] must fail" {
|
||||
update_config ' .linux.rootfsPropagation = "rslave"
|
||||
| .linux.namespaces -= [{"type": "mount"}]
|
||||
| .linux.maskedPaths = []
|
||||
| .linux.readonlyPaths = []
|
||||
| .root.readonly = false'
|
||||
|
||||
run -1 runc run test_host_mntns
|
||||
assert_output --partial "unable to set rootfs propagation without a private MNT namespace"
|
||||
}
|
||||
|
||||
@@ -20,3 +20,17 @@ function teardown() {
|
||||
run -1 runc run --no-pivot test_no_pivot
|
||||
assert_output --partial "mount: permission denied"
|
||||
}
|
||||
|
||||
@test "runc run --no-pivot [host mount ns] must fail" {
|
||||
# Unsafe because, if the bug is present, host's /proc and /sys
|
||||
# are unmounted, breaking the host.
|
||||
requires root unsafe
|
||||
|
||||
update_config ' .linux.namespaces -= [{"type": "mount"}]
|
||||
| .linux.maskedPaths = []
|
||||
| .linux.readonlyPaths = []
|
||||
| .root.readonly = false'
|
||||
|
||||
run -1 runc run --no-pivot test_no_pivot
|
||||
assert_output --partial "unable to use no-pivot without a private MNT namespace"
|
||||
}
|
||||
|
||||
@@ -211,3 +211,10 @@ EOF
|
||||
run -0 runc run test_busybox
|
||||
assert_line --index 0 "/home/tempuser"
|
||||
}
|
||||
|
||||
@test "runc run [rootfs is /] must fail" {
|
||||
update_config '.root.path = "/"'
|
||||
|
||||
run -1 runc run test_hello
|
||||
assert_output --partial "invalid rootfs: / is not allowed"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user