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

compiler: root user setcap returns EPERM on centos6 #39440

Closed
bigMacro opened this issue Jun 6, 2020 · 1 comment
Closed

compiler: root user setcap returns EPERM on centos6 #39440

bigMacro opened this issue Jun 6, 2020 · 1 comment

Comments

@bigMacro
Copy link

bigMacro commented Jun 6, 2020

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

$ go version
go version go1.14.4 linux/amd64

Does this issue reproduce with the latest release?

Yes

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

cat /etc/centos-release Output
CentOS release 6.10 (Final)
go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/root/.cache/go-build"
GOENV="/root/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/root/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/root/captest/go.mod"
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 -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build939080230=/tmp/go-build -gno-record-gcc-switches"

What did you do?

source code

  1. directory structure
/root/captest
├── captest
│   └── main.go
├── go.mod
├── go.sum
  1. mian.go
package main

import (
	"fmt"
	"runtime"
	"syscall"
	"unsafe"
)

func main() {
	runtime.LockOSThread()

	cap, err := CapGet(0)
	if err != nil {
		panic(fmt.Errorf("CapGet error: %v", err))
	}
	encodePermittedCaps := cap.EncodePermittedCaps()

	//if err := CapSet(encodePermittedCaps, encodePermittedCaps, encodePermittedCaps); nil != err {
	//	panic(fmt.Errorf("CapSet error: %v", err))
	//}

	capData := capData{
		Effective:   encodePermittedCaps,
		Permitted:   encodePermittedCaps,
		Inheritable: encodePermittedCaps,
	}
	if err := CapSet2(capData); nil != err {
		panic(fmt.Errorf("CapSet2 error: %v", err))
	}

	fmt.Println("ok")
}

const (
	LINUX_CAPABILITY_VERSION_3 = 0x20080522
	LINUX_CAPABILITY_U32S_3    = 2
)

func CapGet(pid int) (*Cap, error) {
	caps := new(Cap)
	caps.Header.Version = LINUX_CAPABILITY_VERSION_3
	caps.Header.Pid = int32(pid)

	_, _, e1 := syscall.Syscall(syscall.SYS_CAPGET,
		uintptr(unsafe.Pointer(&caps.Header)),
		uintptr(unsafe.Pointer(&caps.Data[0])),
		0)
	if e1 != 0 {
		return nil, e1
	}
	return caps, nil
}

func CapSet(effevtive, permitted, inheritable uint32) error {
	caps := new(Cap)
	caps.Header = capHeader{
		Version: LINUX_CAPABILITY_VERSION_3,
		Pid:     0,
	}
	caps.Data[0] = capData{
		Effective:   effevtive,
		Permitted:   permitted,
		Inheritable: inheritable,
	}
	_, _, err := syscall.RawSyscall(syscall.SYS_CAPSET,
		uintptr(unsafe.Pointer(&caps.Header)),
		uintptr(unsafe.Pointer(&caps.Data[0])), 0)
	if 0 != err {
		return err
	}
	return nil
}

func CapSet2(data capData) error {
	a := &capHeader{
		LINUX_CAPABILITY_VERSION_3,
		0,
	}
	b := &data
	_, _, err := syscall.RawSyscall(syscall.SYS_CAPSET,
		uintptr(unsafe.Pointer(a)),
		uintptr(unsafe.Pointer(b)), 0)
	if 0 != err {
		return err
	}
	return nil
}

type capHeader struct {
	Version uint32
	Pid     int32
}

type capData struct {
	Effective   uint32
	Permitted   uint32
	Inheritable uint32
}

type Cap struct {
	Header capHeader
	Data   [LINUX_CAPABILITY_U32S_3]capData
}

func (c *Cap) EncodeEffectiveCaps() uint32 {
	return c.Data[0].Effective
}

func (c *Cap) EncodePermittedCaps() uint32 {
	return c.Data[0].Permitted
}

func (c *Cap) EncodeInheritableCaps() uint32 {
	return c.Data[0].Inheritable
}

compile the source code

GOOS=linux GOARCH=amd64 go build -o ./bin/captest-CapSet2 ./captest

set cap and run

setcap CAP_SYS_RESOURCE+eip ./bin/captest-CapSet2
./bin/captest-CapSet2

What did you expect to see?

Output is:

ok

What did you see instead?

Output is:

panic: CapSet2 error: operation not permitted

goroutine 1 [running, locked to thread]:
main.main()
	/root/captest/captest/main.go:29 +0x1a8

Other important information

  1. All command above is running as root.
  2. It works if using CapSet instead of CapSet2. The main function is:
func main() {
	runtime.LockOSThread()

	cap, err := CapGet(0)
	if err != nil {
		panic(fmt.Errorf("CapGet error: %v", err))
	}
	encodePermittedCaps := cap.EncodePermittedCaps()

	if err := CapSet(encodePermittedCaps, encodePermittedCaps, encodePermittedCaps); nil != err {
		panic(fmt.Errorf("CapSet error: %v", err))
	}

	//capData := capData{
	//	Effective:   encodePermittedCaps,
	//	Permitted:   encodePermittedCaps,
	//	Inheritable: encodePermittedCaps,
	//}
	//if err := CapSet2(capData); nil != err {
	//	panic(fmt.Errorf("CapSet2 error: %v", err))
	//}

	fmt.Println("ok")
}

I have tried to disassemble the binary, but i am not good at assembly code. So I am trying to learn Go assembly. However, it seems there are some differences between Go assembly and disassembly code.

@bigMacro bigMacro changed the title root user setcap returns EPERM on centos6 compiler: root user setcap returns EPERM on centos6 Jun 7, 2020
@bigMacro
Copy link
Author

bigMacro commented Jun 7, 2020

Disassembly of the binary:
image

Well, I think i konw the answer... 64-bit capabilities use datap[0] and datap[1], whereas 32-bit capabilities use only datap[0]. SetCap2 only supports the datap[0] and datap[1] is undefined...

@bigMacro bigMacro closed this as completed Jun 7, 2020
@golang golang locked and limited conversation to collaborators May 27, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

2 participants