Disable rootless mode except RootlessCgMgr when executed as the root in userns

This PR decomposes `libcontainer/configs.Config.Rootless bool` into `RootlessEUID bool` and
`RootlessCgroups bool`, so as to make "runc-in-userns" to be more compatible with "rootful" runc.

`RootlessEUID` denotes that runc is being executed as a non-root user (euid != 0) in
the current user namespace. `RootlessEUID` is almost identical to the former `Rootless`
except cgroups stuff.

`RootlessCgroups` denotes that runc is unlikely to have the full access to cgroups.
`RootlessCgroups` is set to false if runc is executed as the root (euid == 0) in the initial namespace.
Otherwise `RootlessCgroups` is set to true.
(Hint: if `RootlessEUID` is true, `RootlessCgroups` becomes true as well)

When runc is executed as the root (euid == 0) in an user namespace (e.g. by Docker-in-LXD, Podman, Usernetes),
`RootlessEUID` is set to false but `RootlessCgroups` is set to true.
So, "runc-in-userns" behaves almost same as "rootful" runc except that cgroups errors are ignored.

This PR does not have any impact on CLI flags and `state.json`.

Note about CLI:
* Now `runc --rootless=(auto|true|false)` CLI flag is only used for setting `RootlessCgroups`.
* Now `runc spec --rootless` is only required when `RootlessEUID` is set to true.
  For runc-in-userns, `runc spec`  without `--rootless` should work, when sufficient numbers of
  UID/GID are mapped.

Note about `$XDG_RUNTIME_DIR` (e.g. `/run/user/1000`):
* `$XDG_RUNTIME_DIR` is ignored if runc is being executed as the root (euid == 0) in the initial namespace, for backward compatibility.
  (`/run/runc` is used)
* If runc is executed as the root (euid == 0) in an user namespace, `$XDG_RUNTIME_DIR` is honored if `$USER != "" && $USER != "root"`.
  This allows unprivileged users to allow execute runc as the root in userns, without mounting writable `/run/runc`.

Note about `state.json`:
* `rootless` is set to true when `RootlessEUID == true && RootlessCgroups == true`.

Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
This commit is contained in:
Akihiro Suda
2018-07-05 15:28:21 +09:00
parent 70ca035aa6
commit 06f789cf26
22 changed files with 231 additions and 198 deletions
+5 -6
View File
@@ -4,11 +4,14 @@ package main
import ( import (
"fmt" "fmt"
"os"
"strconv" "strconv"
"strings" "strings"
"github.com/opencontainers/runc/libcontainer" "github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/system"
"github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
@@ -44,12 +47,8 @@ checkpointed.`,
return err return err
} }
// XXX: Currently this is untested with rootless containers. // XXX: Currently this is untested with rootless containers.
rootless, err := isRootless(context) if os.Geteuid() != 0 || system.RunningInUserNS() {
if err != nil { logrus.Warn("runc checkpoint is untested with rootless containers")
return err
}
if rootless {
return fmt.Errorf("runc checkpoint requires root")
} }
container, err := getContainer(context) container, err := getContainer(context)
+8 -2
View File
@@ -65,7 +65,7 @@ type subsystem interface {
type Manager struct { type Manager struct {
mu sync.Mutex mu sync.Mutex
Cgroups *configs.Cgroup Cgroups *configs.Cgroup
Rootless bool Rootless bool // ignore permission-related errors
Paths map[string]string Paths map[string]string
} }
@@ -174,7 +174,7 @@ func (m *Manager) Apply(pid int) (err error) {
m.Paths[sys.Name()] = p m.Paths[sys.Name()] = p
if err := sys.Apply(d); err != nil { if err := sys.Apply(d); err != nil {
// In the case of rootless, where an explicit cgroup path hasn't // In the case of rootless (including euid=0 in userns), where an explicit cgroup path hasn't
// been set, we don't bail on error in case of permission problems. // been set, we don't bail on error in case of permission problems.
// Cases where limits have been set (and we couldn't create our own // Cases where limits have been set (and we couldn't create our own
// cgroup) are handled by Set. // cgroup) are handled by Set.
@@ -236,6 +236,12 @@ func (m *Manager) Set(container *configs.Config) error {
for _, sys := range subsystems { for _, sys := range subsystems {
path := paths[sys.Name()] path := paths[sys.Name()]
if err := sys.Set(path, container.Cgroups); err != nil { if err := sys.Set(path, container.Cgroups); err != nil {
if m.Rootless && sys.Name() == "devices" {
continue
}
// When m.Rootless is true, errors from the device subsystem are ignored because it is really not expected to work.
// However, errors from other subsystems are not ignored.
// see @test "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error"
if path == "" { if path == "" {
// We never created a path for this cgroup, so we cannot set // We never created a path for this cgroup, so we cannot set
// limits for it (though we have already tried at this point). // limits for it (though we have already tried at this point).
+10 -3
View File
@@ -186,12 +186,19 @@ type Config struct {
// callers keyring in this case. // callers keyring in this case.
NoNewKeyring bool `json:"no_new_keyring"` NoNewKeyring bool `json:"no_new_keyring"`
// Rootless specifies whether the container is a rootless container.
Rootless bool `json:"rootless"`
// IntelRdt specifies settings for Intel RDT/CAT group that the container is placed into // IntelRdt specifies settings for Intel RDT/CAT group that the container is placed into
// to limit the resources (e.g., L3 cache) the container has available // to limit the resources (e.g., L3 cache) the container has available
IntelRdt *IntelRdt `json:"intel_rdt,omitempty"` IntelRdt *IntelRdt `json:"intel_rdt,omitempty"`
// RootlessEUID is set when the runc was launched with non-zero EUID.
// Note that RootlessEUID is set to false when launched with EUID=0 in userns.
// When RootlessEUID is set, runc creates a new userns for the container.
// (config.json needs to contain userns settings)
RootlessEUID bool `json:"rootless_euid,omitempty"`
// RootlessCgroups is set when unlikely to have the full access to cgroups.
// When RootlessCgroups is set, cgroups errors are ignored.
RootlessCgroups bool `json:"rootless_cgroups,omitempty"`
} }
type Hooks struct { type Hooks struct {
+13 -40
View File
@@ -2,23 +2,18 @@ package validate
import ( import (
"fmt" "fmt"
"os"
"reflect"
"strings" "strings"
"github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/configs"
) )
var ( // rootlessEUID makes sure that the config can be applied when runc
geteuid = os.Geteuid // is being executed as a non-root user (euid != 0) in the current user namespace.
getegid = os.Getegid func (v *ConfigValidator) rootlessEUID(config *configs.Config) error {
) if err := rootlessEUIDMappings(config); err != nil {
func (v *ConfigValidator) rootless(config *configs.Config) error {
if err := rootlessMappings(config); err != nil {
return err return err
} }
if err := rootlessMount(config); err != nil { if err := rootlessEUIDMount(config); err != nil {
return err return err
} }
@@ -38,46 +33,24 @@ func hasIDMapping(id int, mappings []configs.IDMap) bool {
return false return false
} }
func rootlessMappings(config *configs.Config) error { func rootlessEUIDMappings(config *configs.Config) error {
if euid := geteuid(); euid != 0 { if !config.Namespaces.Contains(configs.NEWUSER) {
if !config.Namespaces.Contains(configs.NEWUSER) { return fmt.Errorf("rootless container requires user namespaces")
return fmt.Errorf("rootless containers require user namespaces")
}
if len(config.UidMappings) == 0 {
return fmt.Errorf("rootless containers requires at least one UID mapping")
}
if len(config.GidMappings) == 0 {
return fmt.Errorf("rootless containers requires at least one GID mapping")
}
} }
return nil if len(config.UidMappings) == 0 {
} return fmt.Errorf("rootless containers requires at least one UID mapping")
// cgroup verifies that the user isn't trying to set any cgroup limits or paths.
func rootlessCgroup(config *configs.Config) error {
// Nothing set at all.
if config.Cgroups == nil || config.Cgroups.Resources == nil {
return nil
} }
if len(config.GidMappings) == 0 {
// Used for comparing to the zero value. return fmt.Errorf("rootless containers requires at least one GID mapping")
left := reflect.ValueOf(*config.Cgroups.Resources)
right := reflect.Zero(left.Type())
// This is all we need to do, since specconv won't add cgroup options in
// rootless mode.
if !reflect.DeepEqual(left.Interface(), right.Interface()) {
return fmt.Errorf("cannot specify resource limits in rootless container")
} }
return nil return nil
} }
// mount verifies that the user isn't trying to set up any mounts they don't have // mount verifies that the user isn't trying to set up any mounts they don't have
// the rights to do. In addition, it makes sure that no mount has a `uid=` or // the rights to do. In addition, it makes sure that no mount has a `uid=` or
// `gid=` option that doesn't resolve to root. // `gid=` option that doesn't resolve to root.
func rootlessMount(config *configs.Config) error { func rootlessEUIDMount(config *configs.Config) error {
// XXX: We could whitelist allowed devices at this point, but I'm not // XXX: We could whitelist allowed devices at this point, but I'm not
// convinced that's a good idea. The kernel is the best arbiter of // convinced that's a good idea. The kernel is the best arbiter of
// access control. // access control.
+20 -24
View File
@@ -6,15 +6,11 @@ import (
"github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/configs"
) )
func init() { func rootlessEUIDConfig() *configs.Config {
geteuid = func() int { return 1337 }
getegid = func() int { return 7331 }
}
func rootlessConfig() *configs.Config {
return &configs.Config{ return &configs.Config{
Rootfs: "/var", Rootfs: "/var",
Rootless: true, RootlessEUID: true,
RootlessCgroups: true,
Namespaces: configs.Namespaces( Namespaces: configs.Namespaces(
[]configs.Namespace{ []configs.Namespace{
{Type: configs.NEWUSER}, {Type: configs.NEWUSER},
@@ -22,14 +18,14 @@ func rootlessConfig() *configs.Config {
), ),
UidMappings: []configs.IDMap{ UidMappings: []configs.IDMap{
{ {
HostID: geteuid(), HostID: 1337,
ContainerID: 0, ContainerID: 0,
Size: 1, Size: 1,
}, },
}, },
GidMappings: []configs.IDMap{ GidMappings: []configs.IDMap{
{ {
HostID: getegid(), HostID: 7331,
ContainerID: 0, ContainerID: 0,
Size: 1, Size: 1,
}, },
@@ -37,51 +33,51 @@ func rootlessConfig() *configs.Config {
} }
} }
func TestValidateRootless(t *testing.T) { func TestValidateRootlessEUID(t *testing.T) {
validator := New() validator := New()
config := rootlessConfig() config := rootlessEUIDConfig()
if err := validator.Validate(config); err != nil { if err := validator.Validate(config); err != nil {
t.Errorf("Expected error to not occur: %+v", err) t.Errorf("Expected error to not occur: %+v", err)
} }
} }
/* rootlessMappings() */ /* rootlessEUIDMappings */
func TestValidateRootlessUserns(t *testing.T) { func TestValidateRootlessEUIDUserns(t *testing.T) {
validator := New() validator := New()
config := rootlessConfig() config := rootlessEUIDConfig()
config.Namespaces = nil config.Namespaces = nil
if err := validator.Validate(config); err == nil { if err := validator.Validate(config); err == nil {
t.Errorf("Expected error to occur if user namespaces not set") t.Errorf("Expected error to occur if user namespaces not set")
} }
} }
func TestValidateRootlessMappingUid(t *testing.T) { func TestValidateRootlessEUIDMappingUid(t *testing.T) {
validator := New() validator := New()
config := rootlessConfig() config := rootlessEUIDConfig()
config.UidMappings = nil config.UidMappings = nil
if err := validator.Validate(config); err == nil { if err := validator.Validate(config); err == nil {
t.Errorf("Expected error to occur if no uid mappings provided") t.Errorf("Expected error to occur if no uid mappings provided")
} }
} }
func TestValidateRootlessMappingGid(t *testing.T) { func TestValidateNonZeroEUIDMappingGid(t *testing.T) {
validator := New() validator := New()
config := rootlessConfig() config := rootlessEUIDConfig()
config.GidMappings = nil config.GidMappings = nil
if err := validator.Validate(config); err == nil { if err := validator.Validate(config); err == nil {
t.Errorf("Expected error to occur if no gid mappings provided") t.Errorf("Expected error to occur if no gid mappings provided")
} }
} }
/* rootlessMount() */ /* rootlessEUIDMount() */
func TestValidateRootlessMountUid(t *testing.T) { func TestValidateRootlessEUIDMountUid(t *testing.T) {
config := rootlessConfig() config := rootlessEUIDConfig()
validator := New() validator := New()
config.Mounts = []*configs.Mount{ config.Mounts = []*configs.Mount{
@@ -119,8 +115,8 @@ func TestValidateRootlessMountUid(t *testing.T) {
} }
} }
func TestValidateRootlessMountGid(t *testing.T) { func TestValidateRootlessEUIDMountGid(t *testing.T) {
config := rootlessConfig() config := rootlessEUIDConfig()
validator := New() validator := New()
config.Mounts = []*configs.Mount{ config.Mounts = []*configs.Mount{
+2 -2
View File
@@ -44,8 +44,8 @@ func (v *ConfigValidator) Validate(config *configs.Config) error {
if err := v.intelrdt(config); err != nil { if err := v.intelrdt(config); err != nil {
return err return err
} }
if config.Rootless { if config.RootlessEUID {
if err := v.rootless(config); err != nil { if err := v.rootlessEUID(config); err != nil {
return err return err
} }
} }
+26 -25
View File
@@ -59,7 +59,8 @@ type State struct {
// Platform specific fields below here // Platform specific fields below here
// Specifies if the container was started under the rootless mode. // Specified if the container was started under the rootless mode.
// Set to true if BaseState.Config.RootlessEUID && BaseState.Config.RootlessCgroups
Rootless bool `json:"rootless"` Rootless bool `json:"rootless"`
// Path to all the cgroups setup for a container. Key is cgroup subsystem name // Path to all the cgroups setup for a container. Key is cgroup subsystem name
@@ -522,14 +523,15 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe,
return nil, err return nil, err
} }
return &setnsProcess{ return &setnsProcess{
cmd: cmd, cmd: cmd,
cgroupPaths: c.cgroupManager.GetPaths(), cgroupPaths: c.cgroupManager.GetPaths(),
intelRdtPath: state.IntelRdtPath, rootlessCgroups: c.config.RootlessCgroups,
childPipe: childPipe, intelRdtPath: state.IntelRdtPath,
parentPipe: parentPipe, childPipe: childPipe,
config: c.newInitConfig(p), parentPipe: parentPipe,
process: p, config: c.newInitConfig(p),
bootstrapData: data, process: p,
bootstrapData: data,
}, nil }, nil
} }
@@ -545,7 +547,8 @@ func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
PassedFilesCount: len(process.ExtraFiles), PassedFilesCount: len(process.ExtraFiles),
ContainerId: c.ID(), ContainerId: c.ID(),
NoNewPrivileges: c.config.NoNewPrivileges, NoNewPrivileges: c.config.NoNewPrivileges,
Rootless: c.config.Rootless, RootlessEUID: c.config.RootlessEUID,
RootlessCgroups: c.config.RootlessCgroups,
AppArmorProfile: c.config.AppArmorProfile, AppArmorProfile: c.config.AppArmorProfile,
ProcessLabel: c.config.ProcessLabel, ProcessLabel: c.config.ProcessLabel,
Rlimits: c.config.Rlimits, Rlimits: c.config.Rlimits,
@@ -613,16 +616,16 @@ func (c *linuxContainer) Resume() error {
func (c *linuxContainer) NotifyOOM() (<-chan struct{}, error) { func (c *linuxContainer) NotifyOOM() (<-chan struct{}, error) {
// XXX(cyphar): This requires cgroups. // XXX(cyphar): This requires cgroups.
if c.config.Rootless { if c.config.RootlessCgroups {
return nil, fmt.Errorf("cannot get OOM notifications from rootless container") logrus.Warn("getting OOM notifications may fail if you don't have the full access to cgroups")
} }
return notifyOnOOM(c.cgroupManager.GetPaths()) return notifyOnOOM(c.cgroupManager.GetPaths())
} }
func (c *linuxContainer) NotifyMemoryPressure(level PressureLevel) (<-chan struct{}, error) { func (c *linuxContainer) NotifyMemoryPressure(level PressureLevel) (<-chan struct{}, error) {
// XXX(cyphar): This requires cgroups. // XXX(cyphar): This requires cgroups.
if c.config.Rootless { if c.config.RootlessCgroups {
return nil, fmt.Errorf("cannot get memory pressure notifications from rootless container") logrus.Warn("getting memory pressure notifications may fail if you don't have the full access to cgroups")
} }
return notifyMemoryPressure(c.cgroupManager.GetPaths(), level) return notifyMemoryPressure(c.cgroupManager.GetPaths(), level)
} }
@@ -866,12 +869,11 @@ func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error {
c.m.Lock() c.m.Lock()
defer c.m.Unlock() defer c.m.Unlock()
// Checkpoint is unlikely to work if os.Geteuid() != 0 || system.RunningInUserNS().
// (CLI prints a warning)
// TODO(avagin): Figure out how to make this work nicely. CRIU 2.0 has // TODO(avagin): Figure out how to make this work nicely. CRIU 2.0 has
// support for doing unprivileged dumps, but the setup of // support for doing unprivileged dumps, but the setup of
// rootless containers might make this complicated. // rootless containers might make this complicated.
if c.config.Rootless {
return fmt.Errorf("cannot checkpoint a rootless container")
}
// criu 1.5.2 => 10502 // criu 1.5.2 => 10502
if err := c.checkCriuVersion(10502); err != nil { if err := c.checkCriuVersion(10502); err != nil {
@@ -1105,11 +1107,10 @@ func (c *linuxContainer) Restore(process *Process, criuOpts *CriuOpts) error {
var extraFiles []*os.File var extraFiles []*os.File
// Restore is unlikely to work if os.Geteuid() != 0 || system.RunningInUserNS().
// (CLI prints a warning)
// TODO(avagin): Figure out how to make this work nicely. CRIU doesn't have // TODO(avagin): Figure out how to make this work nicely. CRIU doesn't have
// support for unprivileged restore at the moment. // support for unprivileged restore at the moment.
if c.config.Rootless {
return fmt.Errorf("cannot restore a rootless container")
}
// criu 1.5.2 => 10502 // criu 1.5.2 => 10502
if err := c.checkCriuVersion(10502); err != nil { if err := c.checkCriuVersion(10502); err != nil {
@@ -1717,7 +1718,7 @@ func (c *linuxContainer) currentState() (*State, error) {
InitProcessStartTime: startTime, InitProcessStartTime: startTime,
Created: c.created, Created: c.created,
}, },
Rootless: c.config.Rootless, Rootless: c.config.RootlessEUID && c.config.RootlessCgroups,
CgroupPaths: c.cgroupManager.GetPaths(), CgroupPaths: c.cgroupManager.GetPaths(),
IntelRdtPath: intelRdtPath, IntelRdtPath: intelRdtPath,
NamespacePaths: make(map[configs.NamespaceType]string), NamespacePaths: make(map[configs.NamespaceType]string),
@@ -1818,7 +1819,7 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Na
if !joinExistingUser { if !joinExistingUser {
// write uid mappings // write uid mappings
if len(c.config.UidMappings) > 0 { if len(c.config.UidMappings) > 0 {
if c.config.Rootless && c.newuidmapPath != "" { if c.config.RootlessEUID && c.newuidmapPath != "" {
r.AddData(&Bytemsg{ r.AddData(&Bytemsg{
Type: UidmapPathAttr, Type: UidmapPathAttr,
Value: []byte(c.newuidmapPath), Value: []byte(c.newuidmapPath),
@@ -1844,7 +1845,7 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Na
Type: GidmapAttr, Type: GidmapAttr,
Value: b, Value: b,
}) })
if c.config.Rootless && c.newgidmapPath != "" { if c.config.RootlessEUID && c.newgidmapPath != "" {
r.AddData(&Bytemsg{ r.AddData(&Bytemsg{
Type: GidmapPathAttr, Type: GidmapPathAttr,
Value: []byte(c.newgidmapPath), Value: []byte(c.newgidmapPath),
@@ -1869,8 +1870,8 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Na
// write rootless // write rootless
r.AddData(&Boolmsg{ r.AddData(&Boolmsg{
Type: RootlessAttr, Type: RootlessEUIDAttr,
Value: c.config.Rootless, Value: c.config.RootlessEUID,
}) })
return bytes.NewReader(r.Serialize()), nil return bytes.NewReader(r.Serialize()), nil
+1 -1
View File
@@ -77,7 +77,7 @@ func Cgroupfs(l *LinuxFactory) error {
// containers that use the native cgroups filesystem implementation to create // containers that use the native cgroups filesystem implementation to create
// and manage cgroups. The difference between RootlessCgroupfs and Cgroupfs is // and manage cgroups. The difference between RootlessCgroupfs and Cgroupfs is
// that RootlessCgroupfs can transparently handle permission errors that occur // that RootlessCgroupfs can transparently handle permission errors that occur
// during rootless container setup (while still allowing cgroup usage if // during rootless container (including euid=0 in userns) setup (while still allowing cgroup usage if
// they've been set up properly). // they've been set up properly).
func RootlessCgroupfs(l *LinuxFactory) error { func RootlessCgroupfs(l *LinuxFactory) error {
l.NewCgroupsManager = func(config *configs.Cgroup, paths map[string]string) cgroups.Manager { l.NewCgroupsManager = func(config *configs.Cgroup, paths map[string]string) cgroups.Manager {
+4 -3
View File
@@ -65,7 +65,8 @@ type initConfig struct {
CreateConsole bool `json:"create_console"` CreateConsole bool `json:"create_console"`
ConsoleWidth uint16 `json:"console_width"` ConsoleWidth uint16 `json:"console_width"`
ConsoleHeight uint16 `json:"console_height"` ConsoleHeight uint16 `json:"console_height"`
Rootless bool `json:"rootless"` RootlessEUID bool `json:"rootless_euid,omitempty"`
RootlessCgroups bool `json:"rootless_cgroups,omitempty"`
} }
type initer interface { type initer interface {
@@ -283,7 +284,7 @@ func setupUser(config *initConfig) error {
return fmt.Errorf("cannot set gid to unmapped user in user namespace") return fmt.Errorf("cannot set gid to unmapped user in user namespace")
} }
if config.Rootless { if config.RootlessEUID {
// We cannot set any additional groups in a rootless container and thus // We cannot set any additional groups in a rootless container and thus
// we bail if the user asked us to do so. TODO: We currently can't do // we bail if the user asked us to do so. TODO: We currently can't do
// this check earlier, but if libcontainer.Process.User was typesafe // this check earlier, but if libcontainer.Process.User was typesafe
@@ -303,7 +304,7 @@ func setupUser(config *initConfig) error {
// There's nothing we can do about /etc/group entries, so we silently // There's nothing we can do about /etc/group entries, so we silently
// ignore setting groups here (since the user didn't explicitly ask us to // ignore setting groups here (since the user didn't explicitly ask us to
// set the group). // set the group).
if !config.Rootless { if !config.RootlessEUID {
suppGroups := append(execUser.Sgids, addGroups...) suppGroups := append(execUser.Sgids, addGroups...)
if err := unix.Setgroups(suppGroups); err != nil { if err := unix.Setgroups(suppGroups); err != nil {
return err return err
+10 -10
View File
@@ -10,16 +10,16 @@ import (
// list of known message types we want to send to bootstrap program // list of known message types we want to send to bootstrap program
// The number is randomly chosen to not conflict with known netlink types // The number is randomly chosen to not conflict with known netlink types
const ( const (
InitMsg uint16 = 62000 InitMsg uint16 = 62000
CloneFlagsAttr uint16 = 27281 CloneFlagsAttr uint16 = 27281
NsPathsAttr uint16 = 27282 NsPathsAttr uint16 = 27282
UidmapAttr uint16 = 27283 UidmapAttr uint16 = 27283
GidmapAttr uint16 = 27284 GidmapAttr uint16 = 27284
SetgroupAttr uint16 = 27285 SetgroupAttr uint16 = 27285
OomScoreAdjAttr uint16 = 27286 OomScoreAdjAttr uint16 = 27286
RootlessAttr uint16 = 27287 RootlessEUIDAttr uint16 = 27287
UidmapPathAttr uint16 = 27288 UidmapPathAttr uint16 = 27288
GidmapPathAttr uint16 = 27289 GidmapPathAttr uint16 = 27289
) )
type Int32msg struct { type Int32msg struct {
+6 -6
View File
@@ -82,7 +82,7 @@ struct nlconfig_t {
uint8_t is_setgroup; uint8_t is_setgroup;
/* Rootless container settings. */ /* Rootless container settings. */
uint8_t is_rootless; uint8_t is_rootless_euid; /* boolean */
char *uidmappath; char *uidmappath;
size_t uidmappath_len; size_t uidmappath_len;
char *gidmappath; char *gidmappath;
@@ -100,7 +100,7 @@ struct nlconfig_t {
#define GIDMAP_ATTR 27284 #define GIDMAP_ATTR 27284
#define SETGROUP_ATTR 27285 #define SETGROUP_ATTR 27285
#define OOM_SCORE_ADJ_ATTR 27286 #define OOM_SCORE_ADJ_ATTR 27286
#define ROOTLESS_ATTR 27287 #define ROOTLESS_EUID_ATTR 27287
#define UIDMAPPATH_ATTR 27288 #define UIDMAPPATH_ATTR 27288
#define GIDMAPPATH_ATTR 27289 #define GIDMAPPATH_ATTR 27289
@@ -419,8 +419,8 @@ static void nl_parse(int fd, struct nlconfig_t *config)
case CLONE_FLAGS_ATTR: case CLONE_FLAGS_ATTR:
config->cloneflags = readint32(current); config->cloneflags = readint32(current);
break; break;
case ROOTLESS_ATTR: case ROOTLESS_EUID_ATTR:
config->is_rootless = readint8(current); config->is_rootless_euid = readint8(current); /* boolean */
break; break;
case OOM_SCORE_ADJ_ATTR: case OOM_SCORE_ADJ_ATTR:
config->oom_score_adj = current; config->oom_score_adj = current;
@@ -687,7 +687,7 @@ void nsexec(void)
* newuidmap/newgidmap shall be used. * newuidmap/newgidmap shall be used.
*/ */
if (config.is_rootless && !config.is_setgroup) if (config.is_rootless_euid && !config.is_setgroup)
update_setgroups(child, SETGROUPS_DENY); update_setgroups(child, SETGROUPS_DENY);
/* Set up mappings. */ /* Set up mappings. */
@@ -953,7 +953,7 @@ void nsexec(void)
if (setgid(0) < 0) if (setgid(0) < 0)
bail("setgid failed"); bail("setgid failed");
if (!config.is_rootless && config.is_setgroup) { if (!config.is_rootless_euid && config.is_setgroup) {
if (setgroups(0, NULL) < 0) if (setgroups(0, NULL) < 0)
bail("setgroups failed"); bail("setgroups failed");
} }
+11 -10
View File
@@ -46,15 +46,16 @@ type parentProcess interface {
} }
type setnsProcess struct { type setnsProcess struct {
cmd *exec.Cmd cmd *exec.Cmd
parentPipe *os.File parentPipe *os.File
childPipe *os.File childPipe *os.File
cgroupPaths map[string]string cgroupPaths map[string]string
intelRdtPath string rootlessCgroups bool
config *initConfig intelRdtPath string
fds []string config *initConfig
process *Process fds []string
bootstrapData io.Reader process *Process
bootstrapData io.Reader
} }
func (p *setnsProcess) startTime() (uint64, error) { func (p *setnsProcess) startTime() (uint64, error) {
@@ -86,7 +87,7 @@ func (p *setnsProcess) start() (err error) {
return newSystemErrorWithCause(err, "executing setns process") return newSystemErrorWithCause(err, "executing setns process")
} }
if len(p.cgroupPaths) > 0 { if len(p.cgroupPaths) > 0 {
if err := cgroups.EnterPid(p.cgroupPaths, p.pid()); err != nil { if err := cgroups.EnterPid(p.cgroupPaths, p.pid()); err != nil && !p.rootlessCgroups {
return newSystemErrorWithCausef(err, "adding pid %d to cgroups", p.pid()) return newSystemErrorWithCausef(err, "adding pid %d to cgroups", p.pid())
} }
} }
+1 -1
View File
@@ -156,7 +156,7 @@ func Example() *specs.Spec {
} }
// ToRootless converts the given spec file into one that should work with // ToRootless converts the given spec file into one that should work with
// rootless containers, by removing incompatible options and adding others that // rootless containers (euid != 0), by removing incompatible options and adding others that
// are needed. // are needed.
func ToRootless(spec *specs.Spec) { func ToRootless(spec *specs.Spec) {
var namespaces []specs.LinuxNamespace var namespaces []specs.LinuxNamespace
+15 -18
View File
@@ -148,7 +148,8 @@ type CreateOpts struct {
NoPivotRoot bool NoPivotRoot bool
NoNewKeyring bool NoNewKeyring bool
Spec *specs.Spec Spec *specs.Spec
Rootless bool RootlessEUID bool
RootlessCgroups bool
} }
// CreateLibcontainerConfig creates a new libcontainer configuration from a // CreateLibcontainerConfig creates a new libcontainer configuration from a
@@ -176,13 +177,14 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
labels = append(labels, fmt.Sprintf("%s=%s", k, v)) labels = append(labels, fmt.Sprintf("%s=%s", k, v))
} }
config := &configs.Config{ config := &configs.Config{
Rootfs: rootfsPath, Rootfs: rootfsPath,
NoPivotRoot: opts.NoPivotRoot, NoPivotRoot: opts.NoPivotRoot,
Readonlyfs: spec.Root.Readonly, Readonlyfs: spec.Root.Readonly,
Hostname: spec.Hostname, Hostname: spec.Hostname,
Labels: append(labels, fmt.Sprintf("bundle=%s", cwd)), Labels: append(labels, fmt.Sprintf("bundle=%s", cwd)),
NoNewKeyring: opts.NoNewKeyring, NoNewKeyring: opts.NoNewKeyring,
Rootless: opts.Rootless, RootlessEUID: opts.RootlessEUID,
RootlessCgroups: opts.RootlessCgroups,
} }
exists := false exists := false
@@ -332,12 +334,9 @@ func createCgroupConfig(opts *CreateOpts) (*configs.Cgroup, error) {
c.Path = myCgroupPath c.Path = myCgroupPath
} }
// In rootless containers, any attempt to make cgroup changes will fail. // In rootless containers, any attempt to make cgroup changes is likely to fail.
// libcontainer will validate this and we shouldn't add any cgroup options // libcontainer will validate this but ignores the error.
// the user didn't specify. c.Resources.AllowedDevices = allowedDevices
if !opts.Rootless {
c.Resources.AllowedDevices = allowedDevices
}
if spec.Linux != nil { if spec.Linux != nil {
r := spec.Linux.Resources r := spec.Linux.Resources
if r == nil { if r == nil {
@@ -490,10 +489,8 @@ func createCgroupConfig(opts *CreateOpts) (*configs.Cgroup, error) {
} }
} }
} }
if !opts.Rootless { // append the default allowed devices to the end of the list
// append the default allowed devices to the end of the list c.Resources.Devices = append(c.Resources.Devices, allowedDevices...)
c.Resources.Devices = append(c.Resources.Devices, allowedDevices...)
}
return c, nil return c, nil
} }
+3 -2
View File
@@ -417,7 +417,7 @@ func TestDupNamespaces(t *testing.T) {
} }
} }
func TestRootlessSpecconvValidate(t *testing.T) { func TestNonZeroEUIDCompatibleSpecconvValidate(t *testing.T) {
if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) { if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) {
t.Skip("userns is unsupported") t.Skip("userns is unsupported")
} }
@@ -430,7 +430,8 @@ func TestRootlessSpecconvValidate(t *testing.T) {
CgroupName: "ContainerID", CgroupName: "ContainerID",
UseSystemdCgroup: false, UseSystemdCgroup: false,
Spec: spec, Spec: spec,
Rootless: true, RootlessEUID: true,
RootlessCgroups: true,
} }
config, err := CreateLibcontainerConfig(opts) config, err := CreateLibcontainerConfig(opts)
+3 -8
View File
@@ -63,13 +63,8 @@ func main() {
app.Version = strings.Join(v, "\n") app.Version = strings.Join(v, "\n")
root := "/run/runc" root := "/run/runc"
rootless, err := isRootless(nil) if shouldHonorXDGRuntimeDir() {
if err != nil { if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" {
fatal(err)
}
if rootless {
runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
if runtimeDir != "" {
root = runtimeDir + "/runc" root = runtimeDir + "/runc"
// According to the XDG specification, we need to set anything in // According to the XDG specification, we need to set anything in
// XDG_RUNTIME_DIR to have a sticky bit if we don't want it to get // XDG_RUNTIME_DIR to have a sticky bit if we don't want it to get
@@ -115,7 +110,7 @@ func main() {
cli.StringFlag{ cli.StringFlag{
Name: "rootless", Name: "rootless",
Value: "auto", Value: "auto",
Usage: "enable rootless mode ('true', 'false', or 'auto')", Usage: "ignore cgroup permission errors ('true', 'false', or 'auto')",
}, },
} }
app.Commands = []cli.Command{ app.Commands = []cli.Command{
+18 -1
View File
@@ -2,7 +2,10 @@
package main package main
import "github.com/urfave/cli" import (
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
var pauseCommand = cli.Command{ var pauseCommand = cli.Command{
Name: "pause", Name: "pause",
@@ -18,6 +21,13 @@ Use runc list to identiy instances of containers and their current status.`,
if err := checkArgs(context, 1, exactArgs); err != nil { if err := checkArgs(context, 1, exactArgs); err != nil {
return err return err
} }
rootlessCg, err := shouldUseRootlessCgroupManager(context)
if err != nil {
return err
}
if rootlessCg {
logrus.Warnf("runc pause may fail if you don't have the full access to cgroups")
}
container, err := getContainer(context) container, err := getContainer(context)
if err != nil { if err != nil {
return err return err
@@ -44,6 +54,13 @@ Use runc list to identiy instances of containers and their current status.`,
if err := checkArgs(context, 1, exactArgs); err != nil { if err := checkArgs(context, 1, exactArgs); err != nil {
return err return err
} }
rootlessCg, err := shouldUseRootlessCgroupManager(context)
if err != nil {
return err
}
if rootlessCg {
logrus.Warn("runc resume may fail if you don't have the full access to cgroups")
}
container, err := getContainer(context) container, err := getContainer(context)
if err != nil { if err != nil {
return err return err
+4 -4
View File
@@ -10,6 +10,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@@ -28,13 +29,12 @@ var psCommand = cli.Command{
if err := checkArgs(context, 1, minArgs); err != nil { if err := checkArgs(context, 1, minArgs); err != nil {
return err return err
} }
// XXX: Currently not supported with rootless containers. rootlessCg, err := shouldUseRootlessCgroupManager(context)
rootless, err := isRootless(context)
if err != nil { if err != nil {
return err return err
} }
if rootless { if rootlessCg {
return fmt.Errorf("runc ps requires root") logrus.Warn("runc ps may fail if you don't have the full access to cgroups")
} }
container, err := getContainer(context) container, err := getContainer(context)
+4 -7
View File
@@ -3,10 +3,11 @@
package main package main
import ( import (
"fmt"
"os" "os"
"github.com/opencontainers/runc/libcontainer" "github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/system"
"github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@@ -96,12 +97,8 @@ using the runc checkpoint command.`,
return err return err
} }
// XXX: Currently this is untested with rootless containers. // XXX: Currently this is untested with rootless containers.
rootless, err := isRootless(context) if os.Geteuid() != 0 || system.RunningInUserNS() {
if err != nil { logrus.Warn("runc checkpoint is untested with rootless containers")
return err
}
if rootless {
return fmt.Errorf("runc restore requires root")
} }
spec, err := setupSpec(context) spec, err := setupSpec(context)
+57
View File
@@ -0,0 +1,57 @@
// +build linux
package main
import (
"os"
"github.com/opencontainers/runc/libcontainer/system"
"github.com/urfave/cli"
)
func shouldUseRootlessCgroupManager(context *cli.Context) (bool, error) {
if context != nil {
b, err := parseBoolOrAuto(context.GlobalString("rootless"))
if err != nil {
return false, err
}
if b != nil {
return *b, nil
}
// nil b stands for "auto detect"
}
if context.GlobalBool("systemd-cgroup") {
return false, nil
}
if os.Geteuid() != 0 {
return true, nil
}
if !system.RunningInUserNS() {
// euid == 0 , in the initial ns (i.e. the real root)
return false, nil
}
// euid = 0, in a userns.
// As we are unaware of cgroups path, we can't determine whether we have the full
// access to the cgroups path.
// Either way, we can safely decide to use the rootless cgroups manager.
return true, nil
}
func shouldHonorXDGRuntimeDir() bool {
if os.Getenv("XDG_RUNTIME_DIR") == "" {
return false
}
if os.Geteuid() != 0 {
return true
}
if !system.RunningInUserNS() {
// euid == 0 , in the initial ns (i.e. the real root)
// in this case, we should use /run/runc and ignore
// $XDG_RUNTIME_DIR (e.g. /run/user/0) for backward
// compatibility.
return false
}
// euid = 0, in a userns.
u, ok := os.LookupEnv("USER")
return !ok || u != "root"
}
+5 -1
View File
@@ -61,7 +61,11 @@ container on your host.
Alternatively, you can start a rootless container, which has the ability to run Alternatively, you can start a rootless container, which has the ability to run
without root privileges. For this to work, the specification file needs to be without root privileges. For this to work, the specification file needs to be
adjusted accordingly. You can pass the parameter --rootless to this command to adjusted accordingly. You can pass the parameter --rootless to this command to
generate a proper rootless spec file.`, generate a proper rootless spec file.
Note that --rootless is not needed when you execute runc as the root in a user namespace
created by an unprivileged user.
`,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ cli.StringFlag{
Name: "bundle, b", Name: "bundle, b",
+5 -24
View File
@@ -16,7 +16,6 @@ import (
"github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/intelrdt" "github.com/opencontainers/runc/libcontainer/intelrdt"
"github.com/opencontainers/runc/libcontainer/specconv" "github.com/opencontainers/runc/libcontainer/specconv"
"github.com/opencontainers/runc/libcontainer/system"
"github.com/opencontainers/runc/libcontainer/utils" "github.com/opencontainers/runc/libcontainer/utils"
"github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-spec/specs-go"
@@ -39,11 +38,11 @@ func loadFactory(context *cli.Context) (libcontainer.Factory, error) {
// We default to cgroupfs, and can only use systemd if the system is a // We default to cgroupfs, and can only use systemd if the system is a
// systemd box. // systemd box.
cgroupManager := libcontainer.Cgroupfs cgroupManager := libcontainer.Cgroupfs
rootless, err := isRootless(context) rootlessCg, err := shouldUseRootlessCgroupManager(context)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if rootless { if rootlessCg {
cgroupManager = libcontainer.RootlessCgroupfs cgroupManager = libcontainer.RootlessCgroupfs
} }
if context.GlobalBool("systemd-cgroup") { if context.GlobalBool("systemd-cgroup") {
@@ -226,27 +225,8 @@ func createPidFile(path string, process *libcontainer.Process) error {
return os.Rename(tmpName, path) return os.Rename(tmpName, path)
} }
func isRootless(context *cli.Context) (bool, error) {
if context != nil {
b, err := parseBoolOrAuto(context.GlobalString("rootless"))
if err != nil {
return false, err
}
if b != nil {
return *b, nil
}
// nil b stands for "auto detect"
}
// Even if os.Geteuid() == 0, it might still require rootless mode,
// especially when running within userns.
// So we use system.GetParentNSeuid() here.
//
// TODO(AkihiroSuda): how to support nested userns?
return system.GetParentNSeuid() != 0 || system.RunningInUserNS(), nil
}
func createContainer(context *cli.Context, id string, spec *specs.Spec) (libcontainer.Container, error) { func createContainer(context *cli.Context, id string, spec *specs.Spec) (libcontainer.Container, error) {
rootless, err := isRootless(context) rootlessCg, err := shouldUseRootlessCgroupManager(context)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -256,7 +236,8 @@ func createContainer(context *cli.Context, id string, spec *specs.Spec) (libcont
NoPivotRoot: context.Bool("no-pivot"), NoPivotRoot: context.Bool("no-pivot"),
NoNewKeyring: context.Bool("no-new-keyring"), NoNewKeyring: context.Bool("no-new-keyring"),
Spec: spec, Spec: spec,
Rootless: rootless, RootlessEUID: os.Geteuid() != 0,
RootlessCgroups: rootlessCg,
}) })
if err != nil { if err != nil {
return nil, err return nil, err