mirror of
https://github.com/podman-container-tools/podman.git
synced 2026-09-11 10:07:53 +00:00
libpod: fix :O overlay volumes in a userns
When the user owning the storage is not mapped into the container user namespace (e.g. root with --userns=auto), the runtime cannot mount an overlay volume from inside the user namespace. Mount the overlay in podman instead and pass the runtime a bind mount. Closes: https://github.com/podman-container-tools/podman/issues/28758 Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
This commit is contained in:
parent
c30add3f1c
commit
58489b82b4
2 changed files with 78 additions and 11 deletions
|
|
@ -298,6 +298,10 @@ func (c *Container) generateSpec(ctx context.Context) (s *spec.Spec, cleanupFunc
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
// If the storage owner is not mapped into the user namespace, the runtime
|
||||
// cannot mount overlay volumes from inside it.
|
||||
forceOverlayMount := !hasCurrentUserMapped(c)
|
||||
|
||||
// Add named volumes
|
||||
for _, namedVol := range c.config.NamedVolumes {
|
||||
volume, err := c.runtime.GetVolume(namedVol.Name)
|
||||
|
|
@ -340,12 +344,27 @@ func (c *Container) generateSpec(ctx context.Context) (s *spec.Spec, cleanupFunc
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
if forceOverlayMount {
|
||||
if upperDir == "" {
|
||||
upperDir = filepath.Join(contentDir, "upper")
|
||||
}
|
||||
if workDir == "" {
|
||||
workDir = filepath.Join(contentDir, "work")
|
||||
}
|
||||
}
|
||||
|
||||
overlayOpts = &overlay.Options{
|
||||
RootUID: c.RootUID(),
|
||||
RootGID: c.RootGID(),
|
||||
UpperDirOptionFragment: upperDir,
|
||||
WorkDirOptionFragment: workDir,
|
||||
GraphOpts: c.runtime.store.GraphOptions(),
|
||||
ForceMount: forceOverlayMount,
|
||||
}
|
||||
if forceOverlayMount {
|
||||
// podman mounts the overlay itself, so it must apply the
|
||||
// container mount label; otherwise the runtime would.
|
||||
overlayOpts.MountLabel = c.MountLabel()
|
||||
}
|
||||
|
||||
overlayMount, err = overlay.MountWithOptions(contentDir, mountPoint, namedVol.Dest, overlayOpts)
|
||||
|
|
@ -394,7 +413,7 @@ func (c *Container) generateSpec(ctx context.Context) (s *spec.Spec, cleanupFunc
|
|||
m.Source = safeMount.mountPoint
|
||||
continue
|
||||
}
|
||||
if o == "idmap" || strings.HasPrefix(o, "idmap=") {
|
||||
if isIdmapOption(o) {
|
||||
var err error
|
||||
m.UIDMappings, m.GIDMappings, err = parseIDMapMountOption(c.config.IDMappings, o)
|
||||
if err != nil {
|
||||
|
|
@ -474,12 +493,22 @@ func (c *Container) generateSpec(ctx context.Context) (s *spec.Spec, cleanupFunc
|
|||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if forceOverlayMount && upperDir == "" && workDir == "" {
|
||||
upperDir = filepath.Join(contentDir, "upper")
|
||||
workDir = filepath.Join(contentDir, "work")
|
||||
}
|
||||
overlayOpts := &overlay.Options{
|
||||
RootUID: c.RootUID(),
|
||||
RootGID: c.RootGID(),
|
||||
UpperDirOptionFragment: upperDir,
|
||||
WorkDirOptionFragment: workDir,
|
||||
GraphOpts: c.runtime.store.GraphOptions(),
|
||||
ForceMount: forceOverlayMount,
|
||||
}
|
||||
if forceOverlayMount {
|
||||
// podman mounts the overlay itself, so it must apply the
|
||||
// container mount label; otherwise the runtime would.
|
||||
overlayOpts.MountLabel = c.MountLabel()
|
||||
}
|
||||
|
||||
overlayMount, err := overlay.MountWithOptions(contentDir, overlayVol.Source, overlayVol.Dest, overlayOpts)
|
||||
|
|
@ -533,11 +562,23 @@ func (c *Container) generateSpec(ctx context.Context) (s *spec.Spec, cleanupFunc
|
|||
}
|
||||
|
||||
var overlayMount spec.Mount
|
||||
if volume.ReadWrite {
|
||||
overlayMount, err = overlay.Mount(contentDir, imagePath, volume.Dest, c.RootUID(), c.RootGID(), c.runtime.store.GraphOptions())
|
||||
} else {
|
||||
overlayMount, err = overlay.MountReadOnly(contentDir, imagePath, volume.Dest, c.RootUID(), c.RootGID(), c.runtime.store.GraphOptions())
|
||||
overlayOpts := &overlay.Options{
|
||||
RootUID: c.RootUID(),
|
||||
RootGID: c.RootGID(),
|
||||
GraphOpts: c.runtime.store.GraphOptions(),
|
||||
ReadOnly: !volume.ReadWrite,
|
||||
ForceMount: forceOverlayMount,
|
||||
}
|
||||
if forceOverlayMount {
|
||||
// podman mounts the overlay itself, so it must apply the
|
||||
// container mount label; otherwise the runtime would.
|
||||
overlayOpts.MountLabel = c.MountLabel()
|
||||
}
|
||||
if forceOverlayMount && volume.ReadWrite {
|
||||
overlayOpts.UpperDirOptionFragment = filepath.Join(contentDir, "upper")
|
||||
overlayOpts.WorkDirOptionFragment = filepath.Join(contentDir, "work")
|
||||
}
|
||||
overlayMount, err = overlay.MountWithOptions(contentDir, imagePath, volume.Dest, overlayOpts)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("creating overlay mount for image %q failed: %w", volume.Source, err)
|
||||
}
|
||||
|
|
@ -3004,13 +3045,14 @@ func (c *Container) createSecretMountDir(runPath string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// isIdmapOption reports whether a single volume option requests idmapping
|
||||
// (the "idmap" or "idmap=..." option).
|
||||
func isIdmapOption(o string) bool {
|
||||
return o == "idmap" || strings.HasPrefix(o, "idmap=")
|
||||
}
|
||||
|
||||
func hasIdmapOption(options []string) bool {
|
||||
for _, o := range options {
|
||||
if o == "idmap" || strings.HasPrefix(o, "idmap=") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.ContainsFunc(options, isIdmapOption)
|
||||
}
|
||||
|
||||
// Fix ownership and permissions of the specified volume if necessary.
|
||||
|
|
|
|||
|
|
@ -204,3 +204,28 @@ EOF
|
|||
|
||||
run_podman rm -f $cname
|
||||
}
|
||||
|
||||
# CANNOT BE PARALLELIZED: userns=auto, rootless, => not enough unused IDs in user namespace
|
||||
@test "podman :O overlay volume in a userns that does not map the storage owner" {
|
||||
# An overlay volume (:O) must be read-write inside a userns that does not
|
||||
# map the storage owner. See containers/crun#2212.
|
||||
skip_if_rootless "storage is not owned by an unmapped user when rootless"
|
||||
grep -E -q "^containers:" /etc/subuid || skip "no IDs allocated for user 'containers'"
|
||||
|
||||
local volname="myvol_$(random_string)"
|
||||
run_podman volume create $volname
|
||||
|
||||
run_podman run --rm -v $volname:/vol $IMAGE sh -c "echo lower > /vol/lower.txt"
|
||||
|
||||
# The lower content must be readable and writes (to the upper dir) must work.
|
||||
run_podman run --rm --userns=auto -v $volname:/mnt:O $IMAGE sh -c \
|
||||
"cat /mnt/lower.txt; echo upper > /mnt/new.txt && cat /mnt/new.txt"
|
||||
assert "$output" =~ "lower" "overlay lower is readable under userns"
|
||||
assert "$output" =~ "upper" "overlay upper is writable under userns"
|
||||
|
||||
# Writes are ephemeral: the named volume itself is untouched.
|
||||
run_podman run --rm -v $volname:/vol $IMAGE ls /vol
|
||||
assert "$output" = "lower.txt" "writes to a :O overlay do not leak into the volume"
|
||||
|
||||
run_podman volume rm $volname
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue