You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 19:35:46