blob: 2bbe5b0ebaf9751227a617517d4159516202b238 [file] [log] [blame] [edit]
// 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/<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")
}
}