Skip to content

Commit

Permalink
Merge pull request #122 from AkihiroSuda/a
Browse files Browse the repository at this point in the history
print warning when preconditions are not satisfied + release v0.9.1
  • Loading branch information
AkihiroSuda authored Mar 7, 2020
2 parents da1dfff + 55e62bb commit 304c2fa
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 2 deletions.
1 change: 1 addition & 0 deletions cmd/rootlesskit/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ func createParentOpt(clicontext *cli.Context, pipeFDEnvKey, stateDirEnvKey, pare
CreatePIDNS: clicontext.Bool("pidns"),
ParentEUIDEnvKey: parentEUIDEnvKey,
ParentEGIDEnvKey: parentEGIDEnvKey,
Propagation: clicontext.String("propagation"),
}
opt.StateDir = clicontext.String("state-dir")
if opt.StateDir == "" {
Expand Down
7 changes: 7 additions & 0 deletions pkg/parent/mount/mount.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Package mount was forked from /~https://github.com/opencontainers/runc/tree/fc5759cf4fcf3f9c77c5973a24d37188dbcc92ee/libcontainer/mount
package mount

// GetMounts retrieves a list of mounts for the current running process.
func GetMounts() ([]*Info, error) {
return parseMountTable()
}
82 changes: 82 additions & 0 deletions pkg/parent/mount/mount_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// +build linux

package mount

import (
"bufio"
"fmt"
"io"
"os"
"strings"
)

const (
/* 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
(1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11)
(1) mount ID: unique identifier of the mount (may be reused after umount)
(2) parent ID: ID of parent (or of self for the top of the mount tree)
(3) major:minor: value of st_dev for files on filesystem
(4) root: root of the mount within the filesystem
(5) mount point: mount point relative to the process's root
(6) mount options: per mount options
(7) optional fields: zero or more fields of the form "tag[:value]"
(8) separator: marks the end of the optional fields
(9) filesystem type: name of filesystem of the form "type[.subtype]"
(10) mount source: filesystem specific information or "none"
(11) super options: per super block options*/
mountinfoFormat = "%d %d %d:%d %s %s %s %s"
)

// Parse /proc/self/mountinfo because comparing Dev and ino does not work from
// bind mounts
func parseMountTable() ([]*Info, error) {
f, err := os.Open("/proc/self/mountinfo")
if err != nil {
return nil, err
}
defer f.Close()

return parseInfoFile(f)
}

func parseInfoFile(r io.Reader) ([]*Info, error) {
var (
s = bufio.NewScanner(r)
out = []*Info{}
)

for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}

var (
p = &Info{}
text = s.Text()
optionalFields string
)

if _, err := fmt.Sscanf(text, mountinfoFormat,
&p.ID, &p.Parent, &p.Major, &p.Minor,
&p.Root, &p.Mountpoint, &p.Opts, &optionalFields); err != nil {
return nil, fmt.Errorf("Scanning '%s' failed: %s", text, err)
}
// Safe as mountinfo encodes mountpoints with spaces as \040.
index := strings.Index(text, " - ")
postSeparatorFields := strings.Fields(text[index+3:])
if len(postSeparatorFields) < 3 {
return nil, fmt.Errorf("Error found less than 3 fields post '-' in %q", text)
}

if optionalFields != "-" {
p.Optional = optionalFields
}

p.Fstype = postSeparatorFields[0]
p.Source = postSeparatorFields[1]
p.VfsOpts = strings.Join(postSeparatorFields[2:], " ")
out = append(out, p)
}
return out, nil
}
40 changes: 40 additions & 0 deletions pkg/parent/mount/mountinfo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package mount

// Info reveals information about a particular mounted filesystem. This
// struct is populated from the content in the /proc/<pid>/mountinfo file.
type Info struct {
// ID is a unique identifier of the mount (may be reused after umount).
ID int

// Parent indicates the ID of the mount parent (or of self for the top of the
// mount tree).
Parent int

// Major indicates one half of the device ID which identifies the device class.
Major int

// Minor indicates one half of the device ID which identifies a specific
// instance of device.
Minor int

// Root of the mount within the filesystem.
Root string

// Mountpoint indicates the mount point relative to the process's root.
Mountpoint string

// Opts represents mount-specific options.
Opts string

// Optional represents optional fields.
Optional string

// Fstype indicates the type of filesystem, such as EXT3.
Fstype string

// Source indicates filesystem specific information or "none".
Source string

// VfsOpts represents per super block options.
VfsOpts string
}
19 changes: 18 additions & 1 deletion pkg/parent/parent.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type Opt struct {
CreatePIDNS bool
ParentEUIDEnvKey string // optional env key to propagate geteuid() value
ParentEGIDEnvKey string // optional env key to propagate getegid() value
Propagation string
}

// Documented state files. Undocumented ones are subject to change.
Expand All @@ -45,7 +46,7 @@ const (
StateFileAPISock = "api.sock" // REST API Socket
)

func Parent(opt Opt) error {
func checkPreflight(opt Opt) error {
if opt.PipeFDEnvKey == "" {
return errors.New("pipe FD env key is not set")
}
Expand All @@ -58,6 +59,22 @@ func Parent(opt Opt) error {
if stat, err := os.Stat(opt.StateDir); err != nil || !stat.IsDir() {
return errors.Wrap(err, "state dir is inaccessible")
}

if os.Geteuid() == 0 {
logrus.Warn("Running RootlessKit as the root user is unsupported.")
}

warnSysctl()

// invalid propagation doesn't result in an error
warnPropagation(opt.Propagation)
return nil
}

func Parent(opt Opt) error {
if err := checkPreflight(opt); err != nil {
return err
}
lockPath := filepath.Join(opt.StateDir, StateFileLock)
lock := flock.NewFlock(lockPath)
locked, err := lock.TryLock()
Expand Down
69 changes: 69 additions & 0 deletions pkg/parent/warn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package parent

import (
"io/ioutil"
"strconv"
"strings"

"github.com/sirupsen/logrus"

"github.com/rootless-containers/rootlesskit/pkg/parent/mount"
)

func warnPropagation(propagation string) {
mounts, err := mount.GetMounts()
if err != nil {
logrus.WithError(err).Warn("Failed to parse mountinfo")
return
}
var root *mount.Info
for _, m := range mounts {
if m.Root == "/" && m.Mountpoint == "/" {
root = m
}
}
if root == nil {
logrus.Warn("Failed to parse mountinfo of the root filesystem")
return
}
// 1. When running on a "sane" host, root.Optional is like "shared:1". ("shared" in findmnt(8) output)
// 2. When running inside a container, root.Optional is like "master:363". ("private, slave" in findmnt(8) output)
//
// Setting non-private propagation is supported for 1, unsupported for 2.
if !strings.Contains(propagation, "private") && !strings.Contains(root.Optional, "shared") {
logrus.Warnf("The host root filesystem is mounted as %q. Setting child propagation to %q is not supported.",
root.Optional, propagation)
}
}

// warnSysctl verifies /proc/sys/kernel/unprivileged_userns_clone and /proc/sys/user/max_user_namespaces
func warnSysctl() {
uuc, err := ioutil.ReadFile("/proc/sys/kernel/unprivileged_userns_clone")
// The file exists only on distros with the "add sysctl to disallow unprivileged CLONE_NEWUSER by default" patch.
// (e.g. Debian and Arch)
if err == nil {
s := strings.TrimSpace(string(uuc))
i, err := strconv.ParseInt(s, 10, 64)
if err != nil {
logrus.WithError(err).Warnf("Failed to parse /proc/sys/kernel/unprivileged_userns_clone (%q)", s)
} else if i == 0 {
logrus.Warn("/proc/sys/kernel/unprivileged_userns_clone needs to be set to 1.")
}
}

mun, err := ioutil.ReadFile("/proc/sys/user/max_user_namespaces")
if err == nil {
s := strings.TrimSpace(string(mun))
i, err := strconv.ParseInt(strings.TrimSpace(string(mun)), 10, 64)
if err != nil {
logrus.WithError(err).Warnf("Failed to parse /proc/sys/user/max_user_namespaces (%q)", s)
} else if i == 0 {
logrus.Warn("/proc/sys/user/max_user_namespaces needs to be set to non-zero.")
} else {
threshold := int64(1024)
if i < threshold {
logrus.Warnf("/proc/sys/user/max_user_namespaces=%d may be low. Consider setting to >= %d.", i, threshold)
}
}
}
}
2 changes: 1 addition & 1 deletion pkg/version/version.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package version

const Version = "0.9.0+dev"
const Version = "0.9.1+dev"

0 comments on commit 304c2fa

Please sign in to comment.