Go 1.19编译程序setuid/setgid位失效问题求助
Go程序setuid/setgid位不生效问题排查与解决
环境信息
➜ go version go version go1.19.6 linux/amd64 ➜ uname -a Linux dmitry-desktop 6.1.18-200.fc37.x86_64 #1 SMP PREEMPT_DYNAMIC Sat Mar 11 16:09:14 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
问题描述
为test用户所有的main可执行文件设置了setuid/setgid位,但以dmitry用户运行时,程序未切换到test的UID,写入test_file时提示权限拒绝。
操作过程
[test@dmitry-desktop setguid]$ whoami test [test@dmitry-desktop setguid]$ id uid=1001(test) gid=1002(test) groups=1002(test) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 [test@dmitry-desktop setguid]$ ./main [test@dmitry-desktop setguid]$ ls -la total 1356 drwxrwxr-x. 2 test test 100 Mar 26 14:07 . drwxrwxrwt. 48 root root 1780 Mar 26 14:07 .. -rwsr-sr-x. 1 test test 1.9M Mar 26 14:16 main -rw-r--r--. 1 test test 225 Mar 26 13:58 main.go -rw-r--r--. 1 test test 9 Mar 26 14:07 test_file [test@dmitry-desktop setguid]$ go build main.go [test@dmitry-desktop setguid]$ chmod u+s main [test@dmitry-desktop setguid]$ chmod g+s main [test@dmitry-desktop setguid]$ ls -la main -rwsr-sr-x. 1 test test 1943252 Mar 26 14:18 main [test@dmitry-desktop setguid]$ ./main Real UID: 1001 Effective UID: 1001 Real UID: 1001 Effective UID: 1001
切换至dmitry用户运行结果
➜ whoami dmitry ✗ /tmp/setguid/main Real UID: 1000 Effective UID: 1000 Real UID: 1000 Effective UID: 1000 panic: open /tmp/setguid/test_file: permission denied goroutine 1 [running]: main.check(...) /tmp/setguid/main.go:14 main.main() /tmp/setguid/main.go:32 +0xe5
原main.go代码
package main import ( "os" "syscall" "log" "fmt" "time" ) func check(e error) { if e != nil { panic(e) } } func main() { printdelay() errgid := syscall.Setuid(syscall.Getuid()) if errgid != nil { log.Fatal(errgid) os.Exit(1) } printdelay() d1 := []byte("hello\ngo\n") err := os.WriteFile("/tmp/setguid/test_file", d1, 0644) check(err) } func printdelay() { fmt.Printf("Real UID: %d\n", syscall.Getuid()) fmt.Printf("Effective UID: %d\n", syscall.Geteuid()) time.Sleep(7 * time.Second) }
后续尝试及报错
执行以下命令:
sudo /sbin/setcap cap_setuid=ep main chown root:root main chmod +s main ls -la main -rwsr-sr-x. 1 root root 1943124 Mar 26 14:28 main
修改main.go代码:
// errgid := syscall.Setuid(syscall.Getuid()) errgid := syscall.Setuid(1001)
运行报错:
✗ id uid=1000(dmitry) gid=1000(dmitry) groups=1000(dmitry),10(wheel),969(plugdev),971(wireshark),975(docker) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c102 ➜ ./main Real UID: 1000 Effective UID: 1000 2023/03/26 14:33:04 operation not permitted
问题排查与解决
1. Go默认编译PIE导致setuid失效
Go 1.14+版本默认编译生成PIE(位置无关可执行文件),而Linux系统中,PIE可执行文件的setuid/setgid位默认不会生效——这是内核的安全限制,防止内存漏洞被利用。
解决方法:编译时添加-buildmode=exe参数,生成非PIE的可执行文件:
go build -buildmode=exe main.go
2. 代码中多余的Setuid调用抵消了setuid效果
原代码中syscall.Setuid(syscall.Getuid())这一行完全是错误操作:它把程序的有效UID设置为当前运行用户的真实UID(dmitry的1000),直接覆盖了setuid带来的有效UID(test的1001)。必须删除这行代码,程序才会保留setuid的权限切换效果。
修正后的main.go核心部分:
func main() { printdelay() // 移除多余的Setuid调用 // errgid := syscall.Setuid(syscall.Getuid()) // if errgid != nil { // log.Fatal(errgid) // os.Exit(1) // } printdelay() d1 := []byte("hello\ngo\n") err := os.WriteFile("/tmp/setguid/test_file", d1, 0644) check(err) }
3. 关于cap_setuid尝试的问题
后续用cap_setuid的方法失败,一是因为PIE的限制未解除,二是其实完全没必要使用cap权限——只要解决PIE和代码的问题,setuid位就能正常生效。
按照上述步骤重新编译、设置权限后,以dmitry用户运行程序,就能正常切换到test用户的UID,成功写入test_file。
内容的提问来源于stack exchange,提问作者DmitrySemenov
相关产品推荐
相关产品推荐

