pkg/seccomp: simplify IsSupported
Current implementation of seccomp.IsSupported (rooted in runc) is not very good. First, it parses the whole /proc/self/status, adding each key: value pair into the map (lots of allocations and future work for garbage collector), when using a single key from that map. Second, the presence of "Seccomp" key in /proc/self/status merely means that kernel option CONFIG_SECCOMP is set, but there is a need to _also_ check for CONFIG_SECCOMP_FILTER (the code for which exists but never executed in case /proc/self/status has Seccomp key). Replace all this with a single call to prctl; see the long comment in the code for details. NOTE historically, parsing /proc/self/status was added after a concern was raised in https://github.com/opencontainers/runc/pull/471 that prctl(PR_GET_SECCOMP, ...) can result in the calling process being killed with SIGKILL. This is a valid concern, so the new code here does not use PR_GET_SECCOMP at all. Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
This commit is contained in:
parent
97681e1ae0
commit
2f8a504f7c
|
|
@ -3,72 +3,37 @@
|
|||
package seccomp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
perrors "github.com/pkg/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const statusFilePath = "/proc/self/status"
|
||||
|
||||
// IsSupported returns true if the system has been configured to support
|
||||
// seccomp.
|
||||
// seccomp (including the check for CONFIG_SECCOMP_FILTER kernel option).
|
||||
func IsSupported() bool {
|
||||
// Since Linux 3.8, the Seccomp field of the /proc/[pid]/status file
|
||||
// provides a method of obtaining the same information, without the risk
|
||||
// that the process is killed; see proc(5).
|
||||
status, err := parseStatusFile(statusFilePath)
|
||||
if err == nil {
|
||||
_, ok := status["Seccomp"]
|
||||
return ok
|
||||
}
|
||||
// Excerpts from prctl(2), section ERRORS:
|
||||
//
|
||||
// EACCES
|
||||
// option is PR_SET_SECCOMP and arg2 is SECCOMP_MODE_FILTER, but
|
||||
// the process does not have the CAP_SYS_ADMIN capability or has
|
||||
// not set the no_new_privs attribute <...>.
|
||||
// <...>
|
||||
// EFAULT
|
||||
// option is PR_SET_SECCOMP, arg2 is SECCOMP_MODE_FILTER, the
|
||||
// system was built with CONFIG_SECCOMP_FILTER, and arg3 is an
|
||||
// invalid address.
|
||||
// <...>
|
||||
// EINVAL
|
||||
// option is PR_SET_SECCOMP or PR_GET_SECCOMP, and the kernel
|
||||
// was not configured with CONFIG_SECCOMP.
|
||||
//
|
||||
// EINVAL
|
||||
// option is PR_SET_SECCOMP, arg2 is SECCOMP_MODE_FILTER,
|
||||
// and the kernel was not configured with CONFIG_SECCOMP_FILTER.
|
||||
// <end of quote>
|
||||
//
|
||||
// Meaning, in case these kernel options are set (this is what we check
|
||||
// for here), we will get some other error (most probably EACCES or
|
||||
// EFAULT). IOW, EINVAL means "seccomp not supported", any other error
|
||||
// means it is supported.
|
||||
|
||||
// PR_GET_SECCOMP (since Linux 2.6.23)
|
||||
// Return (as the function result) the secure computing mode of the calling
|
||||
// thread. If the caller is not in secure computing mode, this operation
|
||||
// returns 0; if the caller is in strict secure computing mode, then the
|
||||
// prctl() call will cause a SIGKILL signal to be sent to the process. If
|
||||
// the caller is in filter mode, and this system call is allowed by the
|
||||
// seccomp filters, it returns 2; otherwise, the process is killed with a
|
||||
// SIGKILL signal. This operation is available only if the kernel is
|
||||
// configured with CONFIG_SECCOMP enabled.
|
||||
if err := unix.Prctl(unix.PR_GET_SECCOMP, 0, 0, 0, 0); !errors.Is(err, unix.EINVAL) {
|
||||
// Make sure the kernel has CONFIG_SECCOMP_FILTER.
|
||||
if err := unix.Prctl(unix.PR_SET_SECCOMP, unix.SECCOMP_MODE_FILTER, 0, 0, 0); !errors.Is(err, unix.EINVAL) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// parseStatusFile reads the provided `file` into a map of strings.
|
||||
func parseStatusFile(file string) (map[string]string, error) {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return nil, perrors.Wrapf(err, "open status file %s", file)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
status := make(map[string]string)
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
text := scanner.Text()
|
||||
parts := strings.SplitN(text, ":", 2)
|
||||
|
||||
if len(parts) <= 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
status[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, perrors.Wrapf(err, "scan status file %s", file)
|
||||
}
|
||||
|
||||
return status, nil
|
||||
return unix.Prctl(unix.PR_SET_SECCOMP, unix.SECCOMP_MODE_FILTER, 0, 0, 0) != unix.EINVAL
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,185 +0,0 @@
|
|||
// +build seccomp
|
||||
|
||||
package seccomp
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var statusFile = `
|
||||
Name: bash
|
||||
Umask: 0022
|
||||
State: S (sleeping)
|
||||
Tgid: 17248
|
||||
Ngid: 0
|
||||
Pid: 17248
|
||||
PPid: 17200
|
||||
TracerPid: 0
|
||||
Uid: 1000 1000 1000 1000
|
||||
Gid: 100 100 100 100
|
||||
FDSize: 256
|
||||
Groups: 16 33 100
|
||||
NStgid: 17248
|
||||
NSpid: 17248
|
||||
NSpgid: 17248
|
||||
NSsid: 17200
|
||||
VmPeak: 131168 kB
|
||||
VmSize: 131168 kB
|
||||
VmLck: 0 kB
|
||||
VmPin: 0 kB
|
||||
VmHWM: 13484 kB
|
||||
VmRSS: 13484 kB
|
||||
RssAnon: 10264 kB
|
||||
RssFile: 3220 kB
|
||||
RssShmem: 0 kB
|
||||
VmData: 10332 kB
|
||||
VmStk: 136 kB
|
||||
VmExe: 992 kB
|
||||
VmLib: 2104 kB
|
||||
VmPTE: 76 kB
|
||||
VmPMD: 12 kB
|
||||
VmSwap: 0 kB
|
||||
HugetlbPages: 0 kB # 4.4
|
||||
Threads: 1
|
||||
SigQ: 0/3067
|
||||
SigPnd: 0000000000000000
|
||||
ShdPnd: 0000000000000000
|
||||
SigBlk: 0000000000010000
|
||||
SigIgn: 0000000000384004
|
||||
SigCgt: 000000004b813efb
|
||||
CapInh: 0000000000000000
|
||||
CapPrm: 0000000000000000
|
||||
CapEff: 0000000000000000
|
||||
CapBnd: ffffffffffffffff
|
||||
CapAmb: 0000000000000000
|
||||
NoNewPrivs: 0
|
||||
Seccomp: 0
|
||||
Cpus_allowed: 00000001
|
||||
Cpus_allowed_list: 0
|
||||
Mems_allowed: 1
|
||||
Mems_allowed_list: 0
|
||||
voluntary_ctxt_switches: 150
|
||||
nonvoluntary_ctxt_switches: 545
|
||||
`
|
||||
|
||||
func TestParseStatusFile(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
getFilePath func() (string, func())
|
||||
shouldErr bool
|
||||
expected map[string]string
|
||||
}{
|
||||
{ // success
|
||||
getFilePath: func() (string, func()) {
|
||||
tempFile, err := ioutil.TempFile("", "parse-status-file-")
|
||||
require.Nil(t, err)
|
||||
|
||||
// Valid entry
|
||||
_, err = tempFile.WriteString("Seccomp: 0\n")
|
||||
require.Nil(t, err)
|
||||
|
||||
// Unparsable entry
|
||||
_, err = tempFile.WriteString("wrong")
|
||||
require.Nil(t, err)
|
||||
|
||||
return tempFile.Name(), func() {
|
||||
require.Nil(t, os.RemoveAll(tempFile.Name()))
|
||||
}
|
||||
},
|
||||
shouldErr: false,
|
||||
expected: map[string]string{"Seccomp": "0"},
|
||||
},
|
||||
{ // success whole file
|
||||
getFilePath: func() (string, func()) {
|
||||
tempFile, err := ioutil.TempFile("", "parse-status-file-")
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = tempFile.WriteString(statusFile)
|
||||
require.Nil(t, err)
|
||||
|
||||
return tempFile.Name(), func() {
|
||||
require.Nil(t, os.RemoveAll(tempFile.Name()))
|
||||
}
|
||||
},
|
||||
shouldErr: false,
|
||||
expected: map[string]string{
|
||||
"CapAmb": "0000000000000000",
|
||||
"CapBnd": "ffffffffffffffff",
|
||||
"CapEff": "0000000000000000",
|
||||
"CapInh": "0000000000000000",
|
||||
"CapPrm": "0000000000000000",
|
||||
"Cpus_allowed": "00000001",
|
||||
"Cpus_allowed_list": "0",
|
||||
"FDSize": "256",
|
||||
"Gid": "100 100 100 100",
|
||||
"Groups": "16 33 100",
|
||||
"HugetlbPages": "0 kB # 4.4",
|
||||
"Mems_allowed": "1",
|
||||
"Mems_allowed_list": "0",
|
||||
"NSpgid": "17248",
|
||||
"NSpid": "17248",
|
||||
"NSsid": "17200",
|
||||
"NStgid": "17248",
|
||||
"Name": "bash",
|
||||
"Ngid": "0",
|
||||
"NoNewPrivs": "0",
|
||||
"PPid": "17200",
|
||||
"Pid": "17248",
|
||||
"RssAnon": "10264 kB",
|
||||
"RssFile": "3220 kB",
|
||||
"RssShmem": "0 kB",
|
||||
"Seccomp": "0",
|
||||
"ShdPnd": "0000000000000000",
|
||||
"SigBlk": "0000000000010000",
|
||||
"SigCgt": "000000004b813efb",
|
||||
"SigIgn": "0000000000384004",
|
||||
"SigPnd": "0000000000000000",
|
||||
"SigQ": "0/3067",
|
||||
"State": "S (sleeping)",
|
||||
"Tgid": "17248",
|
||||
"Threads": "1",
|
||||
"TracerPid": "0",
|
||||
"Uid": "1000 1000 1000 1000",
|
||||
"Umask": "0022",
|
||||
"VmData": "10332 kB",
|
||||
"VmExe": "992 kB",
|
||||
"VmHWM": "13484 kB",
|
||||
"VmLck": "0 kB",
|
||||
"VmLib": "2104 kB",
|
||||
"VmPMD": "12 kB",
|
||||
"VmPTE": "76 kB",
|
||||
"VmPeak": "131168 kB",
|
||||
"VmPin": "0 kB",
|
||||
"VmRSS": "13484 kB",
|
||||
"VmSize": "131168 kB",
|
||||
"VmStk": "136 kB",
|
||||
"VmSwap": "0 kB",
|
||||
"nonvoluntary_ctxt_switches": "545",
|
||||
"voluntary_ctxt_switches": "150",
|
||||
},
|
||||
},
|
||||
{ // error opening file
|
||||
getFilePath: func() (string, func()) {
|
||||
tempFile, err := ioutil.TempFile("", "parse-status-file-")
|
||||
require.Nil(t, err)
|
||||
|
||||
require.Nil(t, os.RemoveAll(tempFile.Name()))
|
||||
|
||||
return tempFile.Name(), func() {}
|
||||
},
|
||||
shouldErr: true,
|
||||
},
|
||||
} {
|
||||
filePath, cleanup := tc.getFilePath()
|
||||
defer cleanup()
|
||||
res, err := parseStatusFile(filePath)
|
||||
if tc.shouldErr {
|
||||
require.NotNil(t, err)
|
||||
} else {
|
||||
require.Equal(t, tc.expected, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue