mirror of
https://github.com/opencontainers/runc.git
synced 2025-09-27 03:46:19 +08:00
Add uid and gid mappings to mounts
Co-authored-by: Francis Laniel <flaniel@linux.microsoft.com> Signed-off-by: Rodrigo Campos <rodrigoca@microsoft.com>
This commit is contained in:
@@ -29,8 +29,24 @@ type Mount struct {
|
||||
|
||||
// Extensions are additional flags that are specific to runc.
|
||||
Extensions int `json:"extensions"`
|
||||
|
||||
// UIDMappings is used to changing file user owners w/o calling chown.
|
||||
// Note that, the underlying filesystem should support this feature to be
|
||||
// used.
|
||||
// Every mount point could have its own mapping.
|
||||
UIDMappings []IDMap `json:"uidMappings,omitempty"`
|
||||
|
||||
// GIDMappings is used to changing file group owners w/o calling chown.
|
||||
// Note that, the underlying filesystem should support this feature to be
|
||||
// used.
|
||||
// Every mount point could have its own mapping.
|
||||
GIDMappings []IDMap `json:"gidMappings,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Mount) IsBind() bool {
|
||||
return m.Flags&unix.MS_BIND != 0
|
||||
}
|
||||
|
||||
func (m *Mount) IsIDMapped() bool {
|
||||
return len(m.UIDMappings) > 0 || len(m.GIDMappings) > 0
|
||||
}
|
||||
|
@@ -253,16 +253,72 @@ func cgroupsCheck(config *configs.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkIDMapMounts(config *configs.Config, m *configs.Mount) error {
|
||||
if !m.IsIDMapped() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !m.IsBind() {
|
||||
return fmt.Errorf("gidMappings/uidMappings is supported only for mounts with the option 'bind'")
|
||||
}
|
||||
if config.RootlessEUID {
|
||||
return fmt.Errorf("gidMappings/uidMappings is not supported when runc is being launched with EUID != 0, needs CAP_SYS_ADMIN on the runc parent's user namespace")
|
||||
}
|
||||
if len(config.UidMappings) == 0 || len(config.GidMappings) == 0 {
|
||||
return fmt.Errorf("not yet supported to use gidMappings/uidMappings in a mount without also using a user namespace")
|
||||
}
|
||||
if !sameMapping(config.UidMappings, m.UIDMappings) {
|
||||
return fmt.Errorf("not yet supported for the mount uidMappings to be different than user namespace uidMapping")
|
||||
}
|
||||
if !sameMapping(config.GidMappings, m.GIDMappings) {
|
||||
return fmt.Errorf("not yet supported for the mount gidMappings to be different than user namespace gidMapping")
|
||||
}
|
||||
if !filepath.IsAbs(m.Source) {
|
||||
return fmt.Errorf("mount source not absolute")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mounts(config *configs.Config) error {
|
||||
for _, m := range config.Mounts {
|
||||
// We upgraded this to an error in runc 1.2. We might need to
|
||||
// revert this change if some users haven't still moved to use
|
||||
// abs paths, in that please move this check inside
|
||||
// checkIDMapMounts() as we do want to ensure that for idmap
|
||||
// mounts anyways.
|
||||
if !filepath.IsAbs(m.Destination) {
|
||||
return fmt.Errorf("invalid mount %+v: mount destination not absolute", m)
|
||||
}
|
||||
if err := checkIDMapMounts(config, m); err != nil {
|
||||
return fmt.Errorf("invalid mount %+v: %w", m, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sameMapping checks if the mappings are the same. If the mappings are the same
|
||||
// but in different order, it returns false.
|
||||
func sameMapping(a, b []configs.IDMap) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range a {
|
||||
if a[i].ContainerID != b[i].ContainerID {
|
||||
return false
|
||||
}
|
||||
if a[i].HostID != b[i].HostID {
|
||||
return false
|
||||
}
|
||||
if a[i].Size != b[i].Size {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isHostNetNS(path string) (bool, error) {
|
||||
const currentProcessNetns = "/proc/self/ns/net"
|
||||
|
||||
|
@@ -386,3 +386,199 @@ func TestValidateMounts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateIDMapMounts(t *testing.T) {
|
||||
mapping := []configs.IDMap{
|
||||
{
|
||||
ContainerID: 0,
|
||||
HostID: 10000,
|
||||
Size: 1,
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
isErr bool
|
||||
config *configs.Config
|
||||
}{
|
||||
{
|
||||
name: "idmap mount without bind opt specified",
|
||||
isErr: true,
|
||||
config: &configs.Config{
|
||||
UidMappings: mapping,
|
||||
GidMappings: mapping,
|
||||
Mounts: []*configs.Mount{
|
||||
{
|
||||
Source: "/abs/path/",
|
||||
Destination: "/abs/path/",
|
||||
UIDMappings: mapping,
|
||||
GIDMappings: mapping,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rootless idmap mount",
|
||||
isErr: true,
|
||||
config: &configs.Config{
|
||||
RootlessEUID: true,
|
||||
UidMappings: mapping,
|
||||
GidMappings: mapping,
|
||||
Mounts: []*configs.Mount{
|
||||
{
|
||||
Source: "/abs/path/",
|
||||
Destination: "/abs/path/",
|
||||
Flags: unix.MS_BIND,
|
||||
UIDMappings: mapping,
|
||||
GIDMappings: mapping,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "idmap mount without userns mappings",
|
||||
isErr: true,
|
||||
config: &configs.Config{
|
||||
Mounts: []*configs.Mount{
|
||||
{
|
||||
Source: "/abs/path/",
|
||||
Destination: "/abs/path/",
|
||||
Flags: unix.MS_BIND,
|
||||
UIDMappings: mapping,
|
||||
GIDMappings: mapping,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "idmap mounts with different userns and mount mappings",
|
||||
isErr: true,
|
||||
config: &configs.Config{
|
||||
UidMappings: mapping,
|
||||
GidMappings: mapping,
|
||||
Mounts: []*configs.Mount{
|
||||
{
|
||||
Source: "/abs/path/",
|
||||
Destination: "/abs/path/",
|
||||
Flags: unix.MS_BIND,
|
||||
UIDMappings: []configs.IDMap{
|
||||
{
|
||||
ContainerID: 10,
|
||||
HostID: 10,
|
||||
Size: 1,
|
||||
},
|
||||
},
|
||||
GIDMappings: mapping,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "idmap mounts with different userns and mount mappings",
|
||||
isErr: true,
|
||||
config: &configs.Config{
|
||||
UidMappings: mapping,
|
||||
GidMappings: mapping,
|
||||
Mounts: []*configs.Mount{
|
||||
{
|
||||
Source: "/abs/path/",
|
||||
Destination: "/abs/path/",
|
||||
Flags: unix.MS_BIND,
|
||||
UIDMappings: mapping,
|
||||
GIDMappings: []configs.IDMap{
|
||||
{
|
||||
ContainerID: 10,
|
||||
HostID: 10,
|
||||
Size: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "idmap mounts without abs source path",
|
||||
isErr: true,
|
||||
config: &configs.Config{
|
||||
UidMappings: mapping,
|
||||
GidMappings: mapping,
|
||||
Mounts: []*configs.Mount{
|
||||
{
|
||||
Source: "./rel/path/",
|
||||
Destination: "/abs/path/",
|
||||
Flags: unix.MS_BIND,
|
||||
UIDMappings: mapping,
|
||||
GIDMappings: mapping,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "idmap mounts without abs dest path",
|
||||
isErr: true,
|
||||
config: &configs.Config{
|
||||
UidMappings: mapping,
|
||||
GidMappings: mapping,
|
||||
Mounts: []*configs.Mount{
|
||||
{
|
||||
Source: "/abs/path/",
|
||||
Destination: "./rel/path/",
|
||||
Flags: unix.MS_BIND,
|
||||
UIDMappings: mapping,
|
||||
GIDMappings: mapping,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "simple idmap mount",
|
||||
config: &configs.Config{
|
||||
UidMappings: mapping,
|
||||
GidMappings: mapping,
|
||||
Mounts: []*configs.Mount{
|
||||
{
|
||||
Source: "/another-abs/path/",
|
||||
Destination: "/abs/path/",
|
||||
Flags: unix.MS_BIND,
|
||||
UIDMappings: mapping,
|
||||
GIDMappings: mapping,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "idmap mount with more flags",
|
||||
config: &configs.Config{
|
||||
UidMappings: mapping,
|
||||
GidMappings: mapping,
|
||||
Mounts: []*configs.Mount{
|
||||
{
|
||||
Source: "/another-abs/path/",
|
||||
Destination: "/abs/path/",
|
||||
Flags: unix.MS_BIND | unix.MS_RDONLY,
|
||||
UIDMappings: mapping,
|
||||
GIDMappings: mapping,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
config := tc.config
|
||||
config.Rootfs = "/var"
|
||||
|
||||
err := mounts(config)
|
||||
if tc.isErr && err == nil {
|
||||
t.Error("expected error, got nil")
|
||||
}
|
||||
|
||||
if !tc.isErr && err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -531,9 +531,9 @@ func (c *Container) shouldSendMountSources() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// We need to send sources if there are bind-mounts.
|
||||
// We need to send sources if there are non-idmap bind-mounts.
|
||||
for _, m := range c.config.Mounts {
|
||||
if m.IsBind() {
|
||||
if m.IsBind() && !m.IsIDMapped() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -2231,7 +2231,7 @@ func (c *Container) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Namespa
|
||||
if it == initStandard && c.shouldSendMountSources() {
|
||||
var mounts []byte
|
||||
for _, m := range c.config.Mounts {
|
||||
if m.IsBind() {
|
||||
if m.IsBind() && !m.IsIDMapped() {
|
||||
if strings.IndexByte(m.Source, 0) >= 0 {
|
||||
return nil, fmt.Errorf("mount source string contains null byte: %q", m.Source)
|
||||
}
|
||||
|
@@ -497,6 +497,18 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func toConfigIDMap(specMaps []specs.LinuxIDMapping) []configs.IDMap {
|
||||
idmaps := make([]configs.IDMap, len(specMaps))
|
||||
for i, id := range specMaps {
|
||||
idmaps[i] = configs.IDMap{
|
||||
ContainerID: int(id.ContainerID),
|
||||
HostID: int(id.HostID),
|
||||
Size: int(id.Size),
|
||||
}
|
||||
}
|
||||
return idmaps
|
||||
}
|
||||
|
||||
func createLibcontainerMount(cwd string, m specs.Mount) (*configs.Mount, error) {
|
||||
if !filepath.IsAbs(m.Destination) {
|
||||
// Relax validation for backward compatibility
|
||||
@@ -519,6 +531,9 @@ func createLibcontainerMount(cwd string, m specs.Mount) (*configs.Mount, error)
|
||||
}
|
||||
}
|
||||
|
||||
mnt.UIDMappings = toConfigIDMap(m.UIDMappings)
|
||||
mnt.GIDMappings = toConfigIDMap(m.GIDMappings)
|
||||
|
||||
// None of the mount arguments can contain a null byte. Normally such
|
||||
// strings would either cause some other failure or would just be truncated
|
||||
// when we hit the null byte, but because we serialise these strings as
|
||||
|
Reference in New Issue
Block a user