Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.

Commit 339edce

Browse files
committed
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 <[email protected]>
1 parent cc42996 commit 339edce

8 files changed

+178
-92
lines changed

linux_console.go

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,9 @@ import (
1212
"github.com/docker/libcontainer/label"
1313
)
1414

15-
const (
16-
containerConsolePath string = "/dev/console"
17-
)
18-
1915
// NewConsole returns an initalized console that can be used within a container by copying bytes
2016
// from the master side to the slave that is attached as the tty for the container's init process.
21-
func NewConsole() (Console, error) {
17+
func NewConsole(uid, gid int) (Console, error) {
2218
master, err := os.OpenFile("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0)
2319
if err != nil {
2420
return nil, err
@@ -30,6 +26,12 @@ func NewConsole() (Console, error) {
3026
if err := unlockpt(master); err != nil {
3127
return nil, err
3228
}
29+
if err := os.Chmod(console, 0600); err != nil {
30+
return nil, err
31+
}
32+
if err := os.Chown(console, uid, gid); err != nil {
33+
return nil, err
34+
}
3335
return &linuxConsole{
3436
slavePath: console,
3537
master: master,
@@ -78,16 +80,10 @@ func (c *linuxConsole) Close() error {
7880
func (c *linuxConsole) mount(rootfs, mountLabel string, uid, gid int) error {
7981
oldMask := syscall.Umask(0000)
8082
defer syscall.Umask(oldMask)
81-
if err := os.Chmod(c.slavePath, 0600); err != nil {
82-
return err
83-
}
84-
if err := os.Chown(c.slavePath, uid, gid); err != nil {
85-
return err
86-
}
8783
if err := label.SetFileLabel(c.slavePath, mountLabel); err != nil {
8884
return err
8985
}
90-
dest := filepath.Join(rootfs, containerConsolePath)
86+
dest := filepath.Join(rootfs, "/dev/console")
9187
f, err := os.Create(dest)
9288
if err != nil && !os.IsExist(err) {
9389
return err

linux_rootfs.go

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -35,52 +35,49 @@ var baseMounts = []*configs.Mount{
3535
Destination: "/dev/pts",
3636
Device: "devpts",
3737
Flags: syscall.MS_NOSUID | syscall.MS_NOEXEC,
38-
Data: "newinstance,ptmxmode=0666,mode=620,gid=5",
38+
Data: "newinstance,ptmxmode=0666,mode=0620,gid=5",
3939
},
4040
}
4141

4242
// setupRootfs sets up the devices, mount points, and filesystems for use inside a
4343
// new mount namespace.
4444
func setupRootfs(config *configs.Config) (err error) {
4545
if err := prepareRoot(config); err != nil {
46-
return err
46+
return newSystemError(err)
4747
}
4848
for _, m := range append(baseMounts, config.Mounts...) {
4949
if err := mount(m, config.Rootfs, config.MountLabel); err != nil {
50-
return err
50+
return newSystemError(err)
5151
}
5252
}
5353
if err := createDevices(config); err != nil {
54-
return err
54+
return newSystemError(err)
5555
}
5656
if err := setupPtmx(config); err != nil {
57-
return err
57+
return newSystemError(err)
5858
}
5959
// stdin, stdout and stderr could be pointing to /dev/null from parent namespace.
60-
// Re-open them inside this namespace.
61-
// FIXME: Need to fix this for user namespaces.
62-
if !config.Namespaces.Contains(configs.NEWUSER) {
63-
if err := reOpenDevNull(config.Rootfs); err != nil {
64-
return err
65-
}
60+
// re-open them inside this namespace.
61+
if err := reOpenDevNull(config.Rootfs); err != nil {
62+
return newSystemError(err)
6663
}
6764
if err := setupDevSymlinks(config.Rootfs); err != nil {
68-
return err
65+
return newSystemError(err)
6966
}
7067
if err := syscall.Chdir(config.Rootfs); err != nil {
71-
return err
68+
return newSystemError(err)
7269
}
7370
if config.NoPivotRoot {
7471
err = msMoveRoot(config.Rootfs)
7572
} else {
7673
err = pivotRoot(config.Rootfs, config.PivotDir)
7774
}
7875
if err != nil {
79-
return err
76+
return newSystemError(err)
8077
}
8178
if config.Readonlyfs {
8279
if err := setReadonly(); err != nil {
83-
return err
80+
return newSystemError(err)
8481
}
8582
}
8683
syscall.Umask(0022)
@@ -209,6 +206,28 @@ func createDeviceNode(rootfs string, node *configs.Device) error {
209206
if err := os.MkdirAll(parent, 0755); err != nil {
210207
return err
211208
}
209+
if err := mknodDevice(dest, node); err != nil {
210+
if os.IsExist(err) {
211+
return nil
212+
}
213+
// containers running in a user namespace are not allowed to mknod
214+
// devices so we can just bind mount it from the host.
215+
if err == syscall.EPERM {
216+
f, err := os.Create(dest)
217+
if err != nil {
218+
if os.IsExist(err) {
219+
return nil
220+
}
221+
return err
222+
}
223+
f.Close()
224+
return syscall.Mount(node.Path, dest, "bind", syscall.MS_BIND, "")
225+
}
226+
}
227+
return nil
228+
}
229+
230+
func mknodDevice(dest string, node *configs.Device) error {
212231
fileMode := node.FileMode
213232
switch node.Type {
214233
case 'c':
@@ -218,13 +237,10 @@ func createDeviceNode(rootfs string, node *configs.Device) error {
218237
default:
219238
return fmt.Errorf("%c is not a valid device type for device %s", node.Type, node.Path)
220239
}
221-
if err := syscall.Mknod(dest, uint32(fileMode), node.Mkdev()); err != nil && !os.IsExist(err) {
222-
return fmt.Errorf("mknod %s %s", node.Path, err)
223-
}
224-
if err := syscall.Chown(dest, int(node.Uid), int(node.Gid)); err != nil {
225-
return fmt.Errorf("chown %s to %d:%d", node.Path, node.Uid, node.Gid)
240+
if err := syscall.Mknod(dest, uint32(fileMode), node.Mkdev()); err != nil {
241+
return err
226242
}
227-
return nil
243+
return syscall.Chown(dest, int(node.Uid), int(node.Gid))
228244
}
229245

230246
func prepareRoot(config *configs.Config) error {
@@ -251,16 +267,8 @@ func setupPtmx(config *configs.Config) error {
251267
return fmt.Errorf("symlink dev ptmx %s", err)
252268
}
253269
if config.Console != "" {
254-
uid, err := config.HostUID()
255-
if err != nil {
256-
return err
257-
}
258-
gid, err := config.HostGID()
259-
if err != nil {
260-
return err
261-
}
262270
console := newConsoleFromPath(config.Console)
263-
return console.mount(config.Rootfs, config.MountLabel, uid, gid)
271+
return console.mount(config.Rootfs, config.MountLabel, 0, 0)
264272
}
265273
return nil
266274
}

linux_userns_init.go

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"syscall"
77

88
"github.com/docker/libcontainer/apparmor"
9+
"github.com/docker/libcontainer/configs"
910
"github.com/docker/libcontainer/label"
1011
"github.com/docker/libcontainer/system"
1112
)
@@ -17,63 +18,69 @@ type linuxUsernsInit struct {
1718
func (l *linuxUsernsInit) Init() error {
1819
// join any namespaces via a path to the namespace fd if provided
1920
if err := joinExistingNamespaces(l.config.Config.Namespaces); err != nil {
20-
return err
21+
return newSystemError(err)
2122
}
2223
consolePath := l.config.Config.Console
2324
if consolePath != "" {
2425
// We use the containerConsolePath here, because the console has already been
2526
// setup by the side car process for the user namespace scenario.
26-
console := newConsoleFromPath(containerConsolePath)
27+
console := newConsoleFromPath(consolePath)
2728
if err := console.dupStdio(); err != nil {
28-
return err
29+
return newSystemError(err)
2930
}
3031
}
3132
if _, err := syscall.Setsid(); err != nil {
32-
return err
33+
return newSystemError(err)
3334
}
3435
if consolePath != "" {
3536
if err := system.Setctty(); err != nil {
36-
return err
37+
return newSystemError(err)
3738
}
3839
}
3940
if l.config.Cwd == "" {
4041
l.config.Cwd = "/"
4142
}
4243
if err := setupRlimits(l.config.Config); err != nil {
43-
return err
44+
return newSystemError(err)
45+
}
46+
// InitializeMountNamespace() can be executed only for a new mount namespace
47+
if l.config.Config.Namespaces.Contains(configs.NEWNS) {
48+
if err := setupRootfs(l.config.Config); err != nil {
49+
return newSystemError(err)
50+
}
4451
}
4552
if hostname := l.config.Config.Hostname; hostname != "" {
4653
if err := syscall.Sethostname([]byte(hostname)); err != nil {
47-
return err
54+
return newSystemError(err)
4855
}
4956
}
5057
if err := apparmor.ApplyProfile(l.config.Config.AppArmorProfile); err != nil {
51-
return err
58+
return newSystemError(err)
5259
}
5360
if err := label.SetProcessLabel(l.config.Config.ProcessLabel); err != nil {
54-
return err
61+
return newSystemError(err)
5562
}
5663
for _, path := range l.config.Config.ReadonlyPaths {
5764
if err := remountReadonly(path); err != nil {
58-
return err
65+
return newSystemError(err)
5966
}
6067
}
6168
for _, path := range l.config.Config.MaskPaths {
6269
if err := maskFile(path); err != nil {
63-
return err
70+
return newSystemError(err)
6471
}
6572
}
6673
pdeath, err := system.GetParentDeathSignal()
6774
if err != nil {
68-
return err
75+
return newSystemError(err)
6976
}
7077
if err := finalizeNamespace(l.config); err != nil {
71-
return err
78+
return newSystemError(err)
7279
}
7380
// finalizeNamespace can change user/group which clears the parent death
7481
// signal, so we restore it here.
7582
if err := pdeath.Restore(); err != nil {
76-
return err
83+
return newSystemError(err)
7784
}
7885
// Signal self if parent is already dead. Does nothing if running in a new
7986
// PID namespace, as Getppid will always return 0.

linux_userns_sidecar_init.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@
22

33
package libcontainer
44

5-
import (
6-
"github.com/docker/libcontainer/configs"
7-
"github.com/docker/libcontainer/label"
8-
)
9-
105
// linuxUsernsSideCar is run to setup mounts and networking related operations
116
// for a user namespace enabled process as a user namespace root doesn't
127
// have permissions to perform these operations.
@@ -24,12 +19,5 @@ func (l *linuxUsernsSideCar) Init() error {
2419
if err := setupRoute(l.config.Config); err != nil {
2520
return err
2621
}
27-
label.Init()
28-
// InitializeMountNamespace() can be executed only for a new mount namespace
29-
if l.config.Config.Namespaces.Contains(configs.NEWNS) {
30-
if err := setupRootfs(l.config.Config); err != nil {
31-
return err
32-
}
33-
}
3422
return nil
3523
}

0 commit comments

Comments
 (0)