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