Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

main: support rootless mode in userns #1688

Merged
merged 4 commits into from
May 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this broke ARM builds in Moby;

linux_amd64_netgo:/usr/local/go/pkg/linux_amd64_netgo"    -e GOARM=6 "docker-dev:master" hack/make.sh binary
04:21:31 
04:21:32 Removing bundles/
04:21:32 
04:21:32 ---> Making bundle: binary (in bundles/binary)
04:21:32 Building: bundles/binary-daemon/dockerd-18.06.0-ce-dev
04:22:29 # github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/system
04:22:29 vendor/github.com/opencontainers/runc/libcontainer/system/linux.go:119:89: constant 4294967295 overflows int

}
return true
}

// GetParentNSeuid returns the euid within the parent user namespace
func GetParentNSeuid() int {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @brauner had a patch like this for Ubuntu? This code should also check /proc/self/setgroups and ensure that it's set to deny.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As commented above, I think we need to find an alternative check to support nested userns

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