Skip to content

Commit

Permalink
Merge pull request #1688 from AkihiroSuda/unshare-m-r
Browse files Browse the repository at this point in the history
main: support rootless mode in userns
  • Loading branch information
crosbymichael authored May 29, 2018
2 parents dd67ab1 + c938157 commit 0e56164
Show file tree
Hide file tree
Showing 12 changed files with 302 additions and 37 deletions.
6 changes: 5 additions & 1 deletion checkpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ checkpointed.`,
return err
}
// XXX: Currently this is untested with rootless containers.
if isRootless() {
rootless, err := isRootless(context)
if err != nil {
return err
}
if rootless {
return fmt.Errorf("runc checkpoint requires root")
}

Expand Down
16 changes: 4 additions & 12 deletions libcontainer/container_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import (

"github.com/golang/protobuf/proto"
"github.com/sirupsen/logrus"
"github.com/syndtr/gocapability/capability"
"github.com/vishvananda/netlink/nl"
"golang.org/x/sys/unix"
)
Expand Down Expand Up @@ -1798,17 +1797,10 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Na
})
}
if requiresRootOrMappingTool(c.config) {
// check if we have CAP_SETGID to setgroup properly
pid, err := capability.NewPid(0)
if err != nil {
return nil, err
}
if !pid.Get(capability.EFFECTIVE, capability.CAP_SETGID) {
r.AddData(&Boolmsg{
Type: SetgroupAttr,
Value: true,
})
}
r.AddData(&Boolmsg{
Type: SetgroupAttr,
Value: true,
})
}
}
}
Expand Down
38 changes: 23 additions & 15 deletions libcontainer/system/linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
package system

import (
"bufio"
"fmt"
"os"
"os/exec"
"syscall" // only for exec
"unsafe"

"github.com/opencontainers/runc/libcontainer/user"
"golang.org/x/sys/unix"
)

Expand Down Expand Up @@ -102,34 +101,43 @@ func Setctty() error {
}

// RunningInUserNS detects whether we are currently running in a user namespace.
// Copied from github.com/lxc/lxd/shared/util.go
// Originally copied from github.com/lxc/lxd/shared/util.go
func RunningInUserNS() bool {
file, err := os.Open("/proc/self/uid_map")
uidmap, err := user.CurrentProcessUIDMap()
if err != nil {
// This kernel-provided file only exists if user namespaces are supported
return false
}
defer file.Close()

buf := bufio.NewReader(file)
l, _, err := buf.ReadLine()
if err != nil {
return false
}
return UIDMapInUserNS(uidmap)
}

line := string(l)
var a, b, c int64
fmt.Sscanf(line, "%d %d %d", &a, &b, &c)
func UIDMapInUserNS(uidmap []user.IDMap) bool {
/*
* We assume we are in the initial user namespace if we have a full
* range - 4294967295 uids starting at uid 0.
*/
if a == 0 && b == 0 && c == 4294967295 {
if len(uidmap) == 1 && uidmap[0].ID == 0 && uidmap[0].ParentID == 0 && uidmap[0].Count == 4294967295 {
return false
}
return true
}

// GetParentNSeuid returns the euid within the parent user namespace
func GetParentNSeuid() int {
euid := os.Geteuid()
uidmap, err := user.CurrentProcessUIDMap()
if err != nil {
// This kernel-provided file only exists if user namespaces are supported
return euid
}
for _, um := range uidmap {
if um.ID <= euid && euid <= um.ID+um.Count-1 {
return um.ParentID + euid - um.ID
}
}
return euid
}

// SetSubreaper sets the value i as the subreaper setting for the calling process
func SetSubreaper(i int) error {
return unix.Prctl(PR_SET_CHILD_SUBREAPER, uintptr(i), 0, 0, 0)
Expand Down
45 changes: 45 additions & 0 deletions libcontainer/system/linux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// +build linux

package system

import (
"strings"
"testing"

"github.com/opencontainers/runc/libcontainer/user"
)

func TestUIDMapInUserNS(t *testing.T) {
cases := []struct {
s string
expected bool
}{
{
s: " 0 0 4294967295\n",
expected: false,
},
{
s: " 0 0 1\n",
expected: true,
},
{
s: " 0 1001 1\n 1 231072 65536\n",
expected: true,
},
{
// file exist but empty (the initial state when userns is created. see man 7 user_namespaces)
s: "",
expected: true,
},
}
for _, c := range cases {
uidmap, err := user.ParseIDMap(strings.NewReader(c.s))
if err != nil {
t.Fatal(err)
}
actual := UIDMapInUserNS(uidmap)
if c.expected != actual {
t.Fatalf("expected %v, got %v for %q", c.expected, actual, c.s)
}
}
}
18 changes: 18 additions & 0 deletions libcontainer/system/unsupported.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,26 @@

package system

import (
"os"

"github.com/opencontainers/runc/libcontainer/user"
)

// RunningInUserNS is a stub for non-Linux systems
// Always returns false
func RunningInUserNS() bool {
return false
}

// UIDMapInUserNS is a stub for non-Linux systems
// Always returns false
func UIDMapInUserNS(uidmap []user.IDMap) bool {
return false
}

// GetParentNSeuid returns the euid within the parent user namespace
// Always returns os.Geteuid on non-linux
func GetParentNSeuid() int {
return os.Geteuid()
}
26 changes: 26 additions & 0 deletions libcontainer/user/lookup_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,29 @@ func CurrentUser() (User, error) {
func CurrentGroup() (Group, error) {
return LookupGid(unix.Getgid())
}

func CurrentUserSubUIDs() ([]SubID, error) {
u, err := CurrentUser()
if err != nil {
return nil, err
}
return ParseSubIDFileFilter("/etc/subuid",
func(entry SubID) bool { return entry.Name == u.Name })
}

func CurrentGroupSubGIDs() ([]SubID, error) {
g, err := CurrentGroup()
if err != nil {
return nil, err
}
return ParseSubIDFileFilter("/etc/subgid",
func(entry SubID) bool { return entry.Name == g.Name })
}

func CurrentProcessUIDMap() ([]IDMap, error) {
return ParseIDMapFile("/proc/self/uid_map")
}

func CurrentProcessGIDMap() ([]IDMap, error) {
return ParseIDMapFile("/proc/self/gid_map")
}
129 changes: 127 additions & 2 deletions libcontainer/user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,29 @@ func groupFromOS(g *user.Group) (Group, error) {
return newGroup, nil
}

// SubID represents an entry in /etc/sub{u,g}id
type SubID struct {
Name string
SubID int
Count int
}

// IDMap represents an entry in /proc/PID/{u,g}id_map
type IDMap struct {
ID int
ParentID int
Count int
}

func parseLine(line string, v ...interface{}) {
if line == "" {
parseParts(strings.Split(line, ":"), v...)
}

func parseParts(parts []string, v ...interface{}) {
if len(parts) == 0 {
return
}

parts := strings.Split(line, ":")
for i, p := range parts {
// Ignore cases where we don't have enough fields to populate the arguments.
// Some configuration files like to misbehave.
Expand Down Expand Up @@ -479,3 +496,111 @@ func GetAdditionalGroupsPath(additionalGroups []string, groupPath string) ([]int
}
return GetAdditionalGroups(additionalGroups, group)
}

func ParseSubIDFile(path string) ([]SubID, error) {
subid, err := os.Open(path)
if err != nil {
return nil, err
}
defer subid.Close()
return ParseSubID(subid)
}

func ParseSubID(subid io.Reader) ([]SubID, error) {
return ParseSubIDFilter(subid, nil)
}

func ParseSubIDFileFilter(path string, filter func(SubID) bool) ([]SubID, error) {
subid, err := os.Open(path)
if err != nil {
return nil, err
}
defer subid.Close()
return ParseSubIDFilter(subid, filter)
}

func ParseSubIDFilter(r io.Reader, filter func(SubID) bool) ([]SubID, error) {
if r == nil {
return nil, fmt.Errorf("nil source for subid-formatted data")
}

var (
s = bufio.NewScanner(r)
out = []SubID{}
)

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

line := strings.TrimSpace(s.Text())
if line == "" {
continue
}

// see: man 5 subuid
p := SubID{}
parseLine(line, &p.Name, &p.SubID, &p.Count)

if filter == nil || filter(p) {
out = append(out, p)
}
}

return out, nil
}

func ParseIDMapFile(path string) ([]IDMap, error) {
r, err := os.Open(path)
if err != nil {
return nil, err
}
defer r.Close()
return ParseIDMap(r)
}

func ParseIDMap(r io.Reader) ([]IDMap, error) {
return ParseIDMapFilter(r, nil)
}

func ParseIDMapFileFilter(path string, filter func(IDMap) bool) ([]IDMap, error) {
r, err := os.Open(path)
if err != nil {
return nil, err
}
defer r.Close()
return ParseIDMapFilter(r, filter)
}

func ParseIDMapFilter(r io.Reader, filter func(IDMap) bool) ([]IDMap, error) {
if r == nil {
return nil, fmt.Errorf("nil source for idmap-formatted data")
}

var (
s = bufio.NewScanner(r)
out = []IDMap{}
)

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

line := strings.TrimSpace(s.Text())
if line == "" {
continue
}

// see: man 7 user_namespaces
p := IDMap{}
parseParts(strings.Fields(line), &p.ID, &p.ParentID, &p.Count)

if filter == nil || filter(p) {
out = append(out, p)
}
}

return out, nil
}
11 changes: 10 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ func main() {
app.Version = strings.Join(v, "\n")

root := "/run/runc"
if os.Geteuid() != 0 {
rootless, err := isRootless(nil)
if err != nil {
fatal(err)
}
if rootless {
runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
if runtimeDir != "" {
root = runtimeDir + "/runc"
Expand Down Expand Up @@ -108,6 +112,11 @@ func main() {
Name: "systemd-cgroup",
Usage: "enable systemd cgroup support, expects cgroupsPath to be of form \"slice:prefix:name\" for e.g. \"system.slice:runc:434234\"",
},
cli.StringFlag{
Name: "rootless",
Value: "auto",
Usage: "enable rootless mode ('true', 'false', or 'auto')",
},
}
app.Commands = []cli.Command{
checkpointCommand,
Expand Down
6 changes: 5 additions & 1 deletion ps.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ var psCommand = cli.Command{
return err
}
// XXX: Currently not supported with rootless containers.
if isRootless() {
rootless, err := isRootless(context)
if err != nil {
return err
}
if rootless {
return fmt.Errorf("runc ps requires root")
}

Expand Down
Loading

0 comments on commit 0e56164

Please sign in to comment.