mirror of
https://github.com/opencontainers/runc.git
synced 2026-07-11 22:37:14 +08:00
b31a9340f9
Commits1f1e91b1a0and2192670a24added validation for mountpoints to be an absolute path, to match the OCI specs. Unfortunately, the old behavior (accepting the path to be a relative path) has been around for a long time, and although "not according to the spec", various higher level runtimes rely on this behavior. While higher level runtime have been updated to address this requirement, there will be a transition period before all runtimes are updated to carry these fixes. This patch relaxes the validation, to generate a WARNING instead of failing, allowing runtimes to update (but allowing them to update runc to the current version, which includes security fixes). We can remove this exception in a future patch release. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
355 lines
7.5 KiB
Go
355 lines
7.5 KiB
Go
package validate_test
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/opencontainers/runc/libcontainer/configs"
|
|
"github.com/opencontainers/runc/libcontainer/configs/validate"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
func TestValidate(t *testing.T) {
|
|
config := &configs.Config{
|
|
Rootfs: "/var",
|
|
}
|
|
|
|
validator := validate.New()
|
|
err := validator.Validate(config)
|
|
if err != nil {
|
|
t.Errorf("Expected error to not occur: %+v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateWithInvalidRootfs(t *testing.T) {
|
|
dir := "rootfs"
|
|
if err := os.Symlink("/var", dir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.Remove(dir)
|
|
|
|
config := &configs.Config{
|
|
Rootfs: dir,
|
|
}
|
|
|
|
validator := validate.New()
|
|
err := validator.Validate(config)
|
|
if err == nil {
|
|
t.Error("Expected error to occur but it was nil")
|
|
}
|
|
}
|
|
|
|
func TestValidateNetworkWithoutNETNamespace(t *testing.T) {
|
|
network := &configs.Network{Type: "loopback"}
|
|
config := &configs.Config{
|
|
Rootfs: "/var",
|
|
Namespaces: []configs.Namespace{},
|
|
Networks: []*configs.Network{network},
|
|
}
|
|
|
|
validator := validate.New()
|
|
err := validator.Validate(config)
|
|
if err == nil {
|
|
t.Error("Expected error to occur but it was nil")
|
|
}
|
|
}
|
|
|
|
func TestValidateNetworkRoutesWithoutNETNamespace(t *testing.T) {
|
|
route := &configs.Route{Gateway: "255.255.255.0"}
|
|
config := &configs.Config{
|
|
Rootfs: "/var",
|
|
Namespaces: []configs.Namespace{},
|
|
Routes: []*configs.Route{route},
|
|
}
|
|
|
|
validator := validate.New()
|
|
err := validator.Validate(config)
|
|
if err == nil {
|
|
t.Error("Expected error to occur but it was nil")
|
|
}
|
|
}
|
|
|
|
func TestValidateHostname(t *testing.T) {
|
|
config := &configs.Config{
|
|
Rootfs: "/var",
|
|
Hostname: "runc",
|
|
Namespaces: configs.Namespaces(
|
|
[]configs.Namespace{
|
|
{Type: configs.NEWUTS},
|
|
},
|
|
),
|
|
}
|
|
|
|
validator := validate.New()
|
|
err := validator.Validate(config)
|
|
if err != nil {
|
|
t.Errorf("Expected error to not occur: %+v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateHostnameWithoutUTSNamespace(t *testing.T) {
|
|
config := &configs.Config{
|
|
Rootfs: "/var",
|
|
Hostname: "runc",
|
|
}
|
|
|
|
validator := validate.New()
|
|
err := validator.Validate(config)
|
|
if err == nil {
|
|
t.Error("Expected error to occur but it was nil")
|
|
}
|
|
}
|
|
|
|
func TestValidateSecurityWithMaskPaths(t *testing.T) {
|
|
config := &configs.Config{
|
|
Rootfs: "/var",
|
|
MaskPaths: []string{"/proc/kcore"},
|
|
Namespaces: configs.Namespaces(
|
|
[]configs.Namespace{
|
|
{Type: configs.NEWNS},
|
|
},
|
|
),
|
|
}
|
|
|
|
validator := validate.New()
|
|
err := validator.Validate(config)
|
|
if err != nil {
|
|
t.Errorf("Expected error to not occur: %+v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateSecurityWithROPaths(t *testing.T) {
|
|
config := &configs.Config{
|
|
Rootfs: "/var",
|
|
ReadonlyPaths: []string{"/proc/sys"},
|
|
Namespaces: configs.Namespaces(
|
|
[]configs.Namespace{
|
|
{Type: configs.NEWNS},
|
|
},
|
|
),
|
|
}
|
|
|
|
validator := validate.New()
|
|
err := validator.Validate(config)
|
|
if err != nil {
|
|
t.Errorf("Expected error to not occur: %+v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateSecurityWithoutNEWNS(t *testing.T) {
|
|
config := &configs.Config{
|
|
Rootfs: "/var",
|
|
MaskPaths: []string{"/proc/kcore"},
|
|
ReadonlyPaths: []string{"/proc/sys"},
|
|
}
|
|
|
|
validator := validate.New()
|
|
err := validator.Validate(config)
|
|
if err == nil {
|
|
t.Error("Expected error to occur but it was nil")
|
|
}
|
|
}
|
|
|
|
func TestValidateUsernamespace(t *testing.T) {
|
|
if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) {
|
|
t.Skip("Test requires userns.")
|
|
}
|
|
config := &configs.Config{
|
|
Rootfs: "/var",
|
|
Namespaces: configs.Namespaces(
|
|
[]configs.Namespace{
|
|
{Type: configs.NEWUSER},
|
|
},
|
|
),
|
|
}
|
|
|
|
validator := validate.New()
|
|
err := validator.Validate(config)
|
|
if err != nil {
|
|
t.Errorf("expected error to not occur %+v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateUsernamespaceWithoutUserNS(t *testing.T) {
|
|
uidMap := configs.IDMap{ContainerID: 123}
|
|
config := &configs.Config{
|
|
Rootfs: "/var",
|
|
UidMappings: []configs.IDMap{uidMap},
|
|
}
|
|
|
|
validator := validate.New()
|
|
err := validator.Validate(config)
|
|
if err == nil {
|
|
t.Error("Expected error to occur but it was nil")
|
|
}
|
|
}
|
|
|
|
func TestValidateSysctl(t *testing.T) {
|
|
sysctl := map[string]string{
|
|
"fs.mqueue.ctl": "ctl",
|
|
"net.ctl": "ctl",
|
|
"kernel.ctl": "ctl",
|
|
}
|
|
|
|
for k, v := range sysctl {
|
|
config := &configs.Config{
|
|
Rootfs: "/var",
|
|
Sysctl: map[string]string{k: v},
|
|
}
|
|
|
|
validator := validate.New()
|
|
err := validator.Validate(config)
|
|
if err == nil {
|
|
t.Error("Expected error to occur but it was nil")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateValidSysctl(t *testing.T) {
|
|
sysctl := map[string]string{
|
|
"fs.mqueue.ctl": "ctl",
|
|
"net.ctl": "ctl",
|
|
"kernel.msgmax": "ctl",
|
|
}
|
|
|
|
for k, v := range sysctl {
|
|
config := &configs.Config{
|
|
Rootfs: "/var",
|
|
Sysctl: map[string]string{k: v},
|
|
Namespaces: []configs.Namespace{
|
|
{
|
|
Type: configs.NEWNET,
|
|
},
|
|
{
|
|
Type: configs.NEWIPC,
|
|
},
|
|
},
|
|
}
|
|
|
|
validator := validate.New()
|
|
err := validator.Validate(config)
|
|
if err != nil {
|
|
t.Errorf("Expected error to not occur with {%s=%s} but got: %q", k, v, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateSysctlWithSameNs(t *testing.T) {
|
|
config := &configs.Config{
|
|
Rootfs: "/var",
|
|
Sysctl: map[string]string{"net.ctl": "ctl"},
|
|
Namespaces: configs.Namespaces(
|
|
[]configs.Namespace{
|
|
{
|
|
Type: configs.NEWNET,
|
|
Path: "/proc/self/ns/net",
|
|
},
|
|
},
|
|
),
|
|
}
|
|
|
|
validator := validate.New()
|
|
err := validator.Validate(config)
|
|
if err == nil {
|
|
t.Error("Expected error to occur but it was nil")
|
|
}
|
|
}
|
|
|
|
func TestValidateSysctlWithBindHostNetNS(t *testing.T) {
|
|
if os.Getuid() != 0 {
|
|
t.Skip("requires root")
|
|
}
|
|
|
|
const selfnet = "/proc/self/ns/net"
|
|
|
|
dir, err := ioutil.TempDir("", t.Name()+"-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.Remove(dir)
|
|
|
|
file := filepath.Join(dir, "default")
|
|
fd, err := os.Create(file)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.Remove(file)
|
|
fd.Close()
|
|
|
|
if err := unix.Mount(selfnet, file, "bind", unix.MS_BIND, ""); err != nil {
|
|
t.Fatalf("can't bind-mount %s to %s: %s", selfnet, file, err)
|
|
}
|
|
defer func() {
|
|
_ = unix.Unmount(file, unix.MNT_DETACH)
|
|
}()
|
|
|
|
config := &configs.Config{
|
|
Rootfs: "/var",
|
|
Sysctl: map[string]string{"net.ctl": "ctl", "net.foo": "bar"},
|
|
Namespaces: configs.Namespaces(
|
|
[]configs.Namespace{
|
|
{
|
|
Type: configs.NEWNET,
|
|
Path: file,
|
|
},
|
|
},
|
|
),
|
|
}
|
|
|
|
validator := validate.New()
|
|
if err := validator.Validate(config); err == nil {
|
|
t.Error("Expected error to occur but it was nil")
|
|
}
|
|
}
|
|
|
|
func TestValidateSysctlWithoutNETNamespace(t *testing.T) {
|
|
config := &configs.Config{
|
|
Rootfs: "/var",
|
|
Sysctl: map[string]string{"net.ctl": "ctl"},
|
|
Namespaces: []configs.Namespace{},
|
|
}
|
|
|
|
validator := validate.New()
|
|
err := validator.Validate(config)
|
|
if err == nil {
|
|
t.Error("Expected error to occur but it was nil")
|
|
}
|
|
}
|
|
|
|
func TestValidateMounts(t *testing.T) {
|
|
testCases := []struct {
|
|
isErr bool
|
|
dest string
|
|
}{
|
|
// TODO (runc v1.x.x): make these relative paths an error. See https://github.com/opencontainers/runc/pull/3004
|
|
{isErr: false, dest: "not/an/abs/path"},
|
|
{isErr: false, dest: "./rel/path"},
|
|
{isErr: false, dest: "./rel/path"},
|
|
{isErr: false, dest: "../../path"},
|
|
|
|
{isErr: false, dest: "/abs/path"},
|
|
{isErr: false, dest: "/abs/but/../unclean"},
|
|
}
|
|
|
|
validator := validate.New()
|
|
|
|
for _, tc := range testCases {
|
|
config := &configs.Config{
|
|
Rootfs: "/var",
|
|
Mounts: []*configs.Mount{
|
|
{Destination: tc.dest},
|
|
},
|
|
}
|
|
|
|
err := validator.Validate(config)
|
|
if tc.isErr && err == nil {
|
|
t.Errorf("mount dest: %s, expected error, got nil", tc.dest)
|
|
}
|
|
if !tc.isErr && err != nil {
|
|
t.Errorf("mount dest: %s, expected nil, got error %v", tc.dest, err)
|
|
}
|
|
}
|
|
}
|