mirror of
https://github.com/opencontainers/runc.git
synced 2026-07-11 06:03:57 +08:00
Merge pull request #5282 from lifubang/backport-5275-1.3
[1.3] libct: reuse tmpfs for directory masks
This commit is contained in:
@@ -400,7 +400,7 @@ func mountCgroupV2(m mountEntry, c *mountConfig) error {
|
|||||||
// Mask `/sys/fs/cgroup` to ensure it is read-only, even when `/sys` is mounted
|
// Mask `/sys/fs/cgroup` to ensure it is read-only, even when `/sys` is mounted
|
||||||
// with `rbind,ro` (`runc spec --rootless` produces `rbind,ro` for `/sys`).
|
// with `rbind,ro` (`runc spec --rootless` produces `rbind,ro` for `/sys`).
|
||||||
err = utils.WithProcfdFile(m.dstFile, func(procfd string) error {
|
err = utils.WithProcfdFile(m.dstFile, func(procfd string) error {
|
||||||
return maskPaths([]string{procfd}, c.label)
|
return maskDir(procfd, c.label)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@@ -1335,12 +1335,20 @@ func verifyDevNull(f *os.File) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// maskDir mounts a read-only tmpfs on top of the specified path.
|
||||||
|
func maskDir(path, mountLabel string) error {
|
||||||
|
return mount("tmpfs", path, "tmpfs", unix.MS_RDONLY, label.FormatMountLabel("nr_blocks=1,nr_inodes=1", mountLabel))
|
||||||
|
}
|
||||||
|
|
||||||
// maskPaths masks the top of the specified paths inside a container to avoid
|
// maskPaths masks the top of the specified paths inside a container to avoid
|
||||||
// security issues from processes reading information from non-namespace aware
|
// security issues from processes reading information from non-namespace aware
|
||||||
// mounts ( proc/kcore ).
|
// mounts ( proc/kcore ).
|
||||||
// For files, maskPath bind mounts /dev/null over the top of the specified path.
|
// For files, maskPath bind mounts /dev/null over the top of the specified path.
|
||||||
// For directories, maskPath mounts read-only tmpfs over the top of the specified path.
|
// For directories, maskPath mounts read-only tmpfs over the top of the specified path.
|
||||||
func maskPaths(paths []string, mountLabel string) error {
|
func maskPaths(rootFs string, paths []string, mountLabel string) error {
|
||||||
|
if len(paths) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
devNull, err := os.OpenFile("/dev/null", unix.O_PATH, 0)
|
devNull, err := os.OpenFile("/dev/null", unix.O_PATH, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't mask paths: %w", err)
|
return fmt.Errorf("can't mask paths: %w", err)
|
||||||
@@ -1353,6 +1361,16 @@ func maskPaths(paths []string, mountLabel string) error {
|
|||||||
procSelfFd, closer := utils.ProcThreadSelf("fd/")
|
procSelfFd, closer := utils.ProcThreadSelf("fd/")
|
||||||
defer closer()
|
defer closer()
|
||||||
|
|
||||||
|
var (
|
||||||
|
sharedMaskFile *os.File
|
||||||
|
sharedMaskSrc *mountSource
|
||||||
|
bindFailed bool
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
if sharedMaskFile != nil {
|
||||||
|
_ = sharedMaskFile.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
// Open the target path; skip if it doesn't exist.
|
// Open the target path; skip if it doesn't exist.
|
||||||
dstFh, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC, 0)
|
dstFh, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC, 0)
|
||||||
@@ -1371,7 +1389,31 @@ func maskPaths(paths []string, mountLabel string) error {
|
|||||||
if st.IsDir() {
|
if st.IsDir() {
|
||||||
// Destination is a directory: bind mount a ro tmpfs over it.
|
// Destination is a directory: bind mount a ro tmpfs over it.
|
||||||
dstType = "dir"
|
dstType = "dir"
|
||||||
err = mount("tmpfs", path, "tmpfs", unix.MS_RDONLY, label.FormatMountLabel("", mountLabel))
|
if !bindFailed && sharedMaskSrc != nil {
|
||||||
|
dstFd := filepath.Join(procSelfFd, strconv.Itoa(int(dstFh.Fd())))
|
||||||
|
err = mountViaFds("", sharedMaskSrc, path, dstFd, "", unix.MS_BIND, "")
|
||||||
|
if err != nil {
|
||||||
|
// A bind-mount inherits MNT_READONLY from the source vfsmount,
|
||||||
|
// but if it fails fall back to individual tmpfs mounts.
|
||||||
|
bindFailed = true
|
||||||
|
logrus.WithError(err).Warn("maskPaths: shared tmpfs bind-mount failed, falling back to per-directory tmpfs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if bindFailed || sharedMaskSrc == nil {
|
||||||
|
err = maskDir(path, mountLabel)
|
||||||
|
if err == nil && !bindFailed && sharedMaskSrc == nil {
|
||||||
|
// Establish this mount as the reusable shared source. reopenAfterMount
|
||||||
|
// resolves the underlying inode via procfs and re-opens it through
|
||||||
|
// rootFd, so the resulting fd is anchored to the real path inside the
|
||||||
|
// container rootfs even if path was a /proc/self/fd/N alias.
|
||||||
|
reopened, err := reopenAfterMount(rootFs, dstFh, unix.O_PATH|unix.O_CLOEXEC)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't reopen shared directory mask: %w", err)
|
||||||
|
}
|
||||||
|
sharedMaskFile = reopened
|
||||||
|
sharedMaskSrc = &mountSource{Type: mountSourcePlain, file: sharedMaskFile}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Destination is a file: mount it to /dev/null.
|
// Destination is a file: mount it to /dev/null.
|
||||||
dstType = "path"
|
dstType = "path"
|
||||||
|
|||||||
@@ -141,9 +141,10 @@ func (l *linuxStandardInit) Init() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := maskPaths(l.config.Config.MaskPaths, l.config.Config.MountLabel); err != nil {
|
if err := maskPaths("/", l.config.Config.MaskPaths, l.config.Config.MountLabel); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pdeath, err := system.GetParentDeathSignal()
|
pdeath, err := system.GetParentDeathSignal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't get pdeath signal: %w", err)
|
return fmt.Errorf("can't get pdeath signal: %w", err)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ function setup() {
|
|||||||
setup_busybox
|
setup_busybox
|
||||||
|
|
||||||
# Create fake rootfs.
|
# Create fake rootfs.
|
||||||
mkdir rootfs/testdir
|
mkdir rootfs/testdir rootfs/testdir2 rootfs/testdir3
|
||||||
echo "Forbidden information!" >rootfs/testfile
|
echo "Forbidden information!" >rootfs/testfile
|
||||||
|
|
||||||
# add extra masked paths
|
# add extra masked paths
|
||||||
@@ -75,3 +75,33 @@ function teardown() {
|
|||||||
# so we merely check that it fails, and do not check the exact error
|
# so we merely check that it fails, and do not check the exact error
|
||||||
# message like for /proc above.
|
# message like for /proc above.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "mask paths [directories share tmpfs]" {
|
||||||
|
update_config '(.. | select(.maskedPaths? != null)) .maskedPaths += ["/testdir2", "/testdir3"]'
|
||||||
|
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
runc exec test_busybox sh -euc '
|
||||||
|
set -- $(stat -c %d /testdir /testdir2 /testdir3)
|
||||||
|
[ "$1" = "$2" ]
|
||||||
|
[ "$2" = "$3" ]
|
||||||
|
'
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
|
||||||
|
runc exec test_busybox touch /testdir2/foo
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "${output}" == *"Read-only file system"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "mask paths [directory with read-only rootfs]" {
|
||||||
|
update_config '(.. | select(.maskedPaths? != null)) .maskedPaths += ["/testdir2", "/testdir3"]'
|
||||||
|
update_config '.root.readonly = true'
|
||||||
|
|
||||||
|
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
|
||||||
|
runc exec test_busybox ls /testdir
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[ -z "$output" ]
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user