1// Program gowns is a small program to explore and demonstrate using
2// Go to Wrap a child in a NameSpace under Linux.
3package main
4
5import (
6	"errors"
7	"flag"
8	"fmt"
9	"log"
10	"os"
11	"strings"
12	"syscall"
13
14	"kernel.org/pub/linux/libs/security/libcap/cap"
15)
16
17// nsDetail is how we summarize the type of namespace we want to
18// enter.
19type nsDetail struct {
20	// uid holds the uid for the base user in this namespace (defaults to getuid).
21	uid int
22
23	// uidMap holds the namespace mapping of uid values.
24	uidMap []syscall.SysProcIDMap
25
26	// gid holds the gid for the base user in this namespace (defaults to getgid).
27	gid int
28
29	// uidMap holds the namespace mapping of gid values.
30	gidMap []syscall.SysProcIDMap
31}
32
33var (
34	baseID = flag.Int("base", -1, "base id for uids and gids (-1 = invoker's uid)")
35	uid    = flag.Int("uid", -1, "uid of the hosting user")
36	gid    = flag.Int("gid", -1, "gid of the hosting user")
37	iab    = flag.String("iab", "", "IAB string for inheritable capabilities")
38	mode   = flag.String("mode", "", "force a libcap mode (capsh --modes for list)")
39
40	ns   = flag.Bool("ns", false, "enable user namespace features")
41	uids = flag.String("uids", "", "comma separated UID ranges to map contiguously (req. CAP_SETUID)")
42	gids = flag.String("gids", "", "comma separated GID ranges to map contiguously (req. CAP_SETGID)")
43
44	shell = flag.String("shell", "/bin/bash", "shell to be launched")
45	debug = flag.Bool("verbose", false, "more verbose output")
46)
47
48// r holds a base and count for a contiguous range.
49type r struct {
50	base, count int
51}
52
53// ranges unpacks numerical ranges.
54func ranges(s string) []r {
55	if s == "" {
56		return nil
57	}
58	var rs []r
59	for _, n := range strings.Split(s, ",") {
60		var base, upper int
61		if _, err := fmt.Sscanf(n, "%d-%d", &base, &upper); err == nil {
62			if upper < base {
63				log.Fatalf("invalid range: [%d-%d]", base, upper)
64			}
65			rs = append(rs, r{
66				base:  base,
67				count: 1 + upper - base,
68			})
69		} else if _, err := fmt.Sscanf(n, "%d", &base); err == nil {
70			rs = append(rs, r{
71				base:  base,
72				count: 1,
73			})
74		} else {
75			log.Fatalf("unable to parse range [%s]", n)
76		}
77	}
78	return rs
79}
80
81// restart launches the program again with the remaining arguments.
82func restart() {
83	log.Fatalf("failed to restart: flags: %q %q", os.Args[0], flag.Args()[1:])
84}
85
86// errUnableToSetup is how nsSetup fails.
87var errUnableToSetup = errors.New("data was not in supported format")
88
89// nsSetup is the callback used to enter the namespace for the user
90// via callback in the cap.Launcher mechanism.
91func nsSetup(pa *syscall.ProcAttr, data interface{}) error {
92	nsD, ok := data.(nsDetail)
93	if !ok {
94		return errUnableToSetup
95	}
96
97	if pa.Sys == nil {
98		pa.Sys = &syscall.SysProcAttr{}
99	}
100	pa.Sys.Cloneflags |= syscall.CLONE_NEWUSER
101	pa.Sys.UidMappings = nsD.uidMap
102	pa.Sys.GidMappings = nsD.gidMap
103	return nil
104}
105
106func parseRanges(detail *nsDetail, ids string, id int) []syscall.SysProcIDMap {
107	base := *baseID
108	if base < 0 {
109		base = detail.uid
110	}
111
112	list := []syscall.SysProcIDMap{
113		syscall.SysProcIDMap{
114			ContainerID: base,
115			HostID:      id,
116			Size:        1,
117		},
118	}
119
120	base++
121	for _, next := range ranges(ids) {
122		fmt.Println("next:", next)
123		list = append(list,
124			syscall.SysProcIDMap{
125				ContainerID: base,
126				HostID:      next.base,
127				Size:        next.count,
128			})
129		base += next.count
130	}
131	return list
132}
133
134func main() {
135	flag.Parse()
136
137	detail := nsDetail{
138		gid: syscall.Getgid(),
139	}
140
141	thisUID := syscall.Getuid()
142	switch *uid {
143	case -1:
144		detail.uid = thisUID
145	default:
146		detail.uid = *uid
147	}
148	detail.uidMap = parseRanges(&detail, *uids, detail.uid)
149
150	thisGID := syscall.Getgid()
151	switch *gid {
152	case -1:
153		detail.gid = thisGID
154	default:
155		detail.gid = *gid
156	}
157	detail.gidMap = parseRanges(&detail, *gids, detail.gid)
158
159	unparsed := flag.Args()
160
161	arg0 := *shell
162	skip := 0
163	var w *cap.Launcher
164	if len(unparsed) > 0 {
165		switch unparsed[0] {
166		case "==":
167			arg0 = os.Args[0]
168			skip++
169		}
170	}
171
172	w = cap.NewLauncher(arg0, append([]string{arg0}, unparsed[skip:]...), nil)
173	if *ns {
174		// Include the namespace setup callback with the launcher.
175		w.Callback(nsSetup)
176	}
177
178	if thisUID != detail.uid {
179		w.SetUID(detail.uid)
180	}
181
182	if thisGID != detail.gid {
183		w.SetGroups(detail.gid, nil)
184	}
185
186	if *iab != "" {
187		ins, err := cap.IABFromText(*iab)
188		if err != nil {
189			log.Fatalf("--iab=%q parsing issue: %v", err)
190		}
191		w.SetIAB(ins)
192	}
193
194	if *mode != "" {
195		for m := cap.Mode(1); ; m++ {
196			if s := m.String(); s == "UNKNOWN" {
197				log.Fatalf("mode %q is unknown", *mode)
198			} else if s == *mode {
199				w.SetMode(m)
200				break
201			}
202		}
203	}
204
205	// The launcher can enable more functionality if involked with
206	// effective capabilities.
207	have := cap.GetProc()
208	for _, c := range []cap.Value{cap.SETUID, cap.SETGID} {
209		if canDo, err := have.GetFlag(cap.Permitted, c); err != nil {
210			log.Fatalf("failed to explore process capabilities, %q for %q", have, c)
211		} else if canDo {
212			if err := have.SetFlag(cap.Effective, true, c); err != nil {
213				log.Fatalf("failed to raise effective capability: \"%v e+%v\"", have, c)
214			}
215		}
216	}
217	if err := have.SetProc(); err != nil {
218		log.Fatalf("privilege assertion %q failed: %v", have, err)
219	}
220
221	if *debug {
222		if *ns {
223			fmt.Println("launching namespace")
224		} else {
225			fmt.Println("launching without namespace")
226		}
227	}
228
229	pid, err := w.Launch(detail)
230	if err != nil {
231		log.Fatalf("launch failed: %v", err)
232	}
233	if err := cap.NewSet().SetProc(); err != nil {
234		log.Fatalf("gowns could not drop privilege: %v", err)
235	}
236
237	p, err := os.FindProcess(pid)
238	if err != nil {
239		log.Fatalf("cannot find process: %v", err)
240	}
241	state, err := p.Wait()
242	if err != nil {
243		log.Fatalf("waiting failed: %v", err)
244	}
245
246	if *debug {
247		fmt.Println("process exited:", state)
248	}
249}
250