【问题标题】:Is it possible to run a goroutine or go method under a different user?是否可以在不同的用户下运行 goroutine 或 go 方法?
【发布时间】:2019-06-01 01:29:20
【问题描述】:

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

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

有什么方法可以实现类似于 Go 中的功能吗? 理想情况下,处理请求的部分代码、goroutine 或被调用的方法应该在调用者的用户帐户下运行。

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

【问题讨论】:

    标签: go threadpool goroutine


    【解决方案1】:

    不可能以不同的用户身份运行 goroutine 或方法,因为它们都在与父进程相同的上下文中运行。 Goroutines 相当于绿色线程,甚至不必为每个例程生成适当的 OS 线程。

    这个答案也可能取决于操作系统,但我认为这也不适用于 Windows。

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

    【讨论】:

    • 即使操作系统支持子进程权限,Go 也不会公开该功能。 (我也不知道存在任何此类操作系统)
    【解决方案2】:

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

    但是,SJP 写的关于线程的内容是正确的,这正是我的问题的答案,但由于线程问题 - whole story in this very long issue 1435,它不会解决您的问题。其中还有一个关于如何解决 setuid 问题的特定子集的建议,并且解决了我的问题。

    但是回到代码...您需要调用LockOSThread 以将当前的go 例程修复到您当前正在执行的线程中,并且您可以使用系统调用setuid 更改上下文。

    这是一个适用于 Linux 的工作示例:

    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()
    }
    

    如果您使用syscall.Setuid,您将收到operation not supported

    serr := syscall.Setuid(1)
    

    而不是

    _, _, serr := syscall.Syscall(syscall.SYS_SETUID, 1, 0, 0)
    

    【讨论】:

      【解决方案3】:

      [这个答案与@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 的内容。

      希望这有助于展示如何按照您的要求进行操作。 [但是,由于它涉及大量黑客攻击,我不建议您真正这样做...]

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-06-27
        • 2018-03-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-21
        • 1970-01-01
        • 2019-09-26
        相关资源
        最近更新 更多