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
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)
|
|
}
|
|
}
|