mirror of https://github.com/containers/podman.git
Merge pull request #17992 from giuseppe/safe-mount-subpath
libpod: mount safely subpaths
This commit is contained in:
commit
8a92f9d626
|
|
@ -1035,10 +1035,11 @@ func (c *Container) init(ctx context.Context, retainRetries bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate the OCI newSpec
|
// Generate the OCI newSpec
|
||||||
newSpec, err := c.generateSpec(ctx)
|
newSpec, cleanupFunc, err := c.generateSpec(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer cleanupFunc()
|
||||||
|
|
||||||
// Make sure the workdir exists while initializing container
|
// Make sure the workdir exists while initializing container
|
||||||
if err := c.resolveWorkDir(); err != nil {
|
if err := c.resolveWorkDir(); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"os/user"
|
"os/user"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
@ -170,7 +171,21 @@ func getOverlayUpperAndWorkDir(options []string) (string, string, error) {
|
||||||
|
|
||||||
// Generate spec for a container
|
// Generate spec for a container
|
||||||
// Accepts a map of the container's dependencies
|
// Accepts a map of the container's dependencies
|
||||||
func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
func (c *Container) generateSpec(ctx context.Context) (s *spec.Spec, cleanupFuncRet func(), err error) {
|
||||||
|
var safeMounts []*safeMountInfo
|
||||||
|
// lock the thread so that the current thread will be kept alive until the mounts are used
|
||||||
|
runtime.LockOSThread()
|
||||||
|
cleanupFunc := func() {
|
||||||
|
runtime.UnlockOSThread()
|
||||||
|
for _, s := range safeMounts {
|
||||||
|
s.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
cleanupFunc()
|
||||||
|
}
|
||||||
|
}()
|
||||||
overrides := c.getUserOverrides()
|
overrides := c.getUserOverrides()
|
||||||
execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, c.config.User, overrides)
|
execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, c.config.User, overrides)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -178,7 +193,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
execUser, err = lookupHostUser(c.config.User)
|
execUser, err = lookupHostUser(c.config.User)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -194,51 +209,57 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
systemdMode = *c.config.Systemd
|
systemdMode = *c.config.Systemd
|
||||||
}
|
}
|
||||||
if err := util.AddPrivilegedDevices(&g, systemdMode); err != nil {
|
if err := util.AddPrivilegedDevices(&g, systemdMode); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If network namespace was requested, add it now
|
// If network namespace was requested, add it now
|
||||||
if err := c.addNetworkNamespace(&g); err != nil {
|
if err := c.addNetworkNamespace(&g); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply AppArmor checks and load the default profile if needed.
|
// Apply AppArmor checks and load the default profile if needed.
|
||||||
if len(c.config.Spec.Process.ApparmorProfile) > 0 {
|
if len(c.config.Spec.Process.ApparmorProfile) > 0 {
|
||||||
updatedProfile, err := apparmor.CheckProfileAndLoadDefault(c.config.Spec.Process.ApparmorProfile)
|
updatedProfile, err := apparmor.CheckProfileAndLoadDefault(c.config.Spec.Process.ApparmorProfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
g.SetProcessApparmorProfile(updatedProfile)
|
g.SetProcessApparmorProfile(updatedProfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.makeBindMounts(); err != nil {
|
if err := c.makeBindMounts(); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.mountNotifySocket(g); err != nil {
|
if err := c.mountNotifySocket(g); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get host UID and GID based on the container process UID and GID.
|
// Get host UID and GID based on the container process UID and GID.
|
||||||
hostUID, hostGID, err := butil.GetHostIDs(util.IDtoolsToRuntimeSpec(c.config.IDMappings.UIDMap), util.IDtoolsToRuntimeSpec(c.config.IDMappings.GIDMap), uint32(execUser.Uid), uint32(execUser.Gid))
|
hostUID, hostGID, err := butil.GetHostIDs(util.IDtoolsToRuntimeSpec(c.config.IDMappings.UIDMap), util.IDtoolsToRuntimeSpec(c.config.IDMappings.GIDMap), uint32(execUser.Uid), uint32(execUser.Gid))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add named volumes
|
// Add named volumes
|
||||||
for _, namedVol := range c.config.NamedVolumes {
|
for _, namedVol := range c.config.NamedVolumes {
|
||||||
volume, err := c.runtime.GetVolume(namedVol.Name)
|
volume, err := c.runtime.GetVolume(namedVol.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("retrieving volume %s to add to container %s: %w", namedVol.Name, c.ID(), err)
|
return nil, nil, fmt.Errorf("retrieving volume %s to add to container %s: %w", namedVol.Name, c.ID(), err)
|
||||||
}
|
}
|
||||||
mountPoint, err := volume.MountPoint()
|
mountPoint, err := volume.MountPoint()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(namedVol.SubPath) > 0 {
|
if len(namedVol.SubPath) > 0 {
|
||||||
mountPoint = filepath.Join(mountPoint, namedVol.SubPath)
|
safeMount, err := c.safeMountSubPath(mountPoint, namedVol.SubPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
safeMounts = append(safeMounts, safeMount)
|
||||||
|
|
||||||
|
mountPoint = safeMount.mountPoint
|
||||||
}
|
}
|
||||||
|
|
||||||
overlayFlag := false
|
overlayFlag := false
|
||||||
|
|
@ -249,7 +270,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
overlayFlag = true
|
overlayFlag = true
|
||||||
upperDir, workDir, err = getOverlayUpperAndWorkDir(namedVol.Options)
|
upperDir, workDir, err = getOverlayUpperAndWorkDir(namedVol.Options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -259,7 +280,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
var overlayOpts *overlay.Options
|
var overlayOpts *overlay.Options
|
||||||
contentDir, err := overlay.TempDir(c.config.StaticDir, c.RootUID(), c.RootGID())
|
contentDir, err := overlay.TempDir(c.config.StaticDir, c.RootUID(), c.RootGID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
overlayOpts = &overlay.Options{RootUID: c.RootUID(),
|
overlayOpts = &overlay.Options{RootUID: c.RootUID(),
|
||||||
|
|
@ -271,17 +292,17 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
|
|
||||||
overlayMount, err = overlay.MountWithOptions(contentDir, mountPoint, namedVol.Dest, overlayOpts)
|
overlayMount, err = overlay.MountWithOptions(contentDir, mountPoint, namedVol.Dest, overlayOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("mounting overlay failed %q: %w", mountPoint, err)
|
return nil, nil, fmt.Errorf("mounting overlay failed %q: %w", mountPoint, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, o := range namedVol.Options {
|
for _, o := range namedVol.Options {
|
||||||
if o == "U" {
|
if o == "U" {
|
||||||
if err := c.ChangeHostPathOwnership(mountPoint, true, int(hostUID), int(hostGID)); err != nil {
|
if err := c.ChangeHostPathOwnership(mountPoint, true, int(hostUID), int(hostGID)); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ChangeHostPathOwnership(contentDir, true, int(hostUID), int(hostGID)); err != nil {
|
if err := c.ChangeHostPathOwnership(contentDir, true, int(hostUID), int(hostGID)); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -305,11 +326,21 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
m := &g.Config.Mounts[i]
|
m := &g.Config.Mounts[i]
|
||||||
var options []string
|
var options []string
|
||||||
for _, o := range m.Options {
|
for _, o := range m.Options {
|
||||||
|
if strings.HasPrefix(o, "subpath=") {
|
||||||
|
subpath := strings.Split(o, "=")[1]
|
||||||
|
safeMount, err := c.safeMountSubPath(m.Source, subpath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
safeMounts = append(safeMounts, safeMount)
|
||||||
|
m.Source = safeMount.mountPoint
|
||||||
|
continue
|
||||||
|
}
|
||||||
if o == "idmap" || strings.HasPrefix(o, "idmap=") {
|
if o == "idmap" || strings.HasPrefix(o, "idmap=") {
|
||||||
var err error
|
var err error
|
||||||
m.UIDMappings, m.GIDMappings, err = parseIDMapMountOption(c.config.IDMappings, o)
|
m.UIDMappings, m.GIDMappings, err = parseIDMapMountOption(c.config.IDMappings, o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -320,14 +351,14 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
} else {
|
} else {
|
||||||
// only chown on initial creation of container
|
// only chown on initial creation of container
|
||||||
if err := c.ChangeHostPathOwnership(m.Source, true, int(hostUID), int(hostGID)); err != nil {
|
if err := c.ChangeHostPathOwnership(m.Source, true, int(hostUID), int(hostGID)); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "z":
|
case "z":
|
||||||
fallthrough
|
fallthrough
|
||||||
case "Z":
|
case "Z":
|
||||||
if err := c.relabel(m.Source, c.MountLabel(), label.IsShared(o)); err != nil {
|
if err := c.relabel(m.Source, c.MountLabel(), label.IsShared(o)); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -365,11 +396,11 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
for _, overlayVol := range c.config.OverlayVolumes {
|
for _, overlayVol := range c.config.OverlayVolumes {
|
||||||
upperDir, workDir, err := getOverlayUpperAndWorkDir(overlayVol.Options)
|
upperDir, workDir, err := getOverlayUpperAndWorkDir(overlayVol.Options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
contentDir, err := overlay.TempDir(c.config.StaticDir, c.RootUID(), c.RootGID())
|
contentDir, err := overlay.TempDir(c.config.StaticDir, c.RootUID(), c.RootGID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
overlayOpts := &overlay.Options{RootUID: c.RootUID(),
|
overlayOpts := &overlay.Options{RootUID: c.RootUID(),
|
||||||
RootGID: c.RootGID(),
|
RootGID: c.RootGID(),
|
||||||
|
|
@ -380,18 +411,18 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
|
|
||||||
overlayMount, err := overlay.MountWithOptions(contentDir, overlayVol.Source, overlayVol.Dest, overlayOpts)
|
overlayMount, err := overlay.MountWithOptions(contentDir, overlayVol.Source, overlayVol.Dest, overlayOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("mounting overlay failed %q: %w", overlayVol.Source, err)
|
return nil, nil, fmt.Errorf("mounting overlay failed %q: %w", overlayVol.Source, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check overlay volume options
|
// Check overlay volume options
|
||||||
for _, o := range overlayVol.Options {
|
for _, o := range overlayVol.Options {
|
||||||
if o == "U" {
|
if o == "U" {
|
||||||
if err := c.ChangeHostPathOwnership(overlayVol.Source, true, int(hostUID), int(hostGID)); err != nil {
|
if err := c.ChangeHostPathOwnership(overlayVol.Source, true, int(hostUID), int(hostGID)); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ChangeHostPathOwnership(contentDir, true, int(hostUID), int(hostGID)); err != nil {
|
if err := c.ChangeHostPathOwnership(contentDir, true, int(hostUID), int(hostGID)); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -404,16 +435,16 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
// Mount the specified image.
|
// Mount the specified image.
|
||||||
img, _, err := c.runtime.LibimageRuntime().LookupImage(volume.Source, nil)
|
img, _, err := c.runtime.LibimageRuntime().LookupImage(volume.Source, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("creating image volume %q:%q: %w", volume.Source, volume.Dest, err)
|
return nil, nil, fmt.Errorf("creating image volume %q:%q: %w", volume.Source, volume.Dest, err)
|
||||||
}
|
}
|
||||||
mountPoint, err := img.Mount(ctx, nil, "")
|
mountPoint, err := img.Mount(ctx, nil, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("mounting image volume %q:%q: %w", volume.Source, volume.Dest, err)
|
return nil, nil, fmt.Errorf("mounting image volume %q:%q: %w", volume.Source, volume.Dest, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentDir, err := overlay.TempDir(c.config.StaticDir, c.RootUID(), c.RootGID())
|
contentDir, err := overlay.TempDir(c.config.StaticDir, c.RootUID(), c.RootGID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create TempDir in the %s directory: %w", c.config.StaticDir, err)
|
return nil, nil, fmt.Errorf("failed to create TempDir in the %s directory: %w", c.config.StaticDir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var overlayMount spec.Mount
|
var overlayMount spec.Mount
|
||||||
|
|
@ -423,7 +454,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
overlayMount, err = overlay.MountReadOnly(contentDir, mountPoint, volume.Dest, c.RootUID(), c.RootGID(), c.runtime.store.GraphOptions())
|
overlayMount, err = overlay.MountReadOnly(contentDir, mountPoint, volume.Dest, c.RootUID(), c.RootGID(), c.runtime.store.GraphOptions())
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("creating overlay mount for image %q failed: %w", volume.Source, err)
|
return nil, nil, fmt.Errorf("creating overlay mount for image %q failed: %w", volume.Source, err)
|
||||||
}
|
}
|
||||||
g.AddMount(overlayMount)
|
g.AddMount(overlayMount)
|
||||||
}
|
}
|
||||||
|
|
@ -449,7 +480,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
if c.config.Umask != "" {
|
if c.config.Umask != "" {
|
||||||
decVal, err := strconv.ParseUint(c.config.Umask, 8, 32)
|
decVal, err := strconv.ParseUint(c.config.Umask, 8, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid Umask Value: %w", err)
|
return nil, nil, fmt.Errorf("invalid Umask Value: %w", err)
|
||||||
}
|
}
|
||||||
umask := uint32(decVal)
|
umask := uint32(decVal)
|
||||||
g.Config.Process.User.Umask = &umask
|
g.Config.Process.User.Umask = &umask
|
||||||
|
|
@ -459,7 +490,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
if len(c.config.Groups) > 0 {
|
if len(c.config.Groups) > 0 {
|
||||||
gids, err := lookup.GetContainerGroups(c.config.Groups, c.state.Mountpoint, overrides)
|
gids, err := lookup.GetContainerGroups(c.config.Groups, c.state.Mountpoint, overrides)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("looking up supplemental groups for container %s: %w", c.ID(), err)
|
return nil, nil, fmt.Errorf("looking up supplemental groups for container %s: %w", c.ID(), err)
|
||||||
}
|
}
|
||||||
for _, gid := range gids {
|
for _, gid := range gids {
|
||||||
g.AddProcessAdditionalGid(gid)
|
g.AddProcessAdditionalGid(gid)
|
||||||
|
|
@ -467,7 +498,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.addSystemdMounts(&g); err != nil {
|
if err := c.addSystemdMounts(&g); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up and add groups the user belongs to, if a group wasn't directly specified
|
// Look up and add groups the user belongs to, if a group wasn't directly specified
|
||||||
|
|
@ -482,7 +513,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
// Check whether the current user namespace has enough gids available.
|
// Check whether the current user namespace has enough gids available.
|
||||||
availableGids, err := rootless.GetAvailableGids()
|
availableGids, err := rootless.GetAvailableGids()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot read number of available GIDs: %w", err)
|
return nil, nil, fmt.Errorf("cannot read number of available GIDs: %w", err)
|
||||||
}
|
}
|
||||||
gidMappings = []idtools.IDMap{{
|
gidMappings = []idtools.IDMap{{
|
||||||
ContainerID: 0,
|
ContainerID: 0,
|
||||||
|
|
@ -514,7 +545,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
|
|
||||||
// Add shared namespaces from other containers
|
// Add shared namespaces from other containers
|
||||||
if err := c.addSharedNamespaces(&g); err != nil {
|
if err := c.addSharedNamespaces(&g); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
g.SetRootPath(c.state.Mountpoint)
|
g.SetRootPath(c.state.Mountpoint)
|
||||||
|
|
@ -525,7 +556,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.setCgroupsPath(&g); err != nil {
|
if err := c.setCgroupsPath(&g); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning: CDI may alter g.Config in place.
|
// Warning: CDI may alter g.Config in place.
|
||||||
|
|
@ -538,7 +569,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
}
|
}
|
||||||
_, err := registry.InjectDevices(g.Config, c.config.CDIDevices...)
|
_, err := registry.InjectDevices(g.Config, c.config.CDIDevices...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("setting up CDI devices: %w", err)
|
return nil, nil, fmt.Errorf("setting up CDI devices: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -554,7 +585,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
if m.Type == "tmpfs" {
|
if m.Type == "tmpfs" {
|
||||||
finalPath, err := securejoin.SecureJoin(c.state.Mountpoint, m.Destination)
|
finalPath, err := securejoin.SecureJoin(c.state.Mountpoint, m.Destination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("resolving symlinks for mount destination %s: %w", m.Destination, err)
|
return nil, nil, fmt.Errorf("resolving symlinks for mount destination %s: %w", m.Destination, err)
|
||||||
}
|
}
|
||||||
trimmedPath := strings.TrimPrefix(finalPath, strings.TrimSuffix(c.state.Mountpoint, "/"))
|
trimmedPath := strings.TrimPrefix(finalPath, strings.TrimSuffix(c.state.Mountpoint, "/"))
|
||||||
m.Destination = trimmedPath
|
m.Destination = trimmedPath
|
||||||
|
|
@ -563,25 +594,25 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.addRootPropagation(&g, mounts); err != nil {
|
if err := c.addRootPropagation(&g, mounts); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning: precreate hooks may alter g.Config in place.
|
// Warning: precreate hooks may alter g.Config in place.
|
||||||
if c.state.ExtensionStageHooks, err = c.setupOCIHooks(ctx, g.Config); err != nil {
|
if c.state.ExtensionStageHooks, err = c.setupOCIHooks(ctx, g.Config); err != nil {
|
||||||
return nil, fmt.Errorf("setting up OCI Hooks: %w", err)
|
return nil, nil, fmt.Errorf("setting up OCI Hooks: %w", err)
|
||||||
}
|
}
|
||||||
if len(c.config.EnvSecrets) > 0 {
|
if len(c.config.EnvSecrets) > 0 {
|
||||||
manager, err := c.runtime.SecretsManager()
|
manager, err := c.runtime.SecretsManager()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
for name, secr := range c.config.EnvSecrets {
|
for name, secr := range c.config.EnvSecrets {
|
||||||
_, data, err := manager.LookupSecretData(secr.Name)
|
_, data, err := manager.LookupSecretData(secr.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
g.AddProcessEnv(name, string(data))
|
g.AddProcessEnv(name, string(data))
|
||||||
}
|
}
|
||||||
|
|
@ -599,7 +630,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return g.Config, nil
|
return g.Config, cleanupFunc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isWorkDirSymlink returns true if resolved workdir is symlink or a chain of symlinks,
|
// isWorkDirSymlink returns true if resolved workdir is symlink or a chain of symlinks,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ package libpod
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
@ -316,3 +317,22 @@ func (c *Container) jailName() string {
|
||||||
return c.ID()
|
return c.ID()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type safeMountInfo struct {
|
||||||
|
// mountPoint is the mount point.
|
||||||
|
mountPoint string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close releases the resources allocated with the safe mount info.
|
||||||
|
func (s *safeMountInfo) Close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// safeMountSubPath securely mounts a subpath inside a volume to a new temporary location.
|
||||||
|
// The function checks that the subpath is a valid subpath within the volume and that it
|
||||||
|
// does not escape the boundaries of the mount point (volume).
|
||||||
|
//
|
||||||
|
// The caller is responsible for closing the file descriptor and unmounting the subpath
|
||||||
|
// when it's no longer needed.
|
||||||
|
func (c *Container) safeMountSubPath(mountPoint, subpath string) (s *safeMountInfo, err error) {
|
||||||
|
return &safeMountInfo{mountPoint: filepath.Join(mountPoint, subpath)}, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ package libpod
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -696,3 +697,81 @@ func (c *Container) getConmonPidFd() int {
|
||||||
}
|
}
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type safeMountInfo struct {
|
||||||
|
// file is the open File.
|
||||||
|
file *os.File
|
||||||
|
|
||||||
|
// mountPoint is the mount point.
|
||||||
|
mountPoint string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close releases the resources allocated with the safe mount info.
|
||||||
|
func (s *safeMountInfo) Close() {
|
||||||
|
_ = unix.Unmount(s.mountPoint, unix.MNT_DETACH)
|
||||||
|
_ = s.file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// safeMountSubPath securely mounts a subpath inside a volume to a new temporary location.
|
||||||
|
// The function checks that the subpath is a valid subpath within the volume and that it
|
||||||
|
// does not escape the boundaries of the mount point (volume).
|
||||||
|
//
|
||||||
|
// The caller is responsible for closing the file descriptor and unmounting the subpath
|
||||||
|
// when it's no longer needed.
|
||||||
|
func (c *Container) safeMountSubPath(mountPoint, subpath string) (s *safeMountInfo, err error) {
|
||||||
|
joinedPath := filepath.Clean(filepath.Join(mountPoint, subpath))
|
||||||
|
fd, err := unix.Open(joinedPath, unix.O_RDONLY|unix.O_PATH, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f := os.NewFile(uintptr(fd), joinedPath)
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Once we got the file descriptor, we need to check that the subpath is a valid. We
|
||||||
|
// refer to the open FD so there won't be other path lookups (and no risk to follow a symlink).
|
||||||
|
fdPath := fmt.Sprintf("/proc/%d/fd/%d", os.Getpid(), f.Fd())
|
||||||
|
p, err := os.Readlink(fdPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
relPath, err := filepath.Rel(mountPoint, p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if relPath == ".." || strings.HasPrefix(relPath, "../") {
|
||||||
|
return nil, fmt.Errorf("subpath %q is outside of the volume %q", subpath, mountPoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := os.Stat(fdPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
npath := ""
|
||||||
|
switch {
|
||||||
|
case fi.Mode()&fs.ModeSymlink != 0:
|
||||||
|
return nil, fmt.Errorf("file %q is a symlink", joinedPath)
|
||||||
|
case fi.IsDir():
|
||||||
|
npath, err = os.MkdirTemp(c.state.RunDir, "subpath")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
tmp, err := os.CreateTemp(c.state.RunDir, "subpath")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tmp.Close()
|
||||||
|
npath = tmp.Name()
|
||||||
|
}
|
||||||
|
if err := unix.Mount(fdPath, npath, "", unix.MS_BIND|unix.MS_REC, ""); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &safeMountInfo{
|
||||||
|
file: f,
|
||||||
|
mountPoint: npath,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
@ -407,10 +406,6 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
|
||||||
}
|
}
|
||||||
|
|
||||||
volume.MountPath = dest
|
volume.MountPath = dest
|
||||||
path := volumeSource.Source
|
|
||||||
if len(volume.SubPath) > 0 {
|
|
||||||
path = filepath.Join(path, volume.SubPath)
|
|
||||||
}
|
|
||||||
switch volumeSource.Type {
|
switch volumeSource.Type {
|
||||||
case KubeVolumeTypeBindMount:
|
case KubeVolumeTypeBindMount:
|
||||||
// If the container has bind mounts, we need to check if
|
// If the container has bind mounts, we need to check if
|
||||||
|
|
@ -419,17 +414,20 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
|
||||||
// Make sure the z/Z option is not already there (from editing the YAML)
|
// Make sure the z/Z option is not already there (from editing the YAML)
|
||||||
if k == define.BindMountPrefix {
|
if k == define.BindMountPrefix {
|
||||||
lastIndex := strings.LastIndex(v, ":")
|
lastIndex := strings.LastIndex(v, ":")
|
||||||
if v[:lastIndex] == path && !cutil.StringInSlice("z", options) && !cutil.StringInSlice("Z", options) {
|
if v[:lastIndex] == volumeSource.Source && !cutil.StringInSlice("z", options) && !cutil.StringInSlice("Z", options) {
|
||||||
options = append(options, v[lastIndex+1:])
|
options = append(options, v[lastIndex+1:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mount := spec.Mount{
|
mount := spec.Mount{
|
||||||
Destination: volume.MountPath,
|
Destination: volume.MountPath,
|
||||||
Source: path,
|
Source: volumeSource.Source,
|
||||||
Type: "bind",
|
Type: "bind",
|
||||||
Options: options,
|
Options: options,
|
||||||
}
|
}
|
||||||
|
if len(volume.SubPath) > 0 {
|
||||||
|
mount.Options = append(mount.Options, fmt.Sprintf("subpath=%s", volume.SubPath))
|
||||||
|
}
|
||||||
s.Mounts = append(s.Mounts, mount)
|
s.Mounts = append(s.Mounts, mount)
|
||||||
case KubeVolumeTypeNamed:
|
case KubeVolumeTypeNamed:
|
||||||
namedVolume := specgen.NamedVolume{
|
namedVolume := specgen.NamedVolume{
|
||||||
|
|
@ -451,7 +449,7 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
|
||||||
// We are setting the path as hostPath:mountPath to comply with pkg/specgen/generate.DeviceFromPath.
|
// We are setting the path as hostPath:mountPath to comply with pkg/specgen/generate.DeviceFromPath.
|
||||||
// The type is here just to improve readability as it is not taken into account when the actual device is created.
|
// The type is here just to improve readability as it is not taken into account when the actual device is created.
|
||||||
device := spec.LinuxDevice{
|
device := spec.LinuxDevice{
|
||||||
Path: fmt.Sprintf("%s:%s", path, volume.MountPath),
|
Path: fmt.Sprintf("%s:%s", volumeSource.Source, volume.MountPath),
|
||||||
Type: "c",
|
Type: "c",
|
||||||
}
|
}
|
||||||
s.Devices = append(s.Devices, device)
|
s.Devices = append(s.Devices, device)
|
||||||
|
|
@ -459,7 +457,7 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
|
||||||
// We are setting the path as hostPath:mountPath to comply with pkg/specgen/generate.DeviceFromPath.
|
// We are setting the path as hostPath:mountPath to comply with pkg/specgen/generate.DeviceFromPath.
|
||||||
// The type is here just to improve readability as it is not taken into account when the actual device is created.
|
// The type is here just to improve readability as it is not taken into account when the actual device is created.
|
||||||
device := spec.LinuxDevice{
|
device := spec.LinuxDevice{
|
||||||
Path: fmt.Sprintf("%s:%s", path, volume.MountPath),
|
Path: fmt.Sprintf("%s:%s", volumeSource.Source, volume.MountPath),
|
||||||
Type: "b",
|
Type: "b",
|
||||||
}
|
}
|
||||||
s.Devices = append(s.Devices, device)
|
s.Devices = append(s.Devices, device)
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,10 @@ func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(splitOpt[0], "subpath") {
|
||||||
|
newOptions = append(newOptions, opt)
|
||||||
|
continue
|
||||||
|
}
|
||||||
if strings.HasPrefix(splitOpt[0], "idmap") {
|
if strings.HasPrefix(splitOpt[0], "idmap") {
|
||||||
if foundIdmap {
|
if foundIdmap {
|
||||||
return nil, fmt.Errorf("the 'idmap' option can only be set once: %w", ErrDupeMntOption)
|
return nil, fmt.Errorf("the 'idmap' option can only be set once: %w", ErrDupeMntOption)
|
||||||
|
|
|
||||||
|
|
@ -5015,6 +5015,58 @@ spec:
|
||||||
Expect(exec.OutputToString()).Should(ContainSubstring("123.txt"))
|
Expect(exec.OutputToString()).Should(ContainSubstring("123.txt"))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("podman play kube with unsafe subpaths", func() {
|
||||||
|
SkipIfRemote("volume export does not exist on remote")
|
||||||
|
volumeCreate := podmanTest.Podman([]string{"volume", "create", "testvol1"})
|
||||||
|
volumeCreate.WaitWithDefaultTimeout()
|
||||||
|
Expect(volumeCreate).Should(Exit(0))
|
||||||
|
|
||||||
|
session := podmanTest.Podman([]string{"run", "--volume", "testvol1:/data", ALPINE, "sh", "-c", "mkdir -p /data/testing && ln -s /etc /data/testing/onlythis"})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session).Should(Exit(0))
|
||||||
|
|
||||||
|
tar := filepath.Join(podmanTest.TempDir, "out.tar")
|
||||||
|
session = podmanTest.Podman([]string{"volume", "export", "--output", tar, "testvol1"})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session).Should(Exit(0))
|
||||||
|
|
||||||
|
volumeCreate = podmanTest.Podman([]string{"volume", "create", "testvol"})
|
||||||
|
volumeCreate.WaitWithDefaultTimeout()
|
||||||
|
Expect(volumeCreate).Should(Exit(0))
|
||||||
|
|
||||||
|
volumeImp := podmanTest.Podman([]string{"volume", "import", "testvol", filepath.Join(podmanTest.TempDir, "out.tar")})
|
||||||
|
volumeImp.WaitWithDefaultTimeout()
|
||||||
|
Expect(volumeImp).Should(Exit(0))
|
||||||
|
|
||||||
|
err = writeYaml(subpathTestNamedVolume, kubeYaml)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
playKube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
|
||||||
|
playKube.WaitWithDefaultTimeout()
|
||||||
|
Expect(playKube).Should(Exit(125))
|
||||||
|
Expect(playKube.OutputToString()).Should(ContainSubstring("is outside"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("podman play kube with unsafe hostPath subpaths", func() {
|
||||||
|
if !Containerized() {
|
||||||
|
Skip("something is wrong with file permissions in CI or in the yaml creation. cannot ls or cat the fs unless in a container")
|
||||||
|
}
|
||||||
|
hostPathLocation := podmanTest.TempDir
|
||||||
|
|
||||||
|
Expect(os.MkdirAll(filepath.Join(hostPathLocation, "testing"), 0755)).To(Succeed())
|
||||||
|
Expect(os.Symlink("/", filepath.Join(hostPathLocation, "testing", "symlink"))).To(Succeed())
|
||||||
|
|
||||||
|
pod := getPod(withPodName("testpod"), withCtr(getCtr(withImage(ALPINE), withName("testctr"), withCmd([]string{"top"}), withVolumeMount("/foo", "testing/symlink", false))), withVolume(getHostPathVolume("DirectoryOrCreate", hostPathLocation)))
|
||||||
|
|
||||||
|
err = generateKubeYaml("pod", pod, kubeYaml)
|
||||||
|
Expect(err).To(Not(HaveOccurred()))
|
||||||
|
|
||||||
|
playKube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
|
||||||
|
playKube.WaitWithDefaultTimeout()
|
||||||
|
Expect(playKube).Should(Exit(125))
|
||||||
|
Expect(playKube.OutputToString()).Should(ContainSubstring("is outside"))
|
||||||
|
})
|
||||||
|
|
||||||
It("podman play kube with configMap subpaths", func() {
|
It("podman play kube with configMap subpaths", func() {
|
||||||
volumeName := "cmVol"
|
volumeName := "cmVol"
|
||||||
cm := getConfigMap(withConfigMapName(volumeName), withConfigMapData("FOO", "foobar"))
|
cm := getConfigMap(withConfigMapName(volumeName), withConfigMapData("FOO", "foobar"))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue