From 339edce03ed7fe59ec4a778abff243fa4cabaa23 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 17 Feb 2015 21:37:02 -0800 Subject: [PATCH 1/2] Update console and mount handling for user namespaces This updates the console handling to chown the console on creation to the root user within the container. This also moves the setup mounts from the userns sidecar process into the main init processes by trying to mknod devices, if it fails on an EPERM then bind mount the device from the host into the container for use. This prevents access issues when the sidecar process mknods the device for the usernamespace returning an EPERM when writting to dev/null. This also adds some error handling for init processes and nsinit updates with added flags for testing and other functions. Signed-off-by: Michael Crosby --- linux_console.go | 20 +++---- linux_rootfs.go | 68 ++++++++++++----------- linux_userns_init.go | 35 +++++++----- linux_userns_sidecar_init.go | 12 ----- nsinit/config.go | 101 ++++++++++++++++++++++++++++++++--- nsinit/exec.go | 10 ++-- nsinit/init.go | 4 +- nsinit/tty.go | 20 +++---- 8 files changed, 178 insertions(+), 92 deletions(-) diff --git a/linux_console.go b/linux_console.go index 60de3ee94..f7a67a014 100644 --- a/linux_console.go +++ b/linux_console.go @@ -12,13 +12,9 @@ import ( "github.com/docker/libcontainer/label" ) -const ( - containerConsolePath string = "/dev/console" -) - // NewConsole returns an initalized console that can be used within a container by copying bytes // from the master side to the slave that is attached as the tty for the container's init process. -func NewConsole() (Console, error) { +func NewConsole(uid, gid int) (Console, error) { master, err := os.OpenFile("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0) if err != nil { return nil, err @@ -30,6 +26,12 @@ func NewConsole() (Console, error) { if err := unlockpt(master); err != nil { return nil, err } + if err := os.Chmod(console, 0600); err != nil { + return nil, err + } + if err := os.Chown(console, uid, gid); err != nil { + return nil, err + } return &linuxConsole{ slavePath: console, master: master, @@ -78,16 +80,10 @@ func (c *linuxConsole) Close() error { func (c *linuxConsole) mount(rootfs, mountLabel string, uid, gid int) error { oldMask := syscall.Umask(0000) defer syscall.Umask(oldMask) - if err := os.Chmod(c.slavePath, 0600); err != nil { - return err - } - if err := os.Chown(c.slavePath, uid, gid); err != nil { - return err - } if err := label.SetFileLabel(c.slavePath, mountLabel); err != nil { return err } - dest := filepath.Join(rootfs, containerConsolePath) + dest := filepath.Join(rootfs, "/dev/console") f, err := os.Create(dest) if err != nil && !os.IsExist(err) { return err diff --git a/linux_rootfs.go b/linux_rootfs.go index 5e3e51d7f..74c711492 100644 --- a/linux_rootfs.go +++ b/linux_rootfs.go @@ -35,7 +35,7 @@ var baseMounts = []*configs.Mount{ Destination: "/dev/pts", Device: "devpts", Flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, - Data: "newinstance,ptmxmode=0666,mode=620,gid=5", + Data: "newinstance,ptmxmode=0666,mode=0620,gid=5", }, } @@ -43,32 +43,29 @@ var baseMounts = []*configs.Mount{ // new mount namespace. func setupRootfs(config *configs.Config) (err error) { if err := prepareRoot(config); err != nil { - return err + return newSystemError(err) } for _, m := range append(baseMounts, config.Mounts...) { if err := mount(m, config.Rootfs, config.MountLabel); err != nil { - return err + return newSystemError(err) } } if err := createDevices(config); err != nil { - return err + return newSystemError(err) } if err := setupPtmx(config); err != nil { - return err + return newSystemError(err) } // stdin, stdout and stderr could be pointing to /dev/null from parent namespace. - // Re-open them inside this namespace. - // FIXME: Need to fix this for user namespaces. - if !config.Namespaces.Contains(configs.NEWUSER) { - if err := reOpenDevNull(config.Rootfs); err != nil { - return err - } + // re-open them inside this namespace. + if err := reOpenDevNull(config.Rootfs); err != nil { + return newSystemError(err) } if err := setupDevSymlinks(config.Rootfs); err != nil { - return err + return newSystemError(err) } if err := syscall.Chdir(config.Rootfs); err != nil { - return err + return newSystemError(err) } if config.NoPivotRoot { err = msMoveRoot(config.Rootfs) @@ -76,11 +73,11 @@ func setupRootfs(config *configs.Config) (err error) { err = pivotRoot(config.Rootfs, config.PivotDir) } if err != nil { - return err + return newSystemError(err) } if config.Readonlyfs { if err := setReadonly(); err != nil { - return err + return newSystemError(err) } } syscall.Umask(0022) @@ -209,6 +206,28 @@ func createDeviceNode(rootfs string, node *configs.Device) error { if err := os.MkdirAll(parent, 0755); err != nil { return err } + if err := mknodDevice(dest, node); err != nil { + if os.IsExist(err) { + return nil + } + // containers running in a user namespace are not allowed to mknod + // devices so we can just bind mount it from the host. + if err == syscall.EPERM { + f, err := os.Create(dest) + if err != nil { + if os.IsExist(err) { + return nil + } + return err + } + f.Close() + return syscall.Mount(node.Path, dest, "bind", syscall.MS_BIND, "") + } + } + return nil +} + +func mknodDevice(dest string, node *configs.Device) error { fileMode := node.FileMode switch node.Type { case 'c': @@ -218,13 +237,10 @@ func createDeviceNode(rootfs string, node *configs.Device) error { default: return fmt.Errorf("%c is not a valid device type for device %s", node.Type, node.Path) } - if err := syscall.Mknod(dest, uint32(fileMode), node.Mkdev()); err != nil && !os.IsExist(err) { - return fmt.Errorf("mknod %s %s", node.Path, err) + if err := syscall.Mknod(dest, uint32(fileMode), node.Mkdev()); err != nil { + return err } - if err := syscall.Chown(dest, int(node.Uid), int(node.Gid)); err != nil { - return fmt.Errorf("chown %s to %d:%d", node.Path, node.Uid, node.Gid) - } - return nil + return syscall.Chown(dest, int(node.Uid), int(node.Gid)) } func prepareRoot(config *configs.Config) error { @@ -251,16 +267,8 @@ func setupPtmx(config *configs.Config) error { return fmt.Errorf("symlink dev ptmx %s", err) } if config.Console != "" { - uid, err := config.HostUID() - if err != nil { - return err - } - gid, err := config.HostGID() - if err != nil { - return err - } console := newConsoleFromPath(config.Console) - return console.mount(config.Rootfs, config.MountLabel, uid, gid) + return console.mount(config.Rootfs, config.MountLabel, 0, 0) } return nil } diff --git a/linux_userns_init.go b/linux_userns_init.go index d07668cd1..7f5806fe5 100644 --- a/linux_userns_init.go +++ b/linux_userns_init.go @@ -6,6 +6,7 @@ import ( "syscall" "github.com/docker/libcontainer/apparmor" + "github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/label" "github.com/docker/libcontainer/system" ) @@ -17,63 +18,69 @@ type linuxUsernsInit struct { func (l *linuxUsernsInit) Init() error { // join any namespaces via a path to the namespace fd if provided if err := joinExistingNamespaces(l.config.Config.Namespaces); err != nil { - return err + return newSystemError(err) } consolePath := l.config.Config.Console if consolePath != "" { // We use the containerConsolePath here, because the console has already been // setup by the side car process for the user namespace scenario. - console := newConsoleFromPath(containerConsolePath) + console := newConsoleFromPath(consolePath) if err := console.dupStdio(); err != nil { - return err + return newSystemError(err) } } if _, err := syscall.Setsid(); err != nil { - return err + return newSystemError(err) } if consolePath != "" { if err := system.Setctty(); err != nil { - return err + return newSystemError(err) } } if l.config.Cwd == "" { l.config.Cwd = "/" } if err := setupRlimits(l.config.Config); err != nil { - return err + return newSystemError(err) + } + // InitializeMountNamespace() can be executed only for a new mount namespace + if l.config.Config.Namespaces.Contains(configs.NEWNS) { + if err := setupRootfs(l.config.Config); err != nil { + return newSystemError(err) + } } if hostname := l.config.Config.Hostname; hostname != "" { if err := syscall.Sethostname([]byte(hostname)); err != nil { - return err + return newSystemError(err) } } if err := apparmor.ApplyProfile(l.config.Config.AppArmorProfile); err != nil { - return err + return newSystemError(err) } if err := label.SetProcessLabel(l.config.Config.ProcessLabel); err != nil { - return err + return newSystemError(err) } for _, path := range l.config.Config.ReadonlyPaths { if err := remountReadonly(path); err != nil { - return err + return newSystemError(err) } } for _, path := range l.config.Config.MaskPaths { if err := maskFile(path); err != nil { - return err + return newSystemError(err) } } pdeath, err := system.GetParentDeathSignal() if err != nil { - return err + return newSystemError(err) } if err := finalizeNamespace(l.config); err != nil { - return err + return newSystemError(err) } // finalizeNamespace can change user/group which clears the parent death // signal, so we restore it here. if err := pdeath.Restore(); err != nil { - return err + return newSystemError(err) } // Signal self if parent is already dead. Does nothing if running in a new // PID namespace, as Getppid will always return 0. diff --git a/linux_userns_sidecar_init.go b/linux_userns_sidecar_init.go index eedc63e0d..119abfabc 100644 --- a/linux_userns_sidecar_init.go +++ b/linux_userns_sidecar_init.go @@ -2,11 +2,6 @@ package libcontainer -import ( - "github.com/docker/libcontainer/configs" - "github.com/docker/libcontainer/label" -) - // linuxUsernsSideCar is run to setup mounts and networking related operations // for a user namespace enabled process as a user namespace root doesn't // have permissions to perform these operations. @@ -24,12 +19,5 @@ func (l *linuxUsernsSideCar) Init() error { if err := setupRoute(l.config.Config); err != nil { return err } - label.Init() - // InitializeMountNamespace() can be executed only for a new mount namespace - if l.config.Config.Namespaces.Contains(configs.NEWNS) { - if err := setupRootfs(l.config.Config); err != nil { - return err - } - } return nil } diff --git a/nsinit/config.go b/nsinit/config.go index e26f39e90..7dbc8a78e 100644 --- a/nsinit/config.go +++ b/nsinit/config.go @@ -7,10 +7,13 @@ import ( "math" "os" "path/filepath" + "strings" "syscall" + "github.com/Sirupsen/logrus" "github.com/codegangsta/cli" "github.com/docker/libcontainer/configs" + "github.com/docker/libcontainer/utils" ) const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV @@ -28,7 +31,17 @@ var createFlags = []cli.Flag{ cli.StringFlag{Name: "apparmor-profile", Usage: "set the apparmor profile"}, cli.StringFlag{Name: "process-label", Usage: "set the process label"}, cli.StringFlag{Name: "mount-label", Usage: "set the mount label"}, + cli.StringFlag{Name: "rootfs", Usage: "set the rootfs"}, cli.IntFlag{Name: "userns-root-uid", Usage: "set the user namespace root uid"}, + cli.StringFlag{Name: "net", Value: "", Usage: "network namespace"}, + cli.StringFlag{Name: "ipc", Value: "", Usage: "ipc namespace"}, + cli.StringFlag{Name: "pid", Value: "", Usage: "pid namespace"}, + cli.StringFlag{Name: "uts", Value: "", Usage: "uts namespace"}, + cli.StringFlag{Name: "mnt", Value: "", Usage: "mount namespace"}, + cli.StringFlag{Name: "veth-bridge", Usage: "veth bridge"}, + cli.StringFlag{Name: "veth-address", Usage: "veth ip address"}, + cli.StringFlag{Name: "veth-gateway", Usage: "veth gateway address"}, + cli.IntFlag{Name: "veth-mtu", Usage: "veth mtu"}, } var configCommand = cli.Command{ @@ -72,10 +85,11 @@ func modify(config *configs.Config, context *cli.Context) { config.AppArmorProfile = context.String("apparmor-profile") config.ProcessLabel = context.String("process-label") config.MountLabel = context.String("mount-label") + config.Rootfs = context.String("rootfs") userns_uid := context.Int("userns-root-uid") if userns_uid != 0 { - config.Namespaces = append(config.Namespaces, configs.Namespace{Type: configs.NEWUSER}) + config.Namespaces.Add(configs.NEWUSER, "") config.UidMappings = []configs.IDMap{ {ContainerID: 0, HostID: userns_uid, Size: 1}, {ContainerID: 1, HostID: 1, Size: userns_uid - 1}, @@ -86,6 +100,84 @@ func modify(config *configs.Config, context *cli.Context) { {ContainerID: 1, HostID: 1, Size: userns_uid - 1}, {ContainerID: userns_uid + 1, HostID: userns_uid + 1, Size: math.MaxInt32 - userns_uid}, } + for _, node := range config.Devices { + node.Uid = uint32(userns_uid) + node.Gid = uint32(userns_uid) + } + } + for _, rawBind := range context.StringSlice("bind") { + mount := &configs.Mount{ + Device: "bind", + Flags: syscall.MS_BIND | syscall.MS_REC, + } + parts := strings.SplitN(rawBind, ":", 3) + switch len(parts) { + default: + logrus.Fatalf("invalid bind mount %s", rawBind) + case 2: + mount.Source, mount.Destination = parts[0], parts[1] + case 3: + mount.Source, mount.Destination = parts[0], parts[1] + switch parts[2] { + case "ro": + mount.Flags |= syscall.MS_RDONLY + case "rw": + default: + logrus.Fatalf("invalid bind mount mode %s", parts[2]) + } + } + config.Mounts = append(config.Mounts, mount) + } + for _, tmpfs := range context.StringSlice("tmpfs") { + config.Mounts = append(config.Mounts, &configs.Mount{ + Device: "tmpfs", + Destination: tmpfs, + Flags: syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV, + }) + } + for flag, value := range map[string]configs.NamespaceType{ + "net": configs.NEWNET, + "mnt": configs.NEWNS, + "pid": configs.NEWPID, + "ipc": configs.NEWIPC, + "uts": configs.NEWUTS, + } { + switch v := context.String(flag); v { + case "host": + config.Namespaces.Remove(value) + case "", "private": + if !config.Namespaces.Contains(value) { + config.Namespaces.Add(value, "") + } + if v == "net" { + config.Networks = []*configs.Network{ + { + Type: "loopback", + Address: "127.0.0.1/0", + Gateway: "localhost", + }, + } + } + default: + config.Namespaces.Remove(value) + config.Namespaces.Add(value, v) + } + } + if bridge := context.String("veth-bridge"); bridge != "" { + hostName, err := utils.GenerateRandomName("veth", 7) + if err != nil { + logrus.Fatal(err) + } + network := &configs.Network{ + Type: "veth", + Name: "eth0", + Bridge: bridge, + Address: context.String("veth-address"), + Gateway: context.String("veth-gateway"), + Mtu: context.Int("veth-mtu"), + HostInterfaceName: hostName, + } + config.Networks = append(config.Networks, network) } } @@ -155,13 +247,6 @@ func getTemplate() *configs.Config { Flags: defaultMountFlags | syscall.MS_RDONLY, }, }, - Networks: []*configs.Network{ - { - Type: "loopback", - Address: "127.0.0.1/0", - Gateway: "localhost", - }, - }, Rlimits: []configs.Rlimit{ { Type: syscall.RLIMIT_NOFILE, diff --git a/nsinit/exec.go b/nsinit/exec.go index b499d66ea..b8d210012 100644 --- a/nsinit/exec.go +++ b/nsinit/exec.go @@ -5,7 +5,6 @@ import ( "os/signal" "syscall" - log "github.com/Sirupsen/logrus" "github.com/codegangsta/cli" "github.com/docker/libcontainer" "github.com/docker/libcontainer/utils" @@ -27,12 +26,12 @@ var execCommand = cli.Command{ cli.StringFlag{Name: "config", Value: "container.json", Usage: "path to the configuration file"}, cli.BoolFlag{Name: "create", Usage: "create the container's configuration on the fly with arguments"}, cli.StringFlag{Name: "user,u", Value: "root", Usage: "set the user, uid, and/or gid for the process"}, + cli.StringFlag{Name: "cwd", Value: "", Usage: "set the current working dir"}, cli.StringSliceFlag{Name: "env", Value: standardEnvironment, Usage: "set environment variables for the process"}, }, createFlags...), } func execAction(context *cli.Context) { - entry := log.WithField("parent", "nsinit") factory, err := loadFactory(context) if err != nil { fatal(err) @@ -44,7 +43,6 @@ func execAction(context *cli.Context) { created := false container, err := factory.Load(context.String("id")) if err != nil { - entry.Debug("creating container") config, err := loadConfig(context) if err != nil { tty.Close() @@ -53,7 +51,6 @@ func execAction(context *cli.Context) { if tty.console != nil { config.Console = tty.console.Path() } - created = true if container, err = factory.Create(context.String("id"), config); err != nil { tty.Close() @@ -65,11 +62,14 @@ func execAction(context *cli.Context) { Args: context.Args(), Env: context.StringSlice("env"), User: context.String("user"), + Cwd: context.String("cwd"), Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr, } - tty.attach(process) + if err := tty.attach(process); err != nil { + fatal(err) + } pid, err := container.Start(process) if err != nil { tty.Close() diff --git a/nsinit/init.go b/nsinit/init.go index 96957824b..7b2cf1935 100644 --- a/nsinit/init.go +++ b/nsinit/init.go @@ -18,10 +18,10 @@ var initCommand = cli.Command{ runtime.LockOSThread() factory, err := libcontainer.New("") if err != nil { - log.Fatal(err) + fatal(err) } if err := factory.StartInitialization(3); err != nil { - log.Fatal(err) + fatal(err) } panic("This line should never been executed") }, diff --git a/nsinit/tty.go b/nsinit/tty.go index f1e593740..d0d343756 100644 --- a/nsinit/tty.go +++ b/nsinit/tty.go @@ -11,19 +11,13 @@ import ( func newTty(context *cli.Context) (*tty, error) { if context.Bool("tty") { - console, err := libcontainer.NewConsole() - if err != nil { - return nil, err - } - go io.Copy(console, os.Stdin) - go io.Copy(os.Stdout, console) - state, err := term.SetRawTerminal(os.Stdin.Fd()) + rootid := context.Int("userns-root-uid") + console, err := libcontainer.NewConsole(rootid, rootid) if err != nil { return nil, err } return &tty{ console: console, - state: state, }, nil } return &tty{}, nil @@ -44,12 +38,20 @@ func (t *tty) Close() error { return nil } -func (t *tty) attach(process *libcontainer.Process) { +func (t *tty) attach(process *libcontainer.Process) error { if t.console != nil { + go io.Copy(t.console, os.Stdin) + go io.Copy(os.Stdout, t.console) + state, err := term.SetRawTerminal(os.Stdin.Fd()) + if err != nil { + return err + } + t.state = state process.Stderr = nil process.Stdout = nil process.Stdin = nil } + return nil } func (t *tty) resize() error { From afa8443118347a1f909941aec2732039d28a9034 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 17 Feb 2015 21:50:43 -0800 Subject: [PATCH 2/2] Remove userns sidecar process Move the network setup back into the standard init even for user namespaces now that mounts are fully supported and working. Signed-off-by: Michael Crosby --- error.go | 8 ---- generic_error.go | 11 +++-- linux_container.go | 1 - linux_factory.go | 4 +- linux_init.go | 14 +----- linux_process.go | 40 +--------------- linux_rootfs.go | 27 +++++------ linux_userns_init.go | 91 ------------------------------------ linux_userns_sidecar_init.go | 23 --------- nsinit/config.go | 9 ++-- nsinit/exec.go | 15 +++--- nsinit/tty.go | 5 +- 12 files changed, 40 insertions(+), 208 deletions(-) delete mode 100644 linux_userns_init.go delete mode 100644 linux_userns_sidecar_init.go diff --git a/error.go b/error.go index 85b0dcaf0..37e99366f 100644 --- a/error.go +++ b/error.go @@ -57,11 +57,3 @@ type Error interface { // Returns the error code for this error. Code() ErrorCode } - -type initError struct { - Message string `json:"message,omitempty"` -} - -func (i initError) Error() string { - return i.Message -} diff --git a/generic_error.go b/generic_error.go index ff614ee66..05ab0a9d3 100644 --- a/generic_error.go +++ b/generic_error.go @@ -11,8 +11,8 @@ import ( var errorTemplate = template.Must(template.New("error").Parse(`Timestamp: {{.Timestamp}} Code: {{.ECode}} -{{if .Err }} -Message: {{.Err.Error}} +{{if .Message }} +Message: {{.Message}} {{end}} Frames:{{range $i, $frame := .Stack.Frames}} --- @@ -28,6 +28,7 @@ func newGenericError(err error, c ErrorCode) Error { return &genericError{ Timestamp: time.Now(), Err: err, + Message: err.Error(), ECode: c, Stack: stacktrace.Capture(1), } @@ -41,6 +42,7 @@ func newSystemError(err error) Error { Timestamp: time.Now(), Err: err, ECode: SystemError, + Message: err.Error(), Stack: stacktrace.Capture(1), } } @@ -48,12 +50,13 @@ func newSystemError(err error) Error { type genericError struct { Timestamp time.Time ECode ErrorCode - Err error + Err error `json:"-"` + Message string Stack stacktrace.Stacktrace } func (e *genericError) Error() string { - return fmt.Sprintf("[%d] %s: %s", e.ECode, e.ECode, e.Err) + return fmt.Sprintf("[%d] %s: %s", e.ECode, e.ECode, e.Message) } func (e *genericError) Code() ErrorCode { diff --git a/linux_container.go b/linux_container.go index 01e34769a..52d9af87d 100644 --- a/linux_container.go +++ b/linux_container.go @@ -147,7 +147,6 @@ func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, c if cmd.SysProcAttr.Credential == nil { cmd.SysProcAttr.Credential = &syscall.Credential{} } - t = "_LIBCONTAINER_INITTYPE=userns" } cmd.Env = append(cmd.Env, t) cmd.SysProcAttr.Cloneflags = cloneFlags diff --git a/linux_factory.go b/linux_factory.go index 879ad4274..d0fca3b41 100644 --- a/linux_factory.go +++ b/linux_factory.go @@ -166,9 +166,7 @@ func (l *LinuxFactory) StartInitialization(pipefd uintptr) (err error) { // ensure that any data sent from the parent is consumed so it doesn't // receive ECONNRESET when the child writes to the pipe. ioutil.ReadAll(pipe) - if err := json.NewEncoder(pipe).Encode(initError{ - Message: err.Error(), - }); err != nil { + if err := json.NewEncoder(pipe).Encode(newSystemError(err)); err != nil { panic(err) } } diff --git a/linux_init.go b/linux_init.go index 4755edb08..37891102f 100644 --- a/linux_init.go +++ b/linux_init.go @@ -21,10 +21,8 @@ import ( type initType string const ( - initSetns initType = "setns" - initStandard initType = "standard" - initUserns initType = "userns" - initUsernsSetup initType = "userns_setup" + initSetns initType = "setns" + initStandard initType = "standard" ) type pid struct { @@ -67,14 +65,6 @@ func newContainerInit(t initType, pipe *os.File) (initer, error) { return &linuxSetnsInit{ config: config, }, nil - case initUserns: - return &linuxUsernsInit{ - config: config, - }, nil - case initUsernsSetup: - return &linuxUsernsSideCar{ - config: config, - }, nil case initStandard: return &linuxStandardInit{ config: config, diff --git a/linux_process.go b/linux_process.go index 83addf240..0fd33b92d 100644 --- a/linux_process.go +++ b/linux_process.go @@ -4,13 +4,11 @@ package libcontainer import ( "encoding/json" - "fmt" "io" "os" "os/exec" "syscall" - log "github.com/Sirupsen/logrus" "github.com/docker/libcontainer/cgroups" "github.com/docker/libcontainer/system" ) @@ -145,28 +143,12 @@ func (p *initProcess) start() error { if err := p.createNetworkInterfaces(); err != nil { return newSystemError(err) } - // Start the setup process to setup the init process - if p.cmd.SysProcAttr.Cloneflags&syscall.CLONE_NEWUSER != 0 { - parent, err := p.newUsernsSetupProcess() - if err != nil { - return newSystemError(err) - } - if err := parent.start(); err != nil { - if err := parent.terminate(); err != nil { - log.Warn(err) - } - return err - } - if _, err := parent.wait(); err != nil { - return newSystemError(err) - } - } if err := p.sendConfig(); err != nil { return newSystemError(err) } // wait for the child process to fully complete and receive an error message // if one was encoutered - var ierr *initError + var ierr *genericError if err := json.NewDecoder(p.parentPipe).Decode(&ierr); err != nil && err != io.EOF { return newSystemError(err) } @@ -229,26 +211,6 @@ func (p *initProcess) createNetworkInterfaces() error { return nil } -func (p *initProcess) newUsernsSetupProcess() (parentProcess, error) { - parentPipe, childPipe, err := newPipe() - if err != nil { - return nil, newSystemError(err) - } - cmd := exec.Command(p.cmd.Args[0], p.cmd.Args[1:]...) - cmd.ExtraFiles = []*os.File{childPipe} - cmd.Dir = p.cmd.Dir - cmd.Env = append(cmd.Env, - fmt.Sprintf("_LIBCONTAINER_INITPID=%d", p.pid()), - fmt.Sprintf("_LIBCONTAINER_INITTYPE=userns_setup"), - ) - return &setnsProcess{ - cmd: cmd, - childPipe: childPipe, - parentPipe: parentPipe, - config: p.config, - }, nil -} - func (p *initProcess) signal(s os.Signal) error { return p.cmd.Process.Signal(s) } diff --git a/linux_rootfs.go b/linux_rootfs.go index 74c711492..7aba21bdf 100644 --- a/linux_rootfs.go +++ b/linux_rootfs.go @@ -199,30 +199,27 @@ func createDevices(config *configs.Config) error { // Creates the device node in the rootfs of the container. func createDeviceNode(rootfs string, node *configs.Device) error { - var ( - dest = filepath.Join(rootfs, node.Path) - parent = filepath.Dir(dest) - ) - if err := os.MkdirAll(parent, 0755); err != nil { + dest := filepath.Join(rootfs, node.Path) + if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil { return err } if err := mknodDevice(dest, node); err != nil { if os.IsExist(err) { return nil } + if err != syscall.EPERM { + return err + } // containers running in a user namespace are not allowed to mknod // devices so we can just bind mount it from the host. - if err == syscall.EPERM { - f, err := os.Create(dest) - if err != nil { - if os.IsExist(err) { - return nil - } - return err - } - f.Close() - return syscall.Mount(node.Path, dest, "bind", syscall.MS_BIND, "") + f, err := os.Create(dest) + if err != nil && !os.IsExist(err) { + return err } + if f != nil { + f.Close() + } + return syscall.Mount(node.Path, dest, "bind", syscall.MS_BIND, "") } return nil } diff --git a/linux_userns_init.go b/linux_userns_init.go deleted file mode 100644 index 7f5806fe5..000000000 --- a/linux_userns_init.go +++ /dev/null @@ -1,91 +0,0 @@ -// +build linux - -package libcontainer - -import ( - "syscall" - - "github.com/docker/libcontainer/apparmor" - "github.com/docker/libcontainer/configs" - "github.com/docker/libcontainer/label" - "github.com/docker/libcontainer/system" -) - -type linuxUsernsInit struct { - config *initConfig -} - -func (l *linuxUsernsInit) Init() error { - // join any namespaces via a path to the namespace fd if provided - if err := joinExistingNamespaces(l.config.Config.Namespaces); err != nil { - return newSystemError(err) - } - consolePath := l.config.Config.Console - if consolePath != "" { - // We use the containerConsolePath here, because the console has already been - // setup by the side car process for the user namespace scenario. - console := newConsoleFromPath(consolePath) - if err := console.dupStdio(); err != nil { - return newSystemError(err) - } - } - if _, err := syscall.Setsid(); err != nil { - return newSystemError(err) - } - if consolePath != "" { - if err := system.Setctty(); err != nil { - return newSystemError(err) - } - } - if l.config.Cwd == "" { - l.config.Cwd = "/" - } - if err := setupRlimits(l.config.Config); err != nil { - return newSystemError(err) - } - // InitializeMountNamespace() can be executed only for a new mount namespace - if l.config.Config.Namespaces.Contains(configs.NEWNS) { - if err := setupRootfs(l.config.Config); err != nil { - return newSystemError(err) - } - } - if hostname := l.config.Config.Hostname; hostname != "" { - if err := syscall.Sethostname([]byte(hostname)); err != nil { - return newSystemError(err) - } - } - if err := apparmor.ApplyProfile(l.config.Config.AppArmorProfile); err != nil { - return newSystemError(err) - } - if err := label.SetProcessLabel(l.config.Config.ProcessLabel); err != nil { - return newSystemError(err) - } - for _, path := range l.config.Config.ReadonlyPaths { - if err := remountReadonly(path); err != nil { - return newSystemError(err) - } - } - for _, path := range l.config.Config.MaskPaths { - if err := maskFile(path); err != nil { - return newSystemError(err) - } - } - pdeath, err := system.GetParentDeathSignal() - if err != nil { - return newSystemError(err) - } - if err := finalizeNamespace(l.config); err != nil { - return newSystemError(err) - } - // finalizeNamespace can change user/group which clears the parent death - // signal, so we restore it here. - if err := pdeath.Restore(); err != nil { - return newSystemError(err) - } - // Signal self if parent is already dead. Does nothing if running in a new - // PID namespace, as Getppid will always return 0. - if syscall.Getppid() == 1 { - return syscall.Kill(syscall.Getpid(), syscall.SIGKILL) - } - return system.Execv(l.config.Args[0], l.config.Args[0:], l.config.Env) -} diff --git a/linux_userns_sidecar_init.go b/linux_userns_sidecar_init.go deleted file mode 100644 index 119abfabc..000000000 --- a/linux_userns_sidecar_init.go +++ /dev/null @@ -1,23 +0,0 @@ -// +build linux - -package libcontainer - -// linuxUsernsSideCar is run to setup mounts and networking related operations -// for a user namespace enabled process as a user namespace root doesn't -// have permissions to perform these operations. -// The setup process joins all the namespaces of user namespace enabled init -// except the user namespace, so it run as root in the root user namespace -// to perform these operations. -type linuxUsernsSideCar struct { - config *initConfig -} - -func (l *linuxUsernsSideCar) Init() error { - if err := setupNetwork(l.config); err != nil { - return err - } - if err := setupRoute(l.config.Config); err != nil { - return err - } - return nil -} diff --git a/nsinit/config.go b/nsinit/config.go index 7dbc8a78e..5663f5560 100644 --- a/nsinit/config.go +++ b/nsinit/config.go @@ -33,6 +33,7 @@ var createFlags = []cli.Flag{ cli.StringFlag{Name: "mount-label", Usage: "set the mount label"}, cli.StringFlag{Name: "rootfs", Usage: "set the rootfs"}, cli.IntFlag{Name: "userns-root-uid", Usage: "set the user namespace root uid"}, + cli.StringFlag{Name: "hostname", Value: "nsinit", Usage: "hostname value for the container"}, cli.StringFlag{Name: "net", Value: "", Usage: "network namespace"}, cli.StringFlag{Name: "ipc", Value: "", Usage: "ipc namespace"}, cli.StringFlag{Name: "pid", Value: "", Usage: "pid namespace"}, @@ -149,7 +150,7 @@ func modify(config *configs.Config, context *cli.Context) { if !config.Namespaces.Contains(value) { config.Namespaces.Add(value, "") } - if v == "net" { + if flag == "net" { config.Networks = []*configs.Network{ { Type: "loopback", @@ -158,6 +159,9 @@ func modify(config *configs.Config, context *cli.Context) { }, } } + if flag == "uts" { + config.Hostname = context.String("hostname") + } default: config.Namespaces.Remove(value) config.Namespaces.Add(value, v) @@ -218,8 +222,7 @@ func getTemplate() *configs.Config { AllowAllDevices: false, AllowedDevices: configs.DefaultAllowedDevices, }, - Devices: configs.DefaultAutoCreatedDevices, - Hostname: "nsinit", + Devices: configs.DefaultAutoCreatedDevices, MaskPaths: []string{ "/proc/kcore", }, diff --git a/nsinit/exec.go b/nsinit/exec.go index b8d210012..52f251440 100644 --- a/nsinit/exec.go +++ b/nsinit/exec.go @@ -36,18 +36,21 @@ func execAction(context *cli.Context) { if err != nil { fatal(err) } - tty, err := newTty(context) + config, err := loadConfig(context) + if err != nil { + fatal(err) + } + rootuid, err := config.HostUID() + if err != nil { + fatal(err) + } + tty, err := newTty(context, rootuid) if err != nil { fatal(err) } created := false container, err := factory.Load(context.String("id")) if err != nil { - config, err := loadConfig(context) - if err != nil { - tty.Close() - fatal(err) - } if tty.console != nil { config.Console = tty.console.Path() } diff --git a/nsinit/tty.go b/nsinit/tty.go index d0d343756..5d417b98d 100644 --- a/nsinit/tty.go +++ b/nsinit/tty.go @@ -9,10 +9,9 @@ import ( "github.com/docker/libcontainer" ) -func newTty(context *cli.Context) (*tty, error) { +func newTty(context *cli.Context, rootuid int) (*tty, error) { if context.Bool("tty") { - rootid := context.Int("userns-root-uid") - console, err := libcontainer.NewConsole(rootid, rootid) + console, err := libcontainer.NewConsole(rootuid, rootuid) if err != nil { return nil, err }