Skip to content

Commit 817bccb

Browse files
authored
Merge pull request #47588 from vvoland/v25.0-47558
[25.0 backport] plugin: fix mounting /etc/hosts when running in UserNS
2 parents 2a0601e + 4be9723 commit 817bccb

File tree

3 files changed

+77
-34
lines changed

3 files changed

+77
-34
lines changed

daemon/oci_linux.go

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/docker/docker/container"
2020
dconfig "github.com/docker/docker/daemon/config"
2121
"github.com/docker/docker/errdefs"
22+
"github.com/docker/docker/internal/rootless/mountopts"
2223
"github.com/docker/docker/oci"
2324
"github.com/docker/docker/oci/caps"
2425
"github.com/docker/docker/pkg/idtools"
@@ -31,7 +32,6 @@ import (
3132
"github.com/opencontainers/runc/libcontainer/cgroups"
3233
specs "github.com/opencontainers/runtime-spec/specs-go"
3334
"github.com/pkg/errors"
34-
"golang.org/x/sys/unix"
3535
)
3636

3737
const inContainerInitPath = "/sbin/" + dconfig.DefaultInitBinary
@@ -468,38 +468,6 @@ func ensureSharedOrSlave(path string) error {
468468
return nil
469469
}
470470

471-
// Get the set of mount flags that are set on the mount that contains the given
472-
// path and are locked by CL_UNPRIVILEGED. This is necessary to ensure that
473-
// bind-mounting "with options" will not fail with user namespaces, due to
474-
// kernel restrictions that require user namespace mounts to preserve
475-
// CL_UNPRIVILEGED locked flags.
476-
func getUnprivilegedMountFlags(path string) ([]string, error) {
477-
var statfs unix.Statfs_t
478-
if err := unix.Statfs(path, &statfs); err != nil {
479-
return nil, err
480-
}
481-
482-
// The set of keys come from https://github.com/torvalds/linux/blob/v4.13/fs/namespace.c#L1034-L1048.
483-
unprivilegedFlags := map[uint64]string{
484-
unix.MS_RDONLY: "ro",
485-
unix.MS_NODEV: "nodev",
486-
unix.MS_NOEXEC: "noexec",
487-
unix.MS_NOSUID: "nosuid",
488-
unix.MS_NOATIME: "noatime",
489-
unix.MS_RELATIME: "relatime",
490-
unix.MS_NODIRATIME: "nodiratime",
491-
}
492-
493-
var flags []string
494-
for mask, flag := range unprivilegedFlags {
495-
if uint64(statfs.Flags)&mask == mask {
496-
flags = append(flags, flag)
497-
}
498-
}
499-
500-
return flags, nil
501-
}
502-
503471
var (
504472
mountPropagationMap = map[string]int{
505473
"private": mount.PRIVATE,
@@ -723,7 +691,7 @@ func withMounts(daemon *Daemon, daemonCfg *configStore, c *container.Container)
723691
// when runc sets up the root filesystem, it is already inside a user
724692
// namespace, and thus cannot change any flags that are locked.
725693
if daemonCfg.RemappedRoot != "" || userns.RunningInUserNS() {
726-
unprivOpts, err := getUnprivilegedMountFlags(m.Source)
694+
unprivOpts, err := mountopts.UnprivilegedMountFlags(m.Source)
727695
if err != nil {
728696
return err
729697
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package mountopts
2+
3+
import (
4+
"golang.org/x/sys/unix"
5+
)
6+
7+
// UnprivilegedMountFlags gets the set of mount flags that are set on the mount that contains the given
8+
// path and are locked by CL_UNPRIVILEGED. This is necessary to ensure that
9+
// bind-mounting "with options" will not fail with user namespaces, due to
10+
// kernel restrictions that require user namespace mounts to preserve
11+
// CL_UNPRIVILEGED locked flags.
12+
//
13+
// TODO: Move to github.com/moby/sys/mount, and update BuildKit copy of this code as well (https://github.com/moby/buildkit/blob/v0.13.0/util/rootless/mountopts/mountopts_linux.go#L11-L18)
14+
func UnprivilegedMountFlags(path string) ([]string, error) {
15+
var statfs unix.Statfs_t
16+
if err := unix.Statfs(path, &statfs); err != nil {
17+
return nil, err
18+
}
19+
20+
// The set of keys come from https://github.com/torvalds/linux/blob/v4.13/fs/namespace.c#L1034-L1048.
21+
unprivilegedFlags := map[uint64]string{
22+
unix.MS_RDONLY: "ro",
23+
unix.MS_NODEV: "nodev",
24+
unix.MS_NOEXEC: "noexec",
25+
unix.MS_NOSUID: "nosuid",
26+
unix.MS_NOATIME: "noatime",
27+
unix.MS_RELATIME: "relatime",
28+
unix.MS_NODIRATIME: "nodiratime",
29+
}
30+
31+
var flags []string
32+
for mask, flag := range unprivilegedFlags {
33+
if uint64(statfs.Flags)&mask == mask {
34+
flags = append(flags, flag)
35+
}
36+
}
37+
38+
return flags, nil
39+
}

plugin/v2/plugin_linux.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
2+
//go:build go1.19
3+
14
package v2 // import "github.com/docker/docker/plugin/v2"
25

36
import (
@@ -6,7 +9,10 @@ import (
69
"runtime"
710
"strings"
811

12+
"github.com/containerd/containerd/pkg/userns"
913
"github.com/docker/docker/api/types"
14+
"github.com/docker/docker/internal/rootless/mountopts"
15+
"github.com/docker/docker/internal/sliceutil"
1016
"github.com/docker/docker/oci"
1117
specs "github.com/opencontainers/runtime-spec/specs-go"
1218
"github.com/pkg/errors"
@@ -136,5 +142,35 @@ func (p *Plugin) InitSpec(execRoot string) (*specs.Spec, error) {
136142
p.modifyRuntimeSpec(&s)
137143
}
138144

145+
// Rootless mode requires modifying the mount flags
146+
// https://github.com/moby/moby/issues/47248#issuecomment-1927776700
147+
// https://github.com/moby/moby/pull/47558
148+
if userns.RunningInUserNS() {
149+
for i := range s.Mounts {
150+
m := &s.Mounts[i]
151+
for _, o := range m.Options {
152+
switch o {
153+
case "bind", "rbind":
154+
if _, err := os.Lstat(m.Source); err != nil {
155+
if errors.Is(err, os.ErrNotExist) {
156+
continue
157+
}
158+
return nil, err
159+
}
160+
// UnprivilegedMountFlags gets the set of mount flags that are set on the mount that contains the given
161+
// path and are locked by CL_UNPRIVILEGED. This is necessary to ensure that
162+
// bind-mounting "with options" will not fail with user namespaces, due to
163+
// kernel restrictions that require user namespace mounts to preserve
164+
// CL_UNPRIVILEGED locked flags.
165+
unpriv, err := mountopts.UnprivilegedMountFlags(m.Source)
166+
if err != nil {
167+
return nil, errors.Wrapf(err, "failed to get unprivileged mount flags for %+v", m)
168+
}
169+
m.Options = sliceutil.Dedup(append(m.Options, unpriv...))
170+
}
171+
}
172+
}
173+
}
174+
139175
return &s, nil
140176
}

0 commit comments

Comments
 (0)