Setpgid, PTY & "operation not permitted"

Here’s a simple code snippet of running a bash script in a PTY:

pty-test.go

package main

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path"
	"sync"
	"syscall"

	"github.com/kr/pty"
)

func main() {
	wd, _ := os.Getwd()
	c := exec.Command(path.Join(wd, "pty-test.sh"))
	c.Dir = wd
	c.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

	f, err := pty.Start(c)
	if err != nil {
		panic(err)
	}

	var wg sync.WaitGroup
	wg.Add(1)

	var buffer bytes.Buffer

	go func() {
		_, err = io.Copy(&buffer, f)
		if e, ok := err.(*os.PathError); ok && e.Err == syscall.EIO {
			// We can safely ignore this error, because it's just
			// the PTY telling us that it closed successfully. See:
			// https://github.com/buildkite/agent/pull/34#issuecomment-46080419
			err = nil
		}

		wg.Done()
	}()

	c.Wait()
	wg.Wait()

	fmt.Printf(buffer.String())
}

pty-test.sh

#!/bin/bash

echo "Oh hai there!"

If you build this program on OSX:

GOOS=linux GOARCH=amd64 go build -o pty-test pty-test.go

Then run it on Ubuntu, you get:

$ ./pty-test
Oh hai there!

If you build it directly on Ubuntu, or run it there via go run, you get:

$ go run pty-test.go
panic: fork/exec /root/pty-test.sh: operation not permitted

goroutine 1 [running]:
main.main()
	/root/pty-test.go:24 +0x229
exit status 2

If you remove the c.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} line, it all works as expected…

Can anyone give me any pointers of what I may be doing wrong?

Thanks!

I am aware that Setpgid is very much system dependent. But not much aware of how/why. I will also be interested to know.

Your sample works for me (ubuntu 14.04/amd64). What linux are you using ? is SELinux raining on your parade ? Checked dmesg ? etc

oh, and which version of Go are you using ?

Hey Dave, to confirm you’re saying the same code compiles and works for you on ubuntu 14.04/amd64? I’m running the same as you:

$ go version
go version go1.5.1 linux/amd64

$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 14.04.3 LTS
Release:	14.04
Codename:	trusty

$ go run pty-test.go
panic: fork/exec /root/pty-test.sh: operation not permitted

goroutine 1 [running]:
main.main()
	/root/pty-test.go:24 +0x229
exit status 2

try go build pty-test.sh
strace -f ./pty-test

and paste the results into a gist

Gah, Discourse won’t let me post a link for some reason. The gist can be found at:

keithpitt/2d33ec737ba4d45ca5d2

Sorry to be a pain!

Are you sure you’re using Go 1.5.1 ? Don’t have GOROOT set, etc ?

Ubuntu 14.04 ships with an ancient version of Go which will be first in your path unless you have removed it.

Yeah, I double checked. Did you see something in the strace to suggest otherwise? I didn’t have $GOROOT set (I did a fresh Ubuntu to make sure it wasn’t my machine).

$ which go
/usr/local/go/bin/go
$ echo $GOROOT
/usr/local/go
$ go version
go version go1.5.1 linux/amd64

I’m running as root if that makes any difference? I ran the test again with $GOROOT but had the same error.

(Sorry about the link problem. Brand new users have some restrictions to prevent spam but they only last for the first ~10 minutes on the site and across the first 5 topics. Either way, I’ve bumped you up so you should be able to post links now.)

Thanks Matt! It was weird because it said: “New users can only post 2 links”, but I was only trying to post one! Weird…

clock_gettime(CLOCK_MONOTONIC, {63682, 399371924}) = 0
clock_gettime(CLOCK_MONOTONIC, {63682, 399535751}) = 0

All these lines are suspicious, I don’t see them in my version (running tip, but not much has changed in the runtime since 1.5)

I’m running as root if that makes any difference? I ran the test again with $GOROOT but had the same error.

Never, ever, ever, set GOROOT (please)

Cool, I didn’t set it in my initial test anyway. Is there any other debugging information I can provide? I just set this up on a fresh Digital Ocean VM (it’s got nothing on it aside from this test). Happy to give you access if you wanted to poke around.

Make 105% sure that you are using Go 1.5, from what I’m seeing, your program was compiled with a version of Go < 1.4

Something like this might answer that question

% strings pty-test | grep ^go1.
go1.4.2

^ sample

$ rm pty-test
$ go build -o pty-test pty-test.go
$ strings pty-test | grep ^go1.
go1.5.1
$ ./pty-test
panic: fork/exec /root/pty-test.sh: operation not permitted

goroutine 1 [running]:
main.main()
	/root/pty-test.go:24 +0x229

What happens if you don’t run that program as root ?

root@experimental-ci:~# sudo su keithpitt

keithpitt@experimental-ci:/root$ cd ~/

keithpitt@experimental-ci:~$ export GOPATH=/home/keithpitt/go

keithpitt@experimental-ci:~$ export PATH=/usr/local/go/bin:$PATH

keithpitt@experimental-ci:~$ go run pty-test.go
panic: fork/exec /home/keithpitt/pty-test.sh: operation not permitted

goroutine 1 [running]:
main.main()
	/home/keithpitt/pty-test.go:24 +0x229
exit status 2

keithpitt@experimental-ci:~$ strings pty-test | grep ^go1.
go1.5.1

Sorry Keith, I’m out of ideas.

Heh, all good Dave. Thanks so much for taking a look though! It’s just so weird that it’s working for you and not the fresh machine I just provisioned! Are you running the same Ubuntu release as me?

Hey Keith - humour me and check if you have SELinux running on that VM. There could be some horrible security policy getting in your way.

Run setenforce 0 as root and then try your command again.