Move and fix GetContainerRuntime check from bpfd proc (#1996)

* Moved genuinetools/bpfd proc

* Removed unused code from util/proc, added genuinetools/bpfd proc test, fixed naming

* refactor: adjust bpfd container runtime detection

* Moved util/proc.go to util/proc/proc.go, added boilerplate

Co-authored-by: ejose19 <8742215+ejose19@users.noreply.github.com>
This commit is contained in:
Igor Scheller
2022-03-23 11:09:01 -04:00
committed by GitHub
co-authored by ejose19
parent cf5ca26aa4
commit 7b161105a5
8 changed files with 341 additions and 586 deletions
+1 -1
View File
@@ -32,8 +32,8 @@ import (
"github.com/GoogleContainerTools/kaniko/pkg/logging"
"github.com/GoogleContainerTools/kaniko/pkg/timing"
"github.com/GoogleContainerTools/kaniko/pkg/util"
"github.com/GoogleContainerTools/kaniko/pkg/util/proc"
"github.com/containerd/containerd/platforms"
"github.com/genuinetools/bpfd/proc"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
-1
View File
@@ -18,7 +18,6 @@ require (
github.com/chrismellard/docker-credential-acr-env v0.0.0-20220119192733-fe33c00cee21
github.com/containerd/cgroups v1.0.3 // indirect
github.com/docker/docker v20.10.13+incompatible
github.com/genuinetools/bpfd v0.0.2-0.20190525234658-c12d8cd9aac8
github.com/go-git/go-billy/v5 v5.3.1
github.com/go-git/go-git/v5 v5.4.2
github.com/godbus/dbus/v5 v5.0.6 // indirect
-3
View File
@@ -653,9 +653,6 @@ github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXt
github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM=
github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E=
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/genuinetools/bpfd v0.0.2-0.20190525234658-c12d8cd9aac8 h1:4gvqvgeXA3WeVPFfudNMwxdSLT2o7ZOQQUFPeEa86aM=
github.com/genuinetools/bpfd v0.0.2-0.20190525234658-c12d8cd9aac8/go.mod h1:O3IeOSRt7TXBSKb3hmOpQdlusALsEbNF+niGBqgKCs0=
github.com/genuinetools/pkg v0.0.0-20181004225747-e152a0f47ee4/go.mod h1:XTcrCYlXPxnxL2UpnwuRn7tcaTn9HAhxFoFJucootk8=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+204
View File
@@ -0,0 +1,204 @@
/*
Copyright 2022 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Ported from https://github.com/genuinetools/bpfd/blob/a4bfa5e3e9d1bfdbc56268a36a0714911ae9b6bf/proc/proc.go
package proc
import (
"fmt"
"io/ioutil"
"os"
"strings"
)
// ContainerRuntime is the type for the various container runtime strings.
type ContainerRuntime string
const (
// RuntimeDocker is the string for the docker runtime.
RuntimeDocker ContainerRuntime = "docker"
// RuntimeRkt is the string for the rkt runtime.
RuntimeRkt ContainerRuntime = "rkt"
// RuntimeNspawn is the string for the systemd-nspawn runtime.
RuntimeNspawn ContainerRuntime = "systemd-nspawn"
// RuntimeLXC is the string for the lxc runtime.
RuntimeLXC ContainerRuntime = "lxc"
// RuntimeLXCLibvirt is the string for the lxc-libvirt runtime.
RuntimeLXCLibvirt ContainerRuntime = "lxc-libvirt"
// RuntimeOpenVZ is the string for the openvz runtime.
RuntimeOpenVZ ContainerRuntime = "openvz"
// RuntimeKubernetes is the string for the kubernetes runtime.
RuntimeKubernetes ContainerRuntime = "kube"
// RuntimeGarden is the string for the garden runtime.
RuntimeGarden ContainerRuntime = "garden"
// RuntimePodman is the string for the podman runtime.
RuntimePodman ContainerRuntime = "podman"
// RuntimeGVisor is the string for the gVisor (runsc) runtime.
RuntimeGVisor ContainerRuntime = "gvisor"
// RuntimeFirejail is the string for the firejail runtime.
RuntimeFirejail ContainerRuntime = "firejail"
// RuntimeWSL is the string for the Windows Subsystem for Linux runtime.
RuntimeWSL ContainerRuntime = "wsl"
// RuntimeNotFound is the string for when no container runtime is found.
RuntimeNotFound ContainerRuntime = "not-found"
)
var (
// ContainerRuntimes contains all the container runtimes.
ContainerRuntimes = []ContainerRuntime{
RuntimeDocker,
RuntimeRkt,
RuntimeNspawn,
RuntimeLXC,
RuntimeLXCLibvirt,
RuntimeOpenVZ,
RuntimeKubernetes,
RuntimeGarden,
RuntimePodman,
RuntimeGVisor,
RuntimeFirejail,
RuntimeWSL,
}
)
// GetContainerRuntime returns the container runtime the process is running in.
// If pid is less than one, it returns the runtime for "self".
func GetContainerRuntime(tgid, pid int) ContainerRuntime {
file := "/proc/self/cgroup"
if pid > 0 {
if tgid > 0 {
file = fmt.Sprintf("/proc/%d/task/%d/cgroup", tgid, pid)
} else {
file = fmt.Sprintf("/proc/%d/cgroup", pid)
}
}
// read the cgroups file
a := readFileString(file)
runtime := getContainerRuntime(a)
if runtime != RuntimeNotFound {
return runtime
}
// /proc/vz exists in container and outside of the container, /proc/bc only outside of the container.
if osFileExists("/proc/vz") && !osFileExists("/proc/bc") {
return RuntimeOpenVZ
}
// /__runsc_containers__ directory is present in gVisor containers.
if osFileExists("/__runsc_containers__") {
return RuntimeGVisor
}
// firejail runs with `firejail` as pid 1.
// As firejail binary cannot be run with argv[0] != "firejail"
// it's okay to rely on cmdline.
a = readFileString("/proc/1/cmdline")
runtime = getContainerRuntime(a)
if runtime != RuntimeNotFound {
return runtime
}
// WSL has /proc/version_signature starting with "Microsoft".
a = readFileString("/proc/version_signature")
if strings.HasPrefix(a, "Microsoft") {
return RuntimeWSL
}
a = os.Getenv("container")
runtime = getContainerRuntime(a)
if runtime != RuntimeNotFound {
return runtime
}
// PID 1 might have dropped this information into a file in /run.
// Read from /run/systemd/container since it is better than accessing /proc/1/environ,
// which needs CAP_SYS_PTRACE
a = readFileString("/run/systemd/container")
runtime = getContainerRuntime(a)
if runtime != RuntimeNotFound {
return runtime
}
// Check for container specific files
runtime = detectContainerFiles()
if runtime != RuntimeNotFound {
return runtime
}
return RuntimeNotFound
}
// Related implementation: https://github.com/systemd/systemd/blob/6604fb0207ee10e8dc05d67f6fe45de0b193b5c4/src/basic/virt.c#L523-L549
func detectContainerFiles() ContainerRuntime {
files := []struct {
runtime ContainerRuntime
location string
}{
// https://github.com/containers/podman/issues/6192
// https://github.com/containers/podman/issues/3586#issuecomment-661918679
{RuntimePodman, "/run/.containerenv"},
// https://github.com/moby/moby/issues/18355
{RuntimeDocker, "/.dockerenv"},
}
for i := range files {
if osFileExists(files[i].location) {
return files[i].runtime
}
}
return RuntimeNotFound
}
func getContainerRuntime(input string) ContainerRuntime {
if len(strings.TrimSpace(input)) < 1 {
return RuntimeNotFound
}
for _, runtime := range ContainerRuntimes {
if strings.Contains(input, string(runtime)) {
return runtime
}
}
return RuntimeNotFound
}
func osFileExists(file string) bool {
if _, err := os.Stat(file); !os.IsNotExist(err) {
return true
}
return false
}
func readFile(file string) []byte {
if !osFileExists(file) {
return nil
}
b, _ := ioutil.ReadFile(file)
return b
}
func readFileString(file string) string {
b := readFile(file)
if b == nil {
return ""
}
return strings.TrimSpace(string(b))
}
+136
View File
@@ -0,0 +1,136 @@
/*
Copyright 2022 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package proc
import (
"testing"
)
func TestGetContainerRuntime(t *testing.T) {
testcases := map[string]struct {
expectedRuntime ContainerRuntime
input string
}{
"empty": {
expectedRuntime: RuntimeNotFound,
},
"typical docker": {
expectedRuntime: RuntimeDocker,
input: `11:pids:/docker/68fad1f9e0985989408aff30e7b83e7dada1d235ff46a22c5465ca193ddf0fac
10:devices:/docker/68fad1f9e0985989408aff30e7b83e7dada1d235ff46a22c5465ca193ddf0fac
9:freezer:/docker/68fad1f9e0985989408aff30e7b83e7dada1d235ff46a22c5465ca193ddf0fac
8:net_cls,net_prio:/docker/68fad1f9e0985989408aff30e7b83e7dada1d235ff46a22c5465ca193ddf0fac
7:perf_event:/docker/68fad1f9e0985989408aff30e7b83e7dada1d235ff46a22c5465ca193ddf0fac
6:cpuset:/docker/68fad1f9e0985989408aff30e7b83e7dada1d235ff46a22c5465ca193ddf0fac
5:memory:/docker/68fad1f9e0985989408aff30e7b83e7dada1d235ff46a22c5465ca193ddf0fac
4:blkio:/docker/68fad1f9e0985989408aff30e7b83e7dada1d235ff46a22c5465ca193ddf0fac
3:cpu,cpuacct:/docker/68fad1f9e0985989408aff30e7b83e7dada1d235ff46a22c5465ca193ddf0fac
2:hugetlb:/docker/68fad1f9e0985989408aff30e7b83e7dada1d235ff46a22c5465ca193ddf0fac
1:name=systemd:/docker/68fad1f9e0985989408aff30e7b83e7dada1d235ff46a22c5465ca193ddf0fac
0::/system.slice/containerd.service`,
},
"uncontainerized process": {
expectedRuntime: RuntimeNotFound,
input: `11:pids:/system.slice/ssh.service
10:devices:/system.slice/ssh.service
9:freezer:/
8:net_cls,net_prio:/
7:perf_event:/
6:cpuset:/
5:memory:/system.slice/ssh.service
4:blkio:/system.slice/ssh.service
3:cpu,cpuacct:/system.slice/ssh.service
2:hugetlb:/
1:name=systemd:/system.slice/ssh.service
0::/system.slice/ssh.service`,
},
"kubernetes": {
expectedRuntime: RuntimeKubernetes,
input: `12:perf_event:/kubepods/burstable/pod98051bd2-a5fa-11e8-9bb9-0a58ac1f31f2/74998d19bd3c7423744214c344c6e814d19b7908a92f165f9d58243073a27a47
11:freezer:/kubepods/burstable/pod98051bd2-a5fa-11e8-9bb9-0a58ac1f31f2/74998d19bd3c7423744214c344c6e814d19b7908a92f165f9d58243073a27a47
10:pids:/kubepods/burstable/pod98051bd2-a5fa-11e8-9bb9-0a58ac1f31f2/74998d19bd3c7423744214c344c6e814d19b7908a92f165f9d58243073a27a47
9:net_cls,net_prio:/kubepods/burstable/pod98051bd2-a5fa-11e8-9bb9-0a58ac1f31f2/74998d19bd3c7423744214c344c6e814d19b7908a92f165f9d58243073a27a47
8:memory:/kubepods/burstable/pod98051bd2-a5fa-11e8-9bb9-0a58ac1f31f2/74998d19bd3c7423744214c344c6e814d19b7908a92f165f9d58243073a27a47
7:cpuset:/kubepods/burstable/pod98051bd2-a5fa-11e8-9bb9-0a58ac1f31f2/74998d19bd3c7423744214c344c6e814d19b7908a92f165f9d58243073a27a47
6:devices:/kubepods/burstable/pod98051bd2-a5fa-11e8-9bb9-0a58ac1f31f2/74998d19bd3c7423744214c344c6e814d19b7908a92f165f9d58243073a27a47
5:blkio:/kubepods/burstable/pod98051bd2-a5fa-11e8-9bb9-0a58ac1f31f2/74998d19bd3c7423744214c344c6e814d19b7908a92f165f9d58243073a27a47
4:rdma:/
3:hugetlb:/kubepods/burstable/pod98051bd2-a5fa-11e8-9bb9-0a58ac1f31f2/74998d19bd3c7423744214c344c6e814d19b7908a92f165f9d58243073a27a47
2:cpu,cpuacct:/kubepods/burstable/pod98051bd2-a5fa-11e8-9bb9-0a58ac1f31f2/74998d19bd3c7423744214c344c6e814d19b7908a92f165f9d58243073a27a47
1:name=systemd:/kubepods/burstable/pod98051bd2-a5fa-11e8-9bb9-0a58ac1f31f2/74998d19bd3c7423744214c344c6e814d19b7908a92f165f9d58243073a27a47`,
},
"lxc": {
expectedRuntime: RuntimeLXC, // this is usually in $container for lxc
input: `10:cpuset:/lxc/debian2
9:pids:/lxc/debian2
8:devices:/lxc/debian2
7:net_cls,net_prio:/lxc/debian2
6:freezer:/lxc/debian2
5:blkio:/lxc/debian2
4:memory:/lxc/debian2
3:cpu,cpuacct:/lxc/debian2
2:perf_event:/lxc/debian2
1:name=systemd:/lxc/debian2`,
},
"nspawn": {
expectedRuntime: RuntimeNotFound, // since this variable is in $container
input: `10:cpuset:/
9:pids:/machine.slice/machine-nspawntest.scope
8:devices:/machine.slice/machine-nspawntest.scope
7:net_cls,net_prio:/
6:freezer:/user/root/0
5:blkio:/machine.slice/machine-nspawntest.scope
4:memory:/machine.slice/machine-nspawntest.scope
3:cpu,cpuacct:/machine.slice/machine-nspawntest.scope
2:perf_event:/
1:name=systemd:/machine.slice/machine-nspawntest.scope`,
},
"rkt": {
expectedRuntime: RuntimeRkt,
input: `10:cpuset:/
9:pids:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice/debian.service
8:devices:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice/debian.service
7:net_cls,net_prio:/
6:freezer:/user/root/0
5:blkio:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice
4:memory:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice
3:cpu,cpuacct:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice
2:perf_event:/
1:name=systemd:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice/debian.service`,
},
"rkt host": {
expectedRuntime: RuntimeRkt,
input: `10:cpuset:/
9:pids:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice/debian.service
8:devices:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice/debian.service
7:net_cls,net_prio:/
6:freezer:/user/root/0
5:blkio:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice
4:memory:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice
3:cpu,cpuacct:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice
2:perf_event:/
1:name=systemd:/machine.slice/machine-rkt\x2dbfb7d57e\x2d80ff\x2d4ef8\x2db602\x2d9b907b3f3a38.scope/system.slice/debian.service`,
},
}
for key, tc := range testcases {
runtime := getContainerRuntime(tc.input)
if runtime != tc.expectedRuntime {
t.Errorf("[%s]: expected runtime %q, got %q", key, tc.expectedRuntime, runtime)
}
}
}
-21
View File
@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2018 The Genuinetools Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-557
View File
@@ -1,557 +0,0 @@
// Package proc provides tools for inspecting proc.
package proc
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"regexp"
"strconv"
"strings"
"syscall"
"github.com/syndtr/gocapability/capability"
"golang.org/x/sys/unix"
)
// ContainerRuntime is the type for the various container runtime strings.
type ContainerRuntime string
// SeccompMode is the type for the various seccomp mode strings.
type SeccompMode string
const (
// RuntimeDocker is the string for the docker runtime.
RuntimeDocker ContainerRuntime = "docker"
// RuntimeRkt is the string for the rkt runtime.
RuntimeRkt ContainerRuntime = "rkt"
// RuntimeNspawn is the string for the systemd-nspawn runtime.
RuntimeNspawn ContainerRuntime = "systemd-nspawn"
// RuntimeLXC is the string for the lxc runtime.
RuntimeLXC ContainerRuntime = "lxc"
// RuntimeLXCLibvirt is the string for the lxc-libvirt runtime.
RuntimeLXCLibvirt ContainerRuntime = "lxc-libvirt"
// RuntimeOpenVZ is the string for the openvz runtime.
RuntimeOpenVZ ContainerRuntime = "openvz"
// RuntimeKubernetes is the string for the kubernetes runtime.
RuntimeKubernetes ContainerRuntime = "kube"
// RuntimeGarden is the string for the garden runtime.
RuntimeGarden ContainerRuntime = "garden"
// RuntimePodman is the string for the podman runtime.
RuntimePodman ContainerRuntime = "podman"
// RuntimeGVisor is the string for the gVisor (runsc) runtime.
RuntimeGVisor ContainerRuntime = "gvisor"
// RuntimeFirejail is the string for the firejail runtime.
RuntimeFirejail ContainerRuntime = "firejail"
// RuntimeWSL is the string for the Windows Subsystem for Linux runtime.
RuntimeWSL ContainerRuntime = "wsl"
// RuntimeNotFound is the string for when no container runtime is found.
RuntimeNotFound ContainerRuntime = "not-found"
// SeccompModeDisabled is equivalent to "0" in the /proc/{pid}/status file.
SeccompModeDisabled SeccompMode = "disabled"
// SeccompModeStrict is equivalent to "1" in the /proc/{pid}/status file.
SeccompModeStrict SeccompMode = "strict"
// SeccompModeFiltering is equivalent to "2" in the /proc/{pid}/status file.
SeccompModeFiltering SeccompMode = "filtering"
apparmorUnconfined = "unconfined"
uint32Max = 4294967295
cgroupContainerID = ":(/docker/|/kube.*/.*/|/kube.*/.*/.*/.*/|/system.slice/docker-|/machine.slice/machine-rkt-|/machine.slice/machine-|/lxc/|/lxc-libvirt/|/garden/|/podman/)([[:alnum:]\\-]{1,64})(.scope|$)"
statusFileValue = ":(.*)"
)
var (
// ContainerRuntimes contains all the container runtimes.
ContainerRuntimes = []ContainerRuntime{
RuntimeDocker,
RuntimeRkt,
RuntimeNspawn,
RuntimeLXC,
RuntimeLXCLibvirt,
RuntimeOpenVZ,
RuntimeKubernetes,
RuntimeGarden,
RuntimePodman,
RuntimeGVisor,
RuntimeFirejail,
RuntimeWSL,
}
seccompModes = map[string]SeccompMode{
"0": SeccompModeDisabled,
"1": SeccompModeStrict,
"2": SeccompModeFiltering,
}
cgroupContainerIDRegex = regexp.MustCompile(cgroupContainerID)
statusFileValueRegex = regexp.MustCompile(statusFileValue)
)
// GetContainerRuntime returns the container runtime the process is running in.
// If pid is less than one, it returns the runtime for "self".
func GetContainerRuntime(tgid, pid int) ContainerRuntime {
file := "/proc/self/cgroup"
if pid > 0 {
if tgid > 0 {
file = fmt.Sprintf("/proc/%d/task/%d/cgroup", tgid, pid)
} else {
file = fmt.Sprintf("/proc/%d/cgroup", pid)
}
}
// read the cgroups file
a := readFileString(file)
runtime := getContainerRuntime(a)
if runtime != RuntimeNotFound {
return runtime
}
// /proc/vz exists in container and outside of the container, /proc/bc only outside of the container.
if fileExists("/proc/vz") && !fileExists("/proc/bc") {
return RuntimeOpenVZ
}
// /__runsc_containers__ directory is present in gVisor containers.
if fileExists("/__runsc_containers__") {
return RuntimeGVisor
}
// firejail runs with `firejail` as pid 1.
// As firejail binary cannot be run with argv[0] != "firejail"
// it's okay to rely on cmdline.
a = readFileString("/proc/1/cmdline")
runtime = getContainerRuntime(a)
if runtime != RuntimeNotFound {
return runtime
}
// WSL has /proc/version_signature starting with "Microsoft".
a = readFileString("/proc/version_signature")
if strings.HasPrefix(a, "Microsoft") {
return RuntimeWSL
}
a = os.Getenv("container")
runtime = getContainerRuntime(a)
if runtime != RuntimeNotFound {
return runtime
}
// PID 1 might have dropped this information into a file in /run.
// Read from /run/systemd/container since it is better than accessing /proc/1/environ,
// which needs CAP_SYS_PTRACE
a = readFileString("/run/systemd/container")
runtime = getContainerRuntime(a)
if runtime != RuntimeNotFound {
return runtime
}
return RuntimeNotFound
}
func getContainerRuntime(input string) ContainerRuntime {
if len(strings.TrimSpace(input)) < 1 {
return RuntimeNotFound
}
for _, runtime := range ContainerRuntimes {
if strings.Contains(input, string(runtime)) {
return runtime
}
}
return RuntimeNotFound
}
// GetContainerID returns the container ID for a process if it's running in a container.
// If pid is less than one, it returns the container ID for "self".
func GetContainerID(tgid, pid int) string {
file := "/proc/self/cgroup"
if pid > 0 {
if tgid > 0 {
file = fmt.Sprintf("/proc/%d/task/%d/cgroup", tgid, pid)
} else {
file = fmt.Sprintf("/proc/%d/cgroup", pid)
}
}
return getContainerID(readFileString(file))
}
func getContainerID(input string) string {
if len(strings.TrimSpace(input)) < 1 {
return ""
}
// rkt encodes the dashes as ascii, replace them.
input = strings.Replace(input, `\x2d`, "-", -1)
lines := strings.Split(input, "\n")
for _, line := range lines {
matches := cgroupContainerIDRegex.FindStringSubmatch(line)
if len(matches) > 2 {
return matches[2]
}
}
return ""
}
// GetAppArmorProfile determines the AppArmor profile for a process.
// If pid is less than one, it returns the AppArmor profile for "self".
func GetAppArmorProfile(pid int) string {
file := "/proc/self/attr/current"
if pid > 0 {
file = fmt.Sprintf("/proc/%d/attr/current", pid)
}
f := readFileString(file)
if f == "" {
return apparmorUnconfined
}
return f
}
// UserMapping holds the values for a {uid,gid}_map.
type UserMapping struct {
ContainerID int64
HostID int64
Range int64
}
// GetUserNamespaceInfo determines if the process is running in a UserNamespace
// and returns the mappings if true.
// If pid is less than one, it returns the user namespace info for "self".
func GetUserNamespaceInfo(pid int) (bool, []UserMapping) {
file := "/proc/self/uid_map"
if pid > 0 {
file = fmt.Sprintf("/proc/%d/uid_map", pid)
}
f := readFileString(file)
if len(f) < 0 {
// user namespace is uninitialized
return true, nil
}
userNs, mappings, err := readUserMappings(f)
if err != nil {
return false, nil
}
return userNs, mappings
}
func readUserMappings(f string) (iuserNS bool, mappings []UserMapping, err error) {
parts := strings.Split(f, " ")
parts = deleteEmpty(parts)
if len(parts) < 3 {
return false, nil, nil
}
for i := 0; i < len(parts); i += 3 {
nsu, hu, r := parts[i], parts[i+1], parts[i+2]
mapping := UserMapping{}
mapping.ContainerID, err = strconv.ParseInt(nsu, 10, 0)
if err != nil {
return false, nil, nil
}
mapping.HostID, err = strconv.ParseInt(hu, 10, 0)
if err != nil {
return false, nil, nil
}
mapping.Range, err = strconv.ParseInt(r, 10, 0)
if err != nil {
return false, nil, nil
}
if mapping.ContainerID == 0 && mapping.HostID == 0 && mapping.Range == uint32Max {
return false, nil, nil
}
mappings = append(mappings, mapping)
}
return true, mappings, nil
}
// GetCapabilities returns the allowed capabilities for the process.
// If pid is less than one, it returns the capabilities for "self".
func GetCapabilities(pid int) (map[string][]string, error) {
allCaps := capability.List()
caps, err := capability.NewPid(pid)
if err != nil {
return nil, err
}
allowedCaps := map[string][]string{}
allowedCaps["EFFECTIVE | PERMITTED | INHERITABLE"] = []string{}
allowedCaps["BOUNDING"] = []string{}
allowedCaps["AMBIENT"] = []string{}
for _, cap := range allCaps {
if caps.Get(capability.CAPS, cap) {
allowedCaps["EFFECTIVE | PERMITTED | INHERITABLE"] = append(allowedCaps["EFFECTIVE | PERMITTED | INHERITABLE"], cap.String())
}
if caps.Get(capability.BOUNDING, cap) {
allowedCaps["BOUNDING"] = append(allowedCaps["BOUNDING"], cap.String())
}
if caps.Get(capability.AMBIENT, cap) {
allowedCaps["AMBIENT"] = append(allowedCaps["AMBIENT"], cap.String())
}
}
return allowedCaps, nil
}
// GetUIDGID returns the uid and gid for a process.
// If pid is less than one, it returns the seccomp enforcing mode for "self".
func GetUIDGID(tgid, pid int) (uint32, uint32, error) {
file := "/proc/self/status"
if pid > 0 {
if tgid > 0 {
file = fmt.Sprintf("/proc/%d/task/%d/status", tgid, pid)
} else {
file = fmt.Sprintf("/proc/%d/status", pid)
}
}
return getUIDGID(readFileString(file))
}
func getUIDGID(input string) (uint32, uint32, error) {
// Split status file string by line
statusMappings := strings.Split(input, "\n")
statusMappings = deleteEmpty(statusMappings)
var uid, gid string
for _, line := range statusMappings {
if strings.Contains(line, "Uid:") {
matches := statusFileValueRegex.FindStringSubmatch(line)
if len(matches) > 1 {
uid = matches[1]
continue
}
}
if strings.Contains(line, "Gid:") {
matches := statusFileValueRegex.FindStringSubmatch(line)
if len(matches) > 1 {
gid = matches[1]
continue
}
}
if len(uid) > 0 && len(gid) > 0 {
break
}
}
if len(uid) < 1 && len(gid) < 1 {
return 0, 0, nil
}
u, err := strconv.Atoi(strings.Split(strings.Split(strings.TrimSpace(uid), " ")[0], "\t")[0])
if err != nil {
return 0, 0, err
}
g, err := strconv.Atoi(strings.Split(strings.Split(strings.TrimSpace(gid), " ")[0], "\t")[0])
if err != nil {
return 0, 0, err
}
return uint32(u), uint32(g), nil
}
// GetSeccompEnforcingMode returns the seccomp enforcing level (disabled, filtering, strict)
// for a process.
// If pid is less than one, it returns the seccomp enforcing mode for "self".
func GetSeccompEnforcingMode(pid int) SeccompMode {
file := "/proc/self/status"
if pid > 0 {
file = fmt.Sprintf("/proc/%d/status", pid)
}
return getSeccompEnforcingMode(readFileString(file))
}
func getSeccompEnforcingMode(input string) SeccompMode {
mode := getStatusEntry(input, "Seccomp:")
sm, ok := seccompModes[mode]
if ok {
return sm
}
// Pre linux 3.8, check if Seccomp is supported, via CONFIG_SECCOMP.
if err := unix.Prctl(unix.PR_GET_SECCOMP, 0, 0, 0, 0); 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); err != unix.EINVAL {
return SeccompModeStrict
}
}
return SeccompModeDisabled
}
// GetNoNewPrivileges returns if no_new_privileges is set
// for a process.
// If pid is less than one, it returns if set for "self".
func GetNoNewPrivileges(pid int) bool {
file := "/proc/self/status"
if pid > 0 {
file = fmt.Sprintf("/proc/%d/status", pid)
}
return getNoNewPrivileges(readFileString(file))
}
func getNoNewPrivileges(input string) bool {
return getStatusEntry(input, "NoNewPrivs:") == "1"
}
// GetCmdline returns the cmdline for a process.
// If pid is less than one, it returns the cmdline for "self".
func GetCmdline(pid int) []string {
file := "/proc/self/cmdline"
if pid > 0 {
file = fmt.Sprintf("/proc/%d/cmdline", pid)
}
return parseProcFile(readFile(file))
}
// GetEnviron returns the environ for a process.
// If pid is less than one, it returns the environ for "self".
func GetEnviron(pid int) []string {
file := "/proc/self/environ"
if pid > 0 {
file = fmt.Sprintf("/proc/%d/environ", pid)
}
return parseProcFile(readFile(file))
}
// GetCwd returns the current working directory for the process.
// If pid is less than one, it returns the current working directory for "self".
func GetCwd(pid int) string {
file := "/proc/self/cwd"
if pid > 0 {
file = fmt.Sprintf("/proc/%d/cwd", pid)
}
cwd, err := os.Readlink(file)
if err != nil {
if os.IsPermission(err) {
// Ignore the permission errors or the logs are noisy.
return ""
}
// Ignore errors in general.
return ""
}
return cwd
}
// TODO: make this function more efficient and read the file line by line.
func getStatusEntry(input, find string) string {
// Split status file string by line
statusMappings := strings.Split(input, "\n")
statusMappings = deleteEmpty(statusMappings)
for _, line := range statusMappings {
if strings.Contains(line, find) {
matches := statusFileValueRegex.FindStringSubmatch(line)
if len(matches) > 1 {
return strings.TrimSpace(matches[1])
}
}
}
return ""
}
func fileExists(file string) bool {
if _, err := os.Stat(file); !os.IsNotExist(err) {
return true
}
return false
}
func readFile(file string) []byte {
if !fileExists(file) {
return nil
}
b, _ := ioutil.ReadFile(file)
return b
}
func readFileString(file string) string {
b := readFile(file)
if b == nil {
return ""
}
return strings.TrimSpace(string(b))
}
func deleteEmpty(s []string) []string {
var r []string
for _, str := range s {
if strings.TrimSpace(str) != "" {
r = append(r, strings.TrimSpace(str))
}
}
return r
}
func parseProcFile(data []byte) []string {
if len(data) < 1 {
return nil
}
if data[len(data)-1] == 0 {
data = data[:len(data)-1]
}
parts := bytes.Split(data, []byte{0})
var strParts []string
for _, p := range parts {
strParts = append(strParts, string(p))
}
return strParts
}
// IsValidContainerRuntime checks if a string is a valid container runtime.
func IsValidContainerRuntime(s string) bool {
for _, b := range ContainerRuntimes {
if string(b) == s {
return true
}
}
return false
}
// HasNamespace determines if a container is using a particular namespace or the
// host namespace.
// The device number of an unnamespaced /proc/1/ns/{ns} is 4 and anything else is
// higher.
// Only works from inside a container.
func HasNamespace(ns string) (bool, error) {
file := fmt.Sprintf("/proc/1/ns/%s", ns)
// Use Lstat to not follow the symlink.
var info syscall.Stat_t
if err := syscall.Lstat(file, &info); err != nil {
return false, &os.PathError{Op: "lstat", Path: file, Err: err}
}
// Get the device number. If it is higher than 4 it is in a namespace.
if info.Dev > 4 {
return true, nil
}
return false, nil
}
-3
View File
@@ -445,9 +445,6 @@ github.com/emirpasic/gods/utils
# github.com/fsnotify/fsnotify v1.5.1
## explicit; go 1.13
github.com/fsnotify/fsnotify
# github.com/genuinetools/bpfd v0.0.2-0.20190525234658-c12d8cd9aac8
## explicit
github.com/genuinetools/bpfd/proc
# github.com/go-git/gcfg v1.5.0
## explicit
github.com/go-git/gcfg