You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

250 lines
5.6 KiB

// Program gowns is a small program to explore and demonstrate using
// Go to Wrap a child in a NameSpace under Linux.
package main
import (
"errors"
"flag"
"fmt"
"log"
"os"
"strings"
"syscall"
"kernel.org/pub/linux/libs/security/libcap/cap"
)
// nsDetail is how we summarize the type of namespace we want to
// enter.
type nsDetail struct {
// uid holds the uid for the base user in this namespace (defaults to getuid).
uid int
// uidMap holds the namespace mapping of uid values.
uidMap []syscall.SysProcIDMap
// gid holds the gid for the base user in this namespace (defaults to getgid).
gid int
// uidMap holds the namespace mapping of gid values.
gidMap []syscall.SysProcIDMap
}
var (
baseID = flag.Int("base", -1, "base id for uids and gids (-1 = invoker's uid)")
uid = flag.Int("uid", -1, "uid of the hosting user")
gid = flag.Int("gid", -1, "gid of the hosting user")
iab = flag.String("iab", "", "IAB string for inheritable capabilities")
mode = flag.String("mode", "", "force a libcap mode (capsh --modes for list)")
ns = flag.Bool("ns", false, "enable user namespace features")
uids = flag.String("uids", "", "comma separated UID ranges to map contiguously (req. CAP_SETUID)")
gids = flag.String("gids", "", "comma separated GID ranges to map contiguously (req. CAP_SETGID)")
shell = flag.String("shell", "/bin/bash", "shell to be launched")
debug = flag.Bool("verbose", false, "more verbose output")
)
// r holds a base and count for a contiguous range.
type r struct {
base, count int
}
// ranges unpacks numerical ranges.
func ranges(s string) []r {
if s == "" {
return nil
}
var rs []r
for _, n := range strings.Split(s, ",") {
var base, upper int
if _, err := fmt.Sscanf(n, "%d-%d", &base, &upper); err == nil {
if upper < base {
log.Fatalf("invalid range: [%d-%d]", base, upper)
}
rs = append(rs, r{
base: base,
count: 1 + upper - base,
})
} else if _, err := fmt.Sscanf(n, "%d", &base); err == nil {
rs = append(rs, r{
base: base,
count: 1,
})
} else {
log.Fatalf("unable to parse range [%s]", n)
}
}
return rs
}
// restart launches the program again with the remaining arguments.
func restart() {
log.Fatalf("failed to restart: flags: %q %q", os.Args[0], flag.Args()[1:])
}
// errUnableToSetup is how nsSetup fails.
var errUnableToSetup = errors.New("data was not in supported format")
// nsSetup is the callback used to enter the namespace for the user
// via callback in the cap.Launcher mechanism.
func nsSetup(pa *syscall.ProcAttr, data interface{}) error {
nsD, ok := data.(nsDetail)
if !ok {
return errUnableToSetup
}
if pa.Sys == nil {
pa.Sys = &syscall.SysProcAttr{}
}
pa.Sys.Cloneflags |= syscall.CLONE_NEWUSER
pa.Sys.UidMappings = nsD.uidMap
pa.Sys.GidMappings = nsD.gidMap
return nil
}
func parseRanges(detail *nsDetail, ids string, id int) []syscall.SysProcIDMap {
base := *baseID
if base < 0 {
base = detail.uid
}
list := []syscall.SysProcIDMap{
syscall.SysProcIDMap{
ContainerID: base,
HostID: id,
Size: 1,
},
}
base++
for _, next := range ranges(ids) {
fmt.Println("next:", next)
list = append(list,
syscall.SysProcIDMap{
ContainerID: base,
HostID: next.base,
Size: next.count,
})
base += next.count
}
return list
}
func main() {
flag.Parse()
detail := nsDetail{
gid: syscall.Getgid(),
}
thisUID := syscall.Getuid()
switch *uid {
case -1:
detail.uid = thisUID
default:
detail.uid = *uid
}
detail.uidMap = parseRanges(&detail, *uids, detail.uid)
thisGID := syscall.Getgid()
switch *gid {
case -1:
detail.gid = thisGID
default:
detail.gid = *gid
}
detail.gidMap = parseRanges(&detail, *gids, detail.gid)
unparsed := flag.Args()
arg0 := *shell
skip := 0
var w *cap.Launcher
if len(unparsed) > 0 {
switch unparsed[0] {
case "==":
arg0 = os.Args[0]
skip++
}
}
w = cap.NewLauncher(arg0, append([]string{arg0}, unparsed[skip:]...), nil)
if *ns {
// Include the namespace setup callback with the launcher.
w.Callback(nsSetup)
}
if thisUID != detail.uid {
w.SetUID(detail.uid)
}
if thisGID != detail.gid {
w.SetGroups(detail.gid, nil)
}
if *iab != "" {
ins, err := cap.IABFromText(*iab)
if err != nil {
log.Fatalf("--iab=%q parsing issue: %v", err)
}
w.SetIAB(ins)
}
if *mode != "" {
for m := cap.Mode(1); ; m++ {
if s := m.String(); s == "UNKNOWN" {
log.Fatalf("mode %q is unknown", *mode)
} else if s == *mode {
w.SetMode(m)
break
}
}
}
// The launcher can enable more functionality if involked with
// effective capabilities.
have := cap.GetProc()
for _, c := range []cap.Value{cap.SETUID, cap.SETGID} {
if canDo, err := have.GetFlag(cap.Permitted, c); err != nil {
log.Fatalf("failed to explore process capabilities, %q for %q", have, c)
} else if canDo {
if err := have.SetFlag(cap.Effective, true, c); err != nil {
log.Fatalf("failed to raise effective capability: \"%v e+%v\"", have, c)
}
}
}
if err := have.SetProc(); err != nil {
log.Fatalf("privilege assertion %q failed: %v", have, err)
}
if *debug {
if *ns {
fmt.Println("launching namespace")
} else {
fmt.Println("launching without namespace")
}
}
pid, err := w.Launch(detail)
if err != nil {
log.Fatalf("launch failed: %v", err)
}
if err := cap.NewSet().SetProc(); err != nil {
log.Fatalf("gowns could not drop privilege: %v", err)
}
p, err := os.FindProcess(pid)
if err != nil {
log.Fatalf("cannot find process: %v", err)
}
state, err := p.Wait()
if err != nil {
log.Fatalf("waiting failed: %v", err)
}
if *debug {
fmt.Println("process exited:", state)
}
}