1*2810ac1bSKiyoung Kim// Program setid demonstrates how the to use the cap and/or psx packages to 2*2810ac1bSKiyoung Kim// change the uid, gids of a program. 3*2810ac1bSKiyoung Kim// 4*2810ac1bSKiyoung Kim// A long writeup explaining how to use it in various different ways 5*2810ac1bSKiyoung Kim// is available: 6*2810ac1bSKiyoung Kim// 7*2810ac1bSKiyoung Kim// https://sites.google.com/site/fullycapable/Home/using-go-to-set-uid-and-gids 8*2810ac1bSKiyoung Kimpackage main 9*2810ac1bSKiyoung Kim 10*2810ac1bSKiyoung Kimimport ( 11*2810ac1bSKiyoung Kim "flag" 12*2810ac1bSKiyoung Kim "fmt" 13*2810ac1bSKiyoung Kim "io/ioutil" 14*2810ac1bSKiyoung Kim "log" 15*2810ac1bSKiyoung Kim "strconv" 16*2810ac1bSKiyoung Kim "strings" 17*2810ac1bSKiyoung Kim "syscall" 18*2810ac1bSKiyoung Kim "unsafe" 19*2810ac1bSKiyoung Kim 20*2810ac1bSKiyoung Kim "kernel.org/pub/linux/libs/security/libcap/cap" 21*2810ac1bSKiyoung Kim "kernel.org/pub/linux/libs/security/libcap/psx" 22*2810ac1bSKiyoung Kim) 23*2810ac1bSKiyoung Kim 24*2810ac1bSKiyoung Kimvar ( 25*2810ac1bSKiyoung Kim uid = flag.Int("uid", -1, "specify a uid with a value other than (euid)") 26*2810ac1bSKiyoung Kim gid = flag.Int("gid", -1, "specify a gid with a value other than (egid)") 27*2810ac1bSKiyoung Kim drop = flag.Bool("drop", true, "drop privilege once IDs have been changed") 28*2810ac1bSKiyoung Kim suppl = flag.String("suppl", "", "comma separated list of groups") 29*2810ac1bSKiyoung Kim withCaps = flag.Bool("caps", true, "raise capabilities to setuid/setgid") 30*2810ac1bSKiyoung Kim) 31*2810ac1bSKiyoung Kim 32*2810ac1bSKiyoung Kim// setIDWithCaps uses the cap.SetUID and cap.SetGroups functions. 33*2810ac1bSKiyoung Kimfunc setIDsWithCaps(setUID, setGID int, gids []int) { 34*2810ac1bSKiyoung Kim if err := cap.SetGroups(setGID, gids...); err != nil { 35*2810ac1bSKiyoung Kim log.Fatalf("group setting failed: %v", err) 36*2810ac1bSKiyoung Kim } 37*2810ac1bSKiyoung Kim if err := cap.SetUID(setUID); err != nil { 38*2810ac1bSKiyoung Kim log.Fatalf("user setting failed: %v", err) 39*2810ac1bSKiyoung Kim } 40*2810ac1bSKiyoung Kim} 41*2810ac1bSKiyoung Kim 42*2810ac1bSKiyoung Kimfunc main() { 43*2810ac1bSKiyoung Kim flag.Parse() 44*2810ac1bSKiyoung Kim 45*2810ac1bSKiyoung Kim showIDs("before", false, syscall.Getuid(), syscall.Getgid()) 46*2810ac1bSKiyoung Kim 47*2810ac1bSKiyoung Kim gids := splitToInts() 48*2810ac1bSKiyoung Kim setGID := *gid 49*2810ac1bSKiyoung Kim if *gid == -1 { 50*2810ac1bSKiyoung Kim setGID = syscall.Getegid() 51*2810ac1bSKiyoung Kim } 52*2810ac1bSKiyoung Kim setUID := *uid 53*2810ac1bSKiyoung Kim if *uid == -1 { 54*2810ac1bSKiyoung Kim setUID = syscall.Getuid() 55*2810ac1bSKiyoung Kim } 56*2810ac1bSKiyoung Kim 57*2810ac1bSKiyoung Kim if *withCaps { 58*2810ac1bSKiyoung Kim setIDsWithCaps(setUID, setGID, gids) 59*2810ac1bSKiyoung Kim } else { 60*2810ac1bSKiyoung Kim if _, _, err := psx.Syscall3(syscall.SYS_SETGID, uintptr(setGID), 0, 0); err != 0 { 61*2810ac1bSKiyoung Kim log.Fatalf("failed to setgid(%d): %v", setGID, err) 62*2810ac1bSKiyoung Kim } 63*2810ac1bSKiyoung Kim if len(gids) != 0 { 64*2810ac1bSKiyoung Kim gids32 := []int32{int32(setGID)} 65*2810ac1bSKiyoung Kim for _, g := range gids { 66*2810ac1bSKiyoung Kim gids32 = append(gids32, int32(g)) 67*2810ac1bSKiyoung Kim } 68*2810ac1bSKiyoung Kim if _, _, err := psx.Syscall3(syscall.SYS_SETGROUPS, uintptr(unsafe.Pointer(&gids32[0])), 0, 0); err != 0 { 69*2810ac1bSKiyoung Kim log.Fatalf("failed to setgroups(%d, %v): %v", setGID, gids32, err) 70*2810ac1bSKiyoung Kim } 71*2810ac1bSKiyoung Kim } 72*2810ac1bSKiyoung Kim if _, _, err := psx.Syscall3(syscall.SYS_SETUID, uintptr(setUID), 0, 0); err != 0 { 73*2810ac1bSKiyoung Kim log.Fatalf("failed to setgid(%d): %v", setUID, err) 74*2810ac1bSKiyoung Kim } 75*2810ac1bSKiyoung Kim } 76*2810ac1bSKiyoung Kim 77*2810ac1bSKiyoung Kim if *drop { 78*2810ac1bSKiyoung Kim if err := cap.NewSet().SetProc(); err != nil { 79*2810ac1bSKiyoung Kim log.Fatalf("unable to drop privilege: %v", err) 80*2810ac1bSKiyoung Kim } 81*2810ac1bSKiyoung Kim } 82*2810ac1bSKiyoung Kim 83*2810ac1bSKiyoung Kim showIDs("after", true, setUID, setGID) 84*2810ac1bSKiyoung Kim} 85*2810ac1bSKiyoung Kim 86*2810ac1bSKiyoung Kim// splitToInts parses a comma separated string to a slice of integers. 87*2810ac1bSKiyoung Kimfunc splitToInts() (ret []int) { 88*2810ac1bSKiyoung Kim if *suppl == "" { 89*2810ac1bSKiyoung Kim return 90*2810ac1bSKiyoung Kim } 91*2810ac1bSKiyoung Kim a := strings.Split(*suppl, ",") 92*2810ac1bSKiyoung Kim for _, s := range a { 93*2810ac1bSKiyoung Kim n, err := strconv.Atoi(s) 94*2810ac1bSKiyoung Kim if err != nil { 95*2810ac1bSKiyoung Kim log.Fatalf("bad supplementary group [%q]: %v", s, err) 96*2810ac1bSKiyoung Kim } 97*2810ac1bSKiyoung Kim ret = append(ret, n) 98*2810ac1bSKiyoung Kim } 99*2810ac1bSKiyoung Kim return 100*2810ac1bSKiyoung Kim} 101*2810ac1bSKiyoung Kim 102*2810ac1bSKiyoung Kim// dumpStatus explores the current process /proc/task/* status files 103*2810ac1bSKiyoung Kim// for matching values. 104*2810ac1bSKiyoung Kimfunc dumpStatus(testCase string, validate bool, filter, expect string) bool { 105*2810ac1bSKiyoung Kim fmt.Printf("%s:\n", testCase) 106*2810ac1bSKiyoung Kim var failed bool 107*2810ac1bSKiyoung Kim pid := syscall.Getpid() 108*2810ac1bSKiyoung Kim fs, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/task", pid)) 109*2810ac1bSKiyoung Kim if err != nil { 110*2810ac1bSKiyoung Kim log.Fatal(err) 111*2810ac1bSKiyoung Kim } 112*2810ac1bSKiyoung Kim for _, f := range fs { 113*2810ac1bSKiyoung Kim tf := fmt.Sprintf("/proc/%s/status", f.Name()) 114*2810ac1bSKiyoung Kim d, err := ioutil.ReadFile(tf) 115*2810ac1bSKiyoung Kim if err != nil { 116*2810ac1bSKiyoung Kim fmt.Println(tf, err) 117*2810ac1bSKiyoung Kim failed = true 118*2810ac1bSKiyoung Kim continue 119*2810ac1bSKiyoung Kim } 120*2810ac1bSKiyoung Kim lines := strings.Split(string(d), "\n") 121*2810ac1bSKiyoung Kim for _, line := range lines { 122*2810ac1bSKiyoung Kim if strings.HasPrefix(line, filter) { 123*2810ac1bSKiyoung Kim fails := line != expect 124*2810ac1bSKiyoung Kim failure := "" 125*2810ac1bSKiyoung Kim if fails && validate { 126*2810ac1bSKiyoung Kim failed = fails 127*2810ac1bSKiyoung Kim failure = " (bad)" 128*2810ac1bSKiyoung Kim } 129*2810ac1bSKiyoung Kim fmt.Printf("%s %s%s\n", tf, line, failure) 130*2810ac1bSKiyoung Kim break 131*2810ac1bSKiyoung Kim } 132*2810ac1bSKiyoung Kim } 133*2810ac1bSKiyoung Kim } 134*2810ac1bSKiyoung Kim return failed 135*2810ac1bSKiyoung Kim} 136*2810ac1bSKiyoung Kim 137*2810ac1bSKiyoung Kim// showIDs dumps the thread map out of the /proc/<proc>/tasks 138*2810ac1bSKiyoung Kim// filesystem to confirm that all of the threads associated with the 139*2810ac1bSKiyoung Kim// process have the same uid/gid values. Note, the code does not 140*2810ac1bSKiyoung Kim// attempt to validate the supplementary groups at present. 141*2810ac1bSKiyoung Kimfunc showIDs(test string, validate bool, wantUID, wantGID int) { 142*2810ac1bSKiyoung Kim fmt.Printf("%s capability state: %q\n", test, cap.GetProc()) 143*2810ac1bSKiyoung Kim 144*2810ac1bSKiyoung Kim failed := dumpStatus(test+" gid", validate, "Gid:", fmt.Sprintf("Gid:\t%d\t%d\t%d\t%d", wantGID, wantGID, wantGID, wantGID)) 145*2810ac1bSKiyoung Kim 146*2810ac1bSKiyoung Kim failed = dumpStatus(test+" uid", validate, "Uid:", fmt.Sprintf("Uid:\t%d\t%d\t%d\t%d", wantUID, wantUID, wantUID, wantUID)) || failed 147*2810ac1bSKiyoung Kim 148*2810ac1bSKiyoung Kim if validate && failed { 149*2810ac1bSKiyoung Kim log.Fatal("did not observe desired *id state") 150*2810ac1bSKiyoung Kim } 151*2810ac1bSKiyoung Kim} 152