是否可以在其他用户下运行goroutine或go方法?

时间:2019-06-01 01:29:20

标签: go threadpool goroutine

我正在一个小型Web服务器上工作,该服务器提供文件并提供对每个用户主目录的访问。

如果源使用C语言,则我可以选择在不同线程下回答每个请求,并确保每个线程都可以将调用者的用户作为其用户来运行。

是否有任何方法可以实现类似于Go中的方法? 理想情况下,处理请求的代码部分,goroutine或被调用的方法应在调用者的用户帐户下运行。

我已经进行了一些研究,似乎在Go中我们可以将一个goroutine粘贴到当前线程上,但是我看不到如何创建一个新线程然后将goroutine附加到该线程上。

3 个答案:

答案 0 :(得分:1)

是的,您可以使用Linux系统调用setuid(而不是内置函数setuid)来实现。我刚刚发现了这个问题,并认为它是必须的,因为我也在其他编程语言中使用了它。所以我解决了我的问题,想报告如何做到这一点。

但是,SJP关于线程的内容是正确的,这正是我的问题的答案,但是由于线程问题-whole story in this very long issue 1435,它无法解决您的问题。其中还建议如何解决setuid问题的特定子集,从而解决了我的问题。

但是回到代码...您需要调用LockOSThread才能将当前的go例程修复为您当前在其中执行的线程,并且,您可以使用syscall {{ 1}}。

这是Linux的有效示例:

setuid

如果使用package main import ( "fmt" "log" "os" "runtime" "sync" "syscall" "time" ) func printUID() { fmt.Printf("Real UID: %d\n", syscall.Getuid()) fmt.Printf("Effective UID: %d\n", syscall.Geteuid()) } func main() { printUID() var wg sync.WaitGroup wg.Add(2) go func(wg *sync.WaitGroup) { defer wg.Done() time.Sleep(2 * time.Second) printUID() }(&wg) go func(wg *sync.WaitGroup) { runtime.LockOSThread() defer runtime.UnlockOSThread() defer wg.Done() _, _, serr := syscall.Syscall(syscall.SYS_SETUID, 1, 0, 0) if serr != 0 { log.Fatal(serr) os.Exit(1) } printUID() }(&wg) wg.Wait() printUID() } ,您将收到operation not supported

syscall.Setuid

代替

serr := syscall.Setuid(1)

答案 1 :(得分:1)

[这个答案与@A.Steinel 的答案相似,但是,唉,我没有足够的声誉来实际评论那个答案。希望这能提供更多完整的工作示例,更重要的是,它展示了如何使运行时免受使用不同 UID 运行的线程的混淆。]

首先,要严格按照您的要求执行操作需要进行一些黑客攻击,而且并不是那么安全...

[Go 喜欢用 POSIX semantics 操作,你想要做的是通过在单个进程中同时操作两个或多个 UID 来打破 POSIX 语义。 Go 需要 POSIX 语义,因为它在任何可用线程上运行 goroutines,并且运行时需要它们的行为都相同才能可靠地工作。由于 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,您可以尝试获取以下网址:

  • localhost:8080/something 显示类似general query "/something" executing as UID=您的 UID 在这里
  • localhost:8080/apple/pie 显示类似 query "/apple/pie" executing as UID=100 的内容。

希望这有助于说明如何执行您的要求。 [因为它涉及很多黑客,但我不建议真正这样做......]

答案 2 :(得分:0)

无法以其他用户身份运行goroutine或方法,因为它们都在与父进程相同的上下文中运行。 Goroutine相当于绿色线程,甚至不必一定要在每个例程中产生适当的OS线程。

这个答案可能还取决于操作系统,但是我也不认为这在Windows上也可以。

如果您通过cmd软件包生成另一个进程,那么此答案可能会有用 Running external commands through os/exec under another user