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
Open

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

wanrui opened this issue Aug 22, 2019 · 7 comments
Labels
Documentation NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@wanrui
Copy link

wanrui 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
Copy link
Author

wanrui 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
Copy link
Contributor

bcmills 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 bcmills added Documentation NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. labels Aug 22, 2019
@bcmills bcmills added this to the Go1.14 milestone Aug 22, 2019
@bcmills
Copy link
Contributor

bcmills commented Aug 22, 2019

CC @bradfitz @kevinburke @ianlancetaylor; see previously #26463.

@bradfitz
Copy link
Contributor

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
Copy link

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

@wanrui
Copy link
Author

wanrui 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
Copy link
Contributor

bradfitz 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
Labels
Documentation NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

No branches or pull requests

5 participants