You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by gi...@apache.org on 2019/01/28 07:52:40 UTC

[mesos] 09/11: Added Seccomp parser tests.

This is an automated email from the ASF dual-hosted git repository.

gilbert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mesos.git

commit 90ddd116fabde038a5726c69d3dda8da3c884855
Author: Andrei Budnik <ab...@mesosphere.com>
AuthorDate: Sun Jan 27 23:52:05 2019 -0800

    Added Seccomp parser tests.
    
    See summary.
    
    Review: https://reviews.apache.org/r/69409/
---
 src/Makefile.am                                    |   5 +
 src/tests/CMakeLists.txt                           |   5 +
 .../containerizer/linux_seccomp_parser_tests.cpp   | 543 +++++++++++++++++++++
 src/tests/environment.cpp                          |  35 ++
 4 files changed, 588 insertions(+)

diff --git a/src/Makefile.am b/src/Makefile.am
index 46e943e..78f3e85 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2651,6 +2651,11 @@ mesos_tests_SOURCES +=						\
   tests/containerizer/xfs_quota_tests.cpp
 endif
 
+if ENABLE_SECCOMP_ISOLATOR
+mesos_tests_SOURCES +=						\
+  tests/containerizer/linux_seccomp_parser_tests.cpp
+endif
+
 mesos_tests_CPPFLAGS = $(MESOS_CPPFLAGS)
 mesos_tests_CPPFLAGS += -DSOURCE_DIR=\"$(abs_top_srcdir)\"
 mesos_tests_CPPFLAGS += -DBUILD_DIR=\"$(abs_top_builddir)\"
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index 7a1265e..ceaa119 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -244,6 +244,11 @@ if (LINUX)
       containerizer/xfs_quota_tests.cpp)
   endif()
 
+  if (ENABLE_SECCOMP_ISOLATOR)
+    list(APPEND MESOS_TESTS_SRC
+      containerizer/linux_seccomp_parser_tests.cpp)
+  endif()
+
   if (ENABLE_LINUX_ROUTING)
     list(APPEND MESOS_TESTS_SRC
       containerizer/routing_tests.cpp)
diff --git a/src/tests/containerizer/linux_seccomp_parser_tests.cpp b/src/tests/containerizer/linux_seccomp_parser_tests.cpp
new file mode 100644
index 0000000..0949332
--- /dev/null
+++ b/src/tests/containerizer/linux_seccomp_parser_tests.cpp
@@ -0,0 +1,543 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+#include <gtest/gtest.h>
+
+#include <stout/gtest.hpp>
+#include <stout/try.hpp>
+
+#include "linux/capabilities.hpp"
+#include "linux/seccomp/seccomp_parser.hpp"
+
+using mesos::seccomp::ContainerSeccompProfile;
+
+using std::multiset;
+using std::string;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+class SeccompParserTest : public ::testing::Test {};
+
+
+// This test verifies that the Seccomp parser can parse a valid Seccomp profile.
+TEST_F(SeccompParserTest, SECCOMP_ParseSeccompProfile)
+{
+  const string config =
+    R"~(
+    {
+      "defaultAction": "SCMP_ACT_ERRNO",
+      "archMap": [
+        {
+          "architecture": "SCMP_ARCH_X86_64",
+          "subArchitectures": [
+            "SCMP_ARCH_X86",
+            "SCMP_ARCH_X32"
+          ]
+        },
+        {
+          "architecture": "SCMP_ARCH_AARCH64",
+          "subArchitectures": [
+            "SCMP_ARCH_ARM"
+          ]
+        }
+      ],
+      "syscalls": [
+        {
+          "names": [
+            "accept",
+            "writev"
+          ],
+          "action": "SCMP_ACT_ALLOW",
+          "args": [],
+          "comment": "",
+          "includes": {},
+          "excludes": {}
+        },
+        {
+          "names": [
+            "clone"
+          ],
+          "action": "SCMP_ACT_ALLOW",
+          "args": [
+            {
+              "index": 0,
+              "value": 2080505856,
+              "valueTwo": 0,
+              "op": "SCMP_CMP_MASKED_EQ"
+            }
+          ],
+          "comment": "",
+          "includes": {},
+          "excludes": {
+            "caps": [
+              "CAP_SYS_ADMIN"
+            ],
+            "arches": [
+              "s390",
+              "s390x"
+            ]
+          }
+        }
+      ]
+    })~";
+
+  Try<ContainerSeccompProfile> profile =
+    mesos::internal::seccomp::parseProfileData(config);
+
+  ASSERT_SOME(profile);
+
+  EXPECT_EQ(profile->default_action(),
+            ContainerSeccompProfile::Syscall::ACT_ERRNO);
+
+  // Note that the Seccomp parser doesn't add an architecture with all its
+  // subarchitectures when the architecture is not native.
+  const auto& architectures = profile->architectures();
+  EXPECT_EQ(multiset<int>(architectures.begin(), architectures.end()),
+            multiset<int>({ContainerSeccompProfile::ARCH_X86_64,
+                           ContainerSeccompProfile::ARCH_X86,
+                           ContainerSeccompProfile::ARCH_X32}));
+
+  EXPECT_EQ(profile->syscalls_size(), 2);
+
+  {
+    const auto& rule = profile->syscalls().Get(0);
+
+    const auto& names = rule.names();
+    EXPECT_EQ(multiset<string>(names.begin(), names.end()),
+              multiset<string>({"accept", "writev"}));
+
+    EXPECT_EQ(rule.action(), ContainerSeccompProfile::Syscall::ACT_ALLOW);
+    EXPECT_EQ(rule.args_size(), 0);
+    EXPECT_FALSE(rule.has_includes());
+    EXPECT_FALSE(rule.has_excludes());
+  }
+
+  {
+    const auto& rule = profile->syscalls().Get(1);
+
+    const auto& names = rule.names();
+    EXPECT_EQ(multiset<string>(names.begin(), names.end()),
+              multiset<string>({"clone"}));
+
+    EXPECT_EQ(rule.action(), ContainerSeccompProfile::Syscall::ACT_ALLOW);
+
+    EXPECT_EQ(rule.args_size(), 1);
+
+    const auto& arg = rule.args().Get(0);
+    EXPECT_EQ(arg.index(), 0u);
+    EXPECT_EQ(arg.value(), 2080505856u);
+    EXPECT_EQ(arg.value_two(), 0u);
+    EXPECT_EQ(arg.op(), ContainerSeccompProfile::Syscall::Arg::CMP_MASKED_EQ);
+
+    EXPECT_FALSE(rule.has_includes());
+
+    EXPECT_TRUE(rule.has_excludes());
+    EXPECT_EQ(rule.excludes().capabilities_size(), 1);
+    EXPECT_EQ(rule.excludes().capabilities().Get(0), CapabilityInfo::SYS_ADMIN);
+  }
+}
+
+
+// This test verifies handling of errors caused by an invalid Seccomp profile.
+TEST_F(SeccompParserTest, SECCOMP_ParseInvalidSeccompProfile)
+{
+  auto check = [](const string& config, const string& failureMessage) {
+    CHECK(!failureMessage.empty());
+
+    Try<ContainerSeccompProfile> profile =
+      mesos::internal::seccomp::parseProfileData(config);
+
+    ASSERT_ERROR(profile);
+    EXPECT_TRUE(strings::contains(profile.error(), failureMessage));
+  };
+
+  // Check for the existence of the top-most objects.
+  check(
+      R"~(
+      {
+        "defaultAction!!BUG": "SCMP_ACT_ERRNO",
+        "archMap": [],
+        "syscalls": []
+      })~",
+      "Cannot determine 'defaultAction'");
+
+  check(
+      R"~(
+      {
+        "defaultAction": "SCMP_ACT_ERRNO",
+        "archMap!!BUG": [],
+        "syscalls": []
+      })~",
+      "Cannot determine 'archMap'");
+
+  check(
+      R"~(
+      {
+        "defaultAction": "SCMP_ACT_ERRNO",
+        "archMap": [],
+        "syscalls!!BUG": []
+      })~",
+      "Cannot determine 'syscalls'");
+
+  // Check `defaultAction` object.
+  check(
+      R"~(
+      {
+        "defaultAction": "SCMP_ACT_!!BUG",
+        "archMap": [],
+        "syscalls": []
+      })~",
+      "Unknown syscall action: 'SCMP_ACT_!!BUG'");
+
+  // Check `archMap` object.
+  check(
+      R"~(
+      {
+        "defaultAction": "SCMP_ACT_ERRNO",
+        "archMap": [
+          {
+            "architecture": "SCMP_ARCH_!!BUG",
+            "subArchitectures": []
+          }
+        ],
+        "syscalls": []
+      })~",
+      "Unknown architecture: 'SCMP_ARCH_!!BUG'");
+
+  check(
+      R"~(
+      {
+        "defaultAction": "SCMP_ACT_ERRNO",
+        "archMap": [
+          {
+            "architecture": "SCMP_ARCH_X86_64",
+            "subArchitectures": [
+              "SCMP_ARCH_X86",
+              "SCMP_ARCH_!!BUG"
+            ]
+          }
+        ],
+        "syscalls": []
+      })~",
+      "Unknown architecture: 'SCMP_ARCH_!!BUG'");
+
+  check(
+      R"~(
+      {
+        "defaultAction": "SCMP_ACT_ERRNO",
+        "archMap": [
+          {
+            "architecture": "SCMP_ARCH_X86_64",
+            "subArchitectures!!BUG": []
+          }
+        ],
+        "syscalls": []
+      })~",
+      "Cannot determine 'subArchitectures'");
+
+  // Check `syscalls` section.
+  check(
+      R"~(
+      {
+        "defaultAction": "SCMP_ACT_ERRNO",
+        "archMap": [],
+        "syscalls": [
+          {
+            "names!!BUG": ["accept"],
+            "action": "SCMP_ACT_ALLOW",
+            "args": [],
+            "includes": {},
+            "excludes": {}
+          }
+        ]
+      })~",
+      "Cannot determine 'names'");
+
+  check(
+      R"~(
+      {
+        "defaultAction": "SCMP_ACT_ERRNO",
+        "archMap": [],
+        "syscalls": [
+          {
+            "names": ["accept"],
+            "action!!BUG": "SCMP_ACT_ALLOW",
+            "args": [],
+            "includes": {},
+            "excludes": {}
+          }
+        ]
+      })~",
+      "Cannot determine 'action'");
+
+  check(
+      R"~(
+      {
+        "defaultAction": "SCMP_ACT_ERRNO",
+        "archMap": [],
+        "syscalls": [
+          {
+            "names": ["accept"],
+            "action": "SCMP_ACT_ALLOW",
+            "args!!BUG": [],
+            "includes": {},
+            "excludes": {}
+          }
+        ]
+      })~",
+      "Cannot determine 'args'");
+
+  check(
+      R"~(
+      {
+        "defaultAction": "SCMP_ACT_ERRNO",
+        "archMap": [],
+        "syscalls": [
+          {
+            "names": ["accept"],
+            "action": "SCMP_ACT_ALLOW",
+            "args": [],
+            "includes!!BUG": {},
+            "excludes": {}
+          }
+        ]
+      })~",
+      "Cannot determine 'includes'");
+
+  check(
+      R"~(
+      {
+        "defaultAction": "SCMP_ACT_ERRNO",
+        "archMap": [],
+        "syscalls": [
+          {
+            "names": ["accept"],
+            "action": "SCMP_ACT_ALLOW",
+            "args": [],
+            "includes": {},
+            "excludes!!BUG": {}
+          }
+        ]
+      })~",
+      "Cannot determine 'excludes'");
+
+  check(
+      R"~(
+      {
+        "defaultAction": "SCMP_ACT_ERRNO",
+        "archMap": [],
+        "syscalls": [
+          {
+            "names": [
+              "accept",
+              "writev!!BUG"
+            ],
+            "action": "SCMP_ACT_ALLOW",
+            "args": [],
+            "includes": {},
+            "excludes": {}
+          }
+        ]
+      })~",
+      "Unrecognized syscall 'writev!!BUG'");
+
+  check(
+      R"~(
+      {
+        "defaultAction": "SCMP_ACT_ERRNO",
+        "archMap": [],
+        "syscalls": [
+          {
+            "names": ["accept"],
+            "action": "SCMP_ACT_!!BUG",
+            "args": [],
+            "includes": {},
+            "excludes": {}
+          }
+        ]
+      })~",
+      "Unknown syscall action: 'SCMP_ACT_!!BUG'");
+
+  // Check `args` object which belongs to the `syscalls` object.
+  check(
+      R"~(
+      {
+        "defaultAction": "SCMP_ACT_ERRNO",
+        "archMap": [],
+        "syscalls": [
+          {
+            "names": ["accept"],
+            "action": "SCMP_ACT_ALLOW",
+            "args": [
+              {
+                "index!!BUG": 0,
+                "value": 2080505856,
+                "valueTwo": 0,
+                "op": "SCMP_CMP_MASKED_EQ"
+              }
+            ],
+            "includes": {},
+            "excludes": {}
+          }
+        ]
+      })~",
+      "Cannot determine 'index'");
+
+  check(
+      R"~(
+      {
+        "defaultAction": "SCMP_ACT_ERRNO",
+        "archMap": [],
+        "syscalls": [
+          {
+            "names": ["accept"],
+            "action": "SCMP_ACT_ALLOW",
+            "args": [
+              {
+                "index": 0,
+                "value!!BUG": 2080505856,
+                "valueTwo": 0,
+                "op": "SCMP_CMP_MASKED_EQ"
+              }
+            ],
+            "includes": {},
+            "excludes": {}
+          }
+        ]
+      })~",
+      "Cannot determine 'value'");
+
+  check(
+      R"~(
+      {
+        "defaultAction": "SCMP_ACT_ERRNO",
+        "archMap": [],
+        "syscalls": [
+          {
+            "names": ["accept"],
+            "action": "SCMP_ACT_ALLOW",
+            "args": [
+              {
+                "index": 0,
+                "value": 2080505856,
+                "valueTwo!!BUG": 0,
+                "op": "SCMP_CMP_MASKED_EQ"
+              }
+            ],
+            "includes": {},
+            "excludes": {}
+          }
+        ]
+      })~",
+      "Cannot determine 'valueTwo'");
+
+  check(
+      R"~(
+      {
+        "defaultAction": "SCMP_ACT_ERRNO",
+        "archMap": [],
+        "syscalls": [
+          {
+            "names": ["accept"],
+            "action": "SCMP_ACT_ALLOW",
+            "args": [
+              {
+                "index": 0,
+                "value": 2080505856,
+                "valueTwo": 0,
+                "op!!BUG": "SCMP_CMP_MASKED_EQ"
+              }
+            ],
+            "includes": {},
+            "excludes": {}
+          }
+        ]
+      })~",
+      "Cannot determine 'op'");
+
+  check(
+      R"~(
+      {
+        "defaultAction": "SCMP_ACT_ERRNO",
+        "archMap": [],
+        "syscalls": [
+          {
+            "names": ["accept"],
+            "action": "SCMP_ACT_ALLOW",
+            "args": [
+              {
+                "index": 0,
+                "value": 2080505856,
+                "valueTwo": 0,
+                "op": "SCMP_CMP_MASKED_EQ!!BUG"
+              }
+            ],
+            "includes": {},
+            "excludes": {}
+          }
+        ]
+      })~",
+      "Unknown operator: 'SCMP_CMP_MASKED_EQ!!BUG'");
+
+  // Check `excludes` object which belongs to the `syscalls` object.
+  check(
+      R"~(
+      {
+        "defaultAction": "SCMP_ACT_ERRNO",
+        "archMap": [],
+        "syscalls": [
+          {
+            "names": ["accept"],
+            "action": "SCMP_ACT_ALLOW",
+            "args": [],
+            "includes": {},
+            "excludes": {
+              "arches": [
+                "amd64!!SPECTRE"
+              ]
+            }
+          }
+        ]
+      })~",
+      "Unknown architecture: 'amd64!!SPECTRE'");
+
+  check(
+      R"~(
+      {
+        "defaultAction": "SCMP_ACT_ERRNO",
+        "archMap": [],
+        "syscalls": [
+          {
+            "names": ["accept"],
+            "action": "SCMP_ACT_ALLOW",
+            "args": [],
+            "includes": {},
+            "excludes": {
+              "caps": [
+                "CAP_SYS_ADMIN!!BUG"
+              ]
+            }
+          }
+        ]
+      })~",
+      "Unknown capability: 'CAP_SYS_ADMIN!!BUG'");
+}
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {
diff --git a/src/tests/environment.cpp b/src/tests/environment.cpp
index 03f8f59..c3596fb 100644
--- a/src/tests/environment.cpp
+++ b/src/tests/environment.cpp
@@ -914,6 +914,40 @@ public:
 };
 
 
+class SeccompFilter : public TestFilter
+{
+public:
+  SeccompFilter()
+  {
+    // Since the Seccomp parser depends on CPU architecture, we can run Seccomp
+    // tests deterministically only on `x86_64`.
+    Try<string> arch = os::shell("arch");
+    if (arch.isError()) {
+      seccompError = arch.error();
+    } else if (strings::trim(arch.get()) != "x86_64") {
+      seccompError = Error("Seccomp tests cannot be run on non-x86_64 systems");
+    }
+
+    if (seccompError.isSome()) {
+      std::cerr
+        << "-------------------------------------------------------------\n"
+        << "We can't run any SECCOMP tests:\n"
+        << seccompError.get() << "\n"
+        << "-------------------------------------------------------------"
+        << std::endl;
+    }
+  }
+
+  bool disable(const ::testing::TestInfo* test) const override
+  {
+    return matches(test, "SECCOMP_") && seccompError.isSome();
+  }
+
+private:
+  Option<Error> seccompError;
+};
+
+
 class UnprivilegedUserFilter : public TestFilter
 {
 public:
@@ -1070,6 +1104,7 @@ Environment::Environment(const Flags& _flags)
             std::make_shared<PerfCPUCyclesFilter>(),
             std::make_shared<PerfFilter>(),
             std::make_shared<RootFilter>(),
+            std::make_shared<SeccompFilter>(),
             std::make_shared<UnprivilegedUserFilter>(),
             std::make_shared<UnzipFilter>(),
             std::make_shared<VEthFilter>(),