// Program setid demonstrates how the to use the cap and/or psx packages to // change the uid, gids of a program. // // A long writeup explaining how to use it in various different ways // is available: // // https://sites.google.com/site/fullycapable/Home/using-go-to-set-uid-and-gids package main import ( "flag" "fmt" "io/ioutil" "log" "strconv" "strings" "syscall" "unsafe" "kernel.org/pub/linux/libs/security/libcap/cap" "kernel.org/pub/linux/libs/security/libcap/psx" ) var ( uid = flag.Int("uid", -1, "specify a uid with a value other than (euid)") gid = flag.Int("gid", -1, "specify a gid with a value other than (egid)") drop = flag.Bool("drop", true, "drop privilege once IDs have been changed") suppl = flag.String("suppl", "", "comma separated list of groups") withCaps = flag.Bool("caps", true, "raise capabilities to setuid/setgid") ) // setIDWithCaps uses the cap.SetUID and cap.SetGroups functions. func setIDsWithCaps(setUID, setGID int, gids []int) { if err := cap.SetGroups(setGID, gids...); err != nil { log.Fatalf("group setting failed: %v", err) } if err := cap.SetUID(setUID); err != nil { log.Fatalf("user setting failed: %v", err) } } func main() { flag.Parse() showIDs("before", false, syscall.Getuid(), syscall.Getgid()) gids := splitToInts() setGID := *gid if *gid == -1 { setGID = syscall.Getegid() } setUID := *uid if *uid == -1 { setUID = syscall.Getuid() } if *withCaps { setIDsWithCaps(setUID, setGID, gids) } else { if _, _, err := psx.Syscall3(syscall.SYS_SETGID, uintptr(setGID), 0, 0); err != 0 { log.Fatalf("failed to setgid(%d): %v", setGID, err) } if len(gids) != 0 { gids32 := []int32{int32(setGID)} for _, g := range gids { gids32 = append(gids32, int32(g)) } if _, _, err := psx.Syscall3(syscall.SYS_SETGROUPS, uintptr(unsafe.Pointer(&gids32[0])), 0, 0); err != 0 { log.Fatalf("failed to setgroups(%d, %v): %v", setGID, gids32, err) } } if _, _, err := psx.Syscall3(syscall.SYS_SETUID, uintptr(setUID), 0, 0); err != 0 { log.Fatalf("failed to setgid(%d): %v", setUID, err) } } if *drop { if err := cap.NewSet().SetProc(); err != nil { log.Fatalf("unable to drop privilege: %v", err) } } showIDs("after", true, setUID, setGID) } // splitToInts parses a comma separated string to a slice of integers. func splitToInts() (ret []int) { if *suppl == "" { return } a := strings.Split(*suppl, ",") for _, s := range a { n, err := strconv.Atoi(s) if err != nil { log.Fatalf("bad supplementary group [%q]: %v", s, err) } ret = append(ret, n) } return } // dumpStatus explores the current process /proc/task/* status files // for matching values. func dumpStatus(testCase string, validate bool, filter, expect string) bool { fmt.Printf("%s:\n", testCase) var failed bool pid := syscall.Getpid() fs, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/task", pid)) if err != nil { log.Fatal(err) } for _, f := range fs { tf := fmt.Sprintf("/proc/%s/status", f.Name()) d, err := ioutil.ReadFile(tf) if err != nil { fmt.Println(tf, err) failed = true continue } lines := strings.Split(string(d), "\n") for _, line := range lines { if strings.HasPrefix(line, filter) { fails := line != expect failure := "" if fails && validate { failed = fails failure = " (bad)" } fmt.Printf("%s %s%s\n", tf, line, failure) break } } } return failed } // showIDs dumps the thread map out of the /proc//tasks // filesystem to confirm that all of the threads associated with the // process have the same uid/gid values. Note, the code does not // attempt to validate the supplementary groups at present. func showIDs(test string, validate bool, wantUID, wantGID int) { fmt.Printf("%s capability state: %q\n", test, cap.GetProc()) failed := dumpStatus(test+" gid", validate, "Gid:", fmt.Sprintf("Gid:\t%d\t%d\t%d\t%d", wantGID, wantGID, wantGID, wantGID)) failed = dumpStatus(test+" uid", validate, "Uid:", fmt.Sprintf("Uid:\t%d\t%d\t%d\t%d", wantUID, wantUID, wantUID, wantUID)) || failed if validate && failed { log.Fatal("did not observe desired *id state") } }