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