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