[这个答案与@A.Steinel 的答案相似,但是,唉,我没有足够的声誉来实际评论那个答案。希望这提供了一个完整的工作示例,更重要的是,它演示了保持运行时不会混淆使用不同 UID 运行的线程。]
首先,严格按照您的要求进行操作需要一些技巧,而且并不是那么安全...
[Go 喜欢用POSIX semantics 操作,而您想要做的是通过在单个进程中同时操作两个或多个 UID 来打破 POSIX 语义。 Go 需要 POSIX 语义,因为它在任何可用线程上运行 goroutine,并且运行时需要它们的行为相同才能可靠地工作。由于 Linux 的 setuid() 系统调用不支持 POSIX 语义,Go 选择了 not implement syscall.Setuid(),直到最近它变成了 possible to implement it with POSIX semantics in go1.16。
- 注意,
glibc,如果您调用 setuid(),系统调用本身会使用修复机制 (glibc/nptl/setxid) 并会同时更改程序中所有线程的 UID 值时间>。因此,即使在 C 语言中,您也必须做一些修改来解决这个细节。]
话虽如此,您可以通过 runtime.LockOSThread() 调用使 goroutine 以您想要的方式工作,但不会在每次专门使用后立即丢弃锁定的线程来混淆 Go 运行时。
类似这样的东西(称之为uidserve.go):
// Program uidserve serves content as different uids. This is adapted
// from the https://golang.org/pkg/net/http/#ListenAndServe example.
package main
import (
"fmt"
"log"
"net/http"
"runtime"
"syscall"
)
// Simple username to uid mapping.
var prefixUIDs = map[string]uintptr{
"apple": 100,
"banana": 101,
"cherry": 102,
}
type uidRunner struct {
uid uintptr
}
func (u *uidRunner) ServeHTTP(w http.ResponseWriter, r *http.Request) {
runtime.LockOSThread()
// Note, we never runtime.UnlockOSThread().
if _, _, e := syscall.RawSyscall(syscall.SYS_SETUID, u.uid, 0, 0); e != 0 {
http.Error(w, "permission problem", http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "query %q executing as UID=%d\n", r.URL.Path, syscall.Getuid())
}
func main() {
for u, uid := range prefixUIDs {
h := &uidRunner{uid: uid}
http.Handle(fmt.Sprint("/", u, "/"), h)
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "general query %q executing as UID=%d\n", r.URL.Path, syscall.Getuid())
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
像这样构建它:
$ go build uidserve.go
接下来,要使其正常工作,您必须授予该程序一些特权。那就是做其中一个或另一个(setcap 是来自libcap suite 的工具):
$ sudo /sbin/setcap cap_setuid=ep ./uidserve
或者,更传统的运行setuid-root的方式:
$ sudo chown root ./uidserve
$ sudo chmod +s ./uidserve
现在,如果您运行 ./uidserve 并将浏览器连接到 localhost:8080,您可以尝试获取以下 URL:
-
localhost:8080/something 显示类似general query "/something" executing as UID=你的 UID 在这里。
-
localhost:8080/apple/pie 显示类似 query "/apple/pie" executing as UID=100 的内容。
- 等
希望这有助于展示如何按照您的要求进行操作。 [但是,由于它涉及大量黑客攻击,我不建议您真正这样做...]