Search code examples
gothreadpoolgoroutine

Is it possible to run a goroutine or go method under a different user?


I am working on a small web server that serves files and provides access to each user's home directory.

If the source was to be in C I had the option of answering each request under different threads and to make sure each thread gets to run with the user of the caller as its users.

Is there any approach to achieve something similar to that in Go? Ideally, the part of the code that handles the request, the goroutine or the method that gets called should be run under the user account of the caller.

I have done some research and it seems in Go we can stick a single goroutine to the current thread but I can't see how it is possible to create a new thread and then attach a goroutine to that thread.


Solution

  • Yes, you can do that with the use of the Linux syscall setuid (not the built in function setuid). I just found this question and thought that it has to be possible, as I use this in other programming languages too. So I got my problem solved and wanted to report back how to do this.

    However, it is correct what SJP wrote about the threads and there lies exactly the answer to my problem, but it will not solve your problem, due to the threading issue - whole story in this very long issue 1435. Therein is also a suggestion in how to solve a specific subset of the setuid problem and that solved my problem.

    But back to code ... you need to call LockOSThread in order to fix the current go routine to the thread you're currently executing in and in that, you can change the context with the syscall setuid.

    Here is a working example for 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()
    }
    

    You will receive operation not supported if you use syscall.Setuid:

    serr := syscall.Setuid(1)
    

    instead of

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