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