Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

os/exec: $HOME not updated when running command as a different UID #33788

Open
wanrui opened this issue Aug 22, 2019 · 7 comments

Comments

@wanrui
Copy link

commented Aug 22, 2019

What version of Go are you using (go version)?

$ go version
go version go1.12.5 linux/amd64

Does this issue reproduce with the latest release?

yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/wanrui/.cache/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/wanrui/workspace"
GOPROXY="https://goproxy.cn"
GORACE=""
GOROOT="/home/wanrui/go"
GOTMPDIR=""
GOTOOLDIR="/home/wanrui/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/dev/null"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build173649310=/tmp/go-build -gno-record-gcc-switches"

What did you do?

there is testfile asynccmd .go

package main

import (
        "bufio"
        "context"
        "flag"
        "fmt"
        _ "net/http/pprof"
        "os/exec"
        "os/user"
        _ "runtime/pprof"
        "strconv"
        "syscall"
        "time"
)

const omg = `ls -al /tmp/ && nohup sleep 1000 &`

func main() {
        timeout := flag.Int("timeout", 120, "")
        command := flag.String("cmd", omg, "")
        runUser := flag.String("user", "www", "")
        pgid := flag.Bool("pgid", true, "Setpgid 开关。")

        flag.Parse()

        ctx, cancle := context.WithTimeout(context.Background(), time.Second*time.Duration(*timeout))
        defer cancle()
        cmd := exec.CommandContext(ctx, "/bin/bash", "-c", *command)

        user, _ := user.Lookup(*runUser)
        uid, _ := strconv.Atoi(user.Uid)
        gid, _ := strconv.Atoi(user.Gid)
        sysProcAttr := &syscall.SysProcAttr{
                Setpgid: *pgid,
                Credential: &syscall.Credential{
                        Uid: uint32(uid),
                        Gid: uint32(gid),
                },
        }
        cmd.SysProcAttr = sysProcAttr

        stdout, _ := cmd.StdoutPipe()
        stderr, _ := cmd.StderrPipe()
        if err := cmd.Start(); err != nil {
                fmt.Println("cmd.start occur error", err)
                return
        }

        go func() {
                b := bufio.NewReader(stdout)
                for {
                        buf, err := b.ReadBytes('\n')
                        if err != nil {
                                fmt.Println("read stdout err:", string(buf), err)
                                return
                        }
                        fmt.Println("read stdout buff:", string(buf))
                }

        }()

        go func() {
                b := bufio.NewReader(stderr)
                for {
                        buf, err := b.ReadBytes('\n')
                        if err != nil {
                                fmt.Println("read stderr err:", string(buf), err)
                                return
                        }
                        fmt.Println("read stderr buff:", string(buf))
                }

        }()

        if err := cmd.Wait(); err != nil {
                syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
                fmt.Println("cmd.wait occur error ", err)
        }

}

there is file : /home/www/.bash_profile

# .bash_profile
# Get the aliases and functions
echo $HOME
if [ -f ~/.bashrc ]; then
        . ~/.bashrc
fi
export PATH

in /root dir run follow command:

./asynccmd -user="www" -cmd=" source /home/www/.bash_profile"

What did you expect to see?

read stdout buff: /home/www

read stdout buff: 

read stdout err: java: read |0: file already closed
read stderr err:  read |0: file already closed

What did you see instead?

out is:

read stdout buff: /root

read stdout buff: 

read stdout err: java: read |0: file already closed
read stderr err:  read |0: file already closed
@wanrui

This comment has been minimized.

Copy link
Author

commented Aug 22, 2019

some properties doesn't set?

@bcmills bcmills changed the title os/exec switch user to run some command ,current $HOME is Wrong? os/exec: $HOME not updated when running command as a different UID Aug 22, 2019

@bcmills

This comment has been minimized.

Copy link
Member

commented Aug 22, 2019

Per http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03 (emphasis mine):

The system shall initialize this variable at the time of login to be a pathname of the user's home directory.


os/exec is not a login operation, so it doesn't update $HOME for you. I'm not sure whether it updates USER, either.

You can set both explicitly for the command using something like:

cmd.Env = append(os.Environ(), "USER="+user.Username, "HOME="+user.HomeDir)

We probably need clearer documentation about it either way.

@bcmills

This comment has been minimized.

Copy link
Member

commented Aug 22, 2019

@bradfitz

This comment has been minimized.

Copy link
Member

commented Aug 22, 2019

I don't think os/exec should be in the business of adjusting the environment too much. We do a bit on Windows to make sure there's a minimally functional environment for loading DLLs, but I don't think we should do much more than that.

@wi-cuckoo

This comment has been minimized.

Copy link

commented Aug 23, 2019

we hope it can simulate working of shell, as we specified the user. like subprocess in python, it works well.

@wanrui

This comment has been minimized.

Copy link
Author

commented Aug 23, 2019

when Current User is root run shell as another User like flow

sudo -u www  sh -c "source /home/www/.bash_profile  whereis java"

output like

/home/www

as @bcmills says we can code
like follow

        fmt.Println("current:cmd.env", cmd.Env)
        fmt.Println("current:os.Environ", os.Environ())
        cmd.Env = append(os.Environ(), "USER="+user.Username, "HOME="+user.HomeDir)
        sysProcAttr := &syscall.SysProcAttr{
                Setpgid: *pgid,
                Credential: &syscall.Credential{
                        Uid: uint32(uid),
                        Gid: uint32(gid),
                },
        }

os.Environ() is current User root 's ENV , May be cmd.Env should set like follow:

cmd.Env = append(cmd.Env, "USER="+user.Username, "HOME="+user.HomeDir)

@bradfitz

This comment has been minimized.

Copy link
Member

commented Aug 26, 2019

I think the most we'll do here is add a small bit of documentation to SysProcAttr or syscall.Credential to say that advanced users are on their own in this regard and we don't do this automatically. Even setting USER and HOME may not be sufficient for all cases. It might be both too much and not enough magic. Better to stay out of it and let advanced users choose exactly what happens without surprises.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants
You can’t perform that action at this time.