You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by jp...@apache.org on 2019/07/21 08:06:47 UTC

[mesos] 02/03: Added a `linux/nnp` isolator.

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

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

commit 2f9494dc808b948720d318fdfc35dbf16ecd65ce
Author: Jacob Janco <jj...@gmail.com>
AuthorDate: Sat Jul 20 23:34:26 2019 -0700

    Added a `linux/nnp` isolator.
    
    This adds a `linux/nnp` isolator to set the NO_NEW_PRIVILEGES flag
    on containerized processes.
    
    Review: https://reviews.apache.org/r/70757/
---
 include/mesos/slave/containerizer.proto            |   3 +
 src/CMakeLists.txt                                 |   1 +
 src/Makefile.am                                    |   3 +
 src/slave/containerizer/mesos/containerizer.cpp    |   2 +
 .../containerizer/mesos/isolators/linux/nnp.cpp    |  78 ++++++++++++
 .../containerizer/mesos/isolators/linux/nnp.hpp    |  45 +++++++
 src/slave/containerizer/mesos/launch.cpp           |   9 ++
 src/tests/CMakeLists.txt                           |   1 +
 .../containerizer/linux_nnp_isolator_tests.cpp     | 141 +++++++++++++++++++++
 9 files changed, 283 insertions(+)

diff --git a/include/mesos/slave/containerizer.proto b/include/mesos/slave/containerizer.proto
index 2d04f3c..a60c963 100644
--- a/include/mesos/slave/containerizer.proto
+++ b/include/mesos/slave/containerizer.proto
@@ -327,6 +327,9 @@ message ContainerLaunchInfo {
   // (POSIX only) The supplementary group IDs specific for command task
   // with its own rootfs.
   repeated uint32 task_supplementary_groups = 20;
+
+  // (Linux only) Set the NO_NEW_PRIVILEGES flag.
+  optional bool no_new_privileges = 23;
 }
 
 
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 4a5eeb0..c455ed6 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -324,6 +324,7 @@ set(LINUX_SRC
   slave/containerizer/mesos/isolators/gpu/volume.cpp
   slave/containerizer/mesos/isolators/linux/capabilities.cpp
   slave/containerizer/mesos/isolators/linux/devices.cpp
+  slave/containerizer/mesos/isolators/linux/nnp.cpp
   slave/containerizer/mesos/isolators/namespaces/ipc.cpp
   slave/containerizer/mesos/isolators/namespaces/pid.cpp
   slave/containerizer/mesos/isolators/network/cni/cni.cpp
diff --git a/src/Makefile.am b/src/Makefile.am
index 7851aba..46c66f1 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1435,6 +1435,8 @@ MESOS_LINUX_FILES =									\
   slave/containerizer/mesos/isolators/linux/capabilities.hpp				\
   slave/containerizer/mesos/isolators/linux/devices.cpp					\
   slave/containerizer/mesos/isolators/linux/devices.hpp					\
+  slave/containerizer/mesos/isolators/linux/nnp.hpp					\
+  slave/containerizer/mesos/isolators/linux/nnp.cpp					\
   slave/containerizer/mesos/isolators/namespaces/ipc.cpp				\
   slave/containerizer/mesos/isolators/namespaces/ipc.hpp				\
   slave/containerizer/mesos/isolators/namespaces/pid.cpp				\
@@ -2764,6 +2766,7 @@ mesos_tests_SOURCES +=						\
   tests/containerizer/docker_volume_isolator_tests.cpp		\
   tests/containerizer/linux_devices_isolator_tests.cpp		\
   tests/containerizer/linux_filesystem_isolator_tests.cpp	\
+  tests/containerizer/linux_nnp_isolator_tests.cpp		\
   tests/containerizer/fs_tests.cpp				\
   tests/containerizer/memory_pressure_tests.cpp			\
   tests/containerizer/nested_mesos_containerizer_tests.cpp	\
diff --git a/src/slave/containerizer/mesos/containerizer.cpp b/src/slave/containerizer/mesos/containerizer.cpp
index 6f76527..a01edc8 100644
--- a/src/slave/containerizer/mesos/containerizer.cpp
+++ b/src/slave/containerizer/mesos/containerizer.cpp
@@ -108,6 +108,7 @@
 #include "slave/containerizer/mesos/isolators/gpu/nvidia.hpp"
 #include "slave/containerizer/mesos/isolators/linux/capabilities.hpp"
 #include "slave/containerizer/mesos/isolators/linux/devices.hpp"
+#include "slave/containerizer/mesos/isolators/linux/nnp.hpp"
 #include "slave/containerizer/mesos/isolators/namespaces/ipc.hpp"
 #include "slave/containerizer/mesos/isolators/namespaces/pid.hpp"
 #include "slave/containerizer/mesos/isolators/network/cni/cni.hpp"
@@ -438,6 +439,7 @@ Try<MesosContainerizer*> MesosContainerizer::create(
 
     {"linux/devices", &LinuxDevicesIsolatorProcess::create},
     {"linux/capabilities", &LinuxCapabilitiesIsolatorProcess::create},
+    {"linux/nnp", &LinuxNNPIsolatorProcess::create},
 
     {"namespaces/ipc", &NamespacesIPCIsolatorProcess::create},
     {"namespaces/pid", &NamespacesPidIsolatorProcess::create},
diff --git a/src/slave/containerizer/mesos/isolators/linux/nnp.cpp b/src/slave/containerizer/mesos/isolators/linux/nnp.cpp
new file mode 100644
index 0000000..bd214b1
--- /dev/null
+++ b/src/slave/containerizer/mesos/isolators/linux/nnp.cpp
@@ -0,0 +1,78 @@
+// 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 <stout/try.hpp>
+
+#include "common/kernel_version.hpp"
+
+#include "slave/containerizer/mesos/isolators/linux/nnp.hpp"
+
+using process::Future;
+using process::Owned;
+
+using mesos::slave::ContainerConfig;
+using mesos::slave::ContainerLaunchInfo;
+using mesos::slave::Isolator;
+
+namespace mesos {
+namespace internal {
+namespace slave {
+
+Try<Isolator*> LinuxNNPIsolatorProcess::create(const Flags& flags)
+{
+  // PR_SET_NO_NEW_PRIVS requires Linux kernel version greater than or
+  // equal to 3.5.
+  Try<Version> version = mesos::kernelVersion();
+
+  if (version.isError()) {
+    return Error("Could not determine kernel version");
+  }
+
+  if (version.get() < Version(3, 5, 0)) {
+    return Error("Linux kernel version greater than or equal to 3.5 required");
+  }
+
+  return new MesosIsolator(
+      Owned<MesosIsolatorProcess>(new LinuxNNPIsolatorProcess()));
+}
+
+
+bool LinuxNNPIsolatorProcess::supportsNesting()
+{
+  return true;
+}
+
+
+bool LinuxNNPIsolatorProcess::supportsStandalone()
+{
+  return true;
+}
+
+
+Future<Option<ContainerLaunchInfo>> LinuxNNPIsolatorProcess::prepare(
+    const ContainerID& containerId,
+    const ContainerConfig& containerConfig)
+{
+  ContainerLaunchInfo launchInfo;
+
+  launchInfo.set_no_new_privileges(true);
+
+  return launchInfo;
+}
+
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {
diff --git a/src/slave/containerizer/mesos/isolators/linux/nnp.hpp b/src/slave/containerizer/mesos/isolators/linux/nnp.hpp
new file mode 100644
index 0000000..5251f61
--- /dev/null
+++ b/src/slave/containerizer/mesos/isolators/linux/nnp.hpp
@@ -0,0 +1,45 @@
+// 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.
+
+#ifndef __LINUX_NNP_ISOLATOR_HPP__
+#define __LINUX_NNP_ISOLATOR_HPP__
+
+#include "slave/flags.hpp"
+
+#include "slave/containerizer/mesos/isolator.hpp"
+
+namespace mesos {
+namespace internal {
+namespace slave {
+
+class LinuxNNPIsolatorProcess : public MesosIsolatorProcess
+{
+public:
+  static Try<mesos::slave::Isolator*> create(const Flags& flags);
+
+  bool supportsNesting() override;
+  bool supportsStandalone() override;
+
+  process::Future<Option<mesos::slave::ContainerLaunchInfo>> prepare(
+      const ContainerID& containerId,
+      const mesos::slave::ContainerConfig& containerConfig) override;
+};
+
+} // namespace slave {
+} // namespace internal {
+} // namespace mesos {
+
+#endif  // __LINUX_NNP_ISOLATOR_HPP__
diff --git a/src/slave/containerizer/mesos/launch.cpp b/src/slave/containerizer/mesos/launch.cpp
index 0419e8e..1f8dc1a 100644
--- a/src/slave/containerizer/mesos/launch.cpp
+++ b/src/slave/containerizer/mesos/launch.cpp
@@ -18,6 +18,7 @@
 #ifdef __linux__
 #include <sched.h>
 #include <signal.h>
+#include <sys/prctl.h>
 #endif // __linux__
 #include <string.h>
 
@@ -1156,6 +1157,14 @@ int MesosContainerizerLaunch::execute()
       exitWithStatus(EXIT_FAILURE);
     }
   }
+
+  if (launchInfo.has_no_new_privileges()) {
+    const int val = launchInfo.no_new_privileges() ? 1 : 0;
+    if (prctl(PR_SET_NO_NEW_PRIVS, val, 0, 0, 0) == -1) {
+      cerr << "Failed to set NO_NEW_PRIVS: " << os::strerror(errno) << endl;
+      exitWithStatus(EXIT_FAILURE);
+    }
+  }
 #endif // __linux__
 
   // Prepare the executable and the argument list for the child.
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index 57344a1..faa0058 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -232,6 +232,7 @@ if (LINUX)
     containerizer/docker_volume_isolator_tests.cpp
     containerizer/fs_tests.cpp
     containerizer/linux_capabilities_isolator_tests.cpp
+    containerizer/linux_nnp_isolator_tests.cpp
     containerizer/linux_devices_isolator_tests.cpp
     containerizer/linux_filesystem_isolator_tests.cpp
     containerizer/memory_pressure_tests.cpp
diff --git a/src/tests/containerizer/linux_nnp_isolator_tests.cpp b/src/tests/containerizer/linux_nnp_isolator_tests.cpp
new file mode 100644
index 0000000..8489daf
--- /dev/null
+++ b/src/tests/containerizer/linux_nnp_isolator_tests.cpp
@@ -0,0 +1,141 @@
+// 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 <map>
+#include <string>
+
+#include <mesos/mesos.hpp>
+
+#include <process/gtest.hpp>
+#include <process/owned.hpp>
+
+#include <stout/try.hpp>
+
+#include "common/kernel_version.hpp"
+
+#include "slave/containerizer/mesos/containerizer.hpp"
+
+#include "tests/mesos.hpp"
+
+#include "tests/containerizer/docker_archive.hpp"
+
+using process::Future;
+using process::Owned;
+
+using std::map;
+using std::string;
+
+using mesos::internal::master::Master;
+
+using mesos::internal::slave::Containerizer;
+using mesos::internal::slave::Fetcher;
+using mesos::internal::slave::MesosContainerizer;
+
+using mesos::slave::ContainerTermination;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+class LinuxNNPIsolatorTest : public MesosTest
+{
+protected:
+  slave::Flags CreateSlaveFlags() override
+  {
+    slave::Flags flags = MesosTest::CreateSlaveFlags();
+    flags.isolation = "filesystem/linux,docker/runtime,linux/nnp";
+    flags.docker_registry = GetRegistryPath();
+    flags.docker_store_dir = path::join(sandbox.get(), "store");
+    flags.image_providers = "docker";
+
+    return flags;
+  }
+
+  string GetRegistryPath() const
+  {
+    return path::join(sandbox.get(), "registry");
+  }
+};
+
+
+// Check that the PR_NO_NEW_PRIVILEGES flag is set.
+TEST_F(LinuxNNPIsolatorTest, CheckNoNewPrivileges)
+{
+  // This tests requires the NoNewPrivs field present in process
+  // status fields which requires Linux kernel version greater than
+  // or equal to 4.10.
+  Try<Version> version = mesos::kernelVersion();
+  ASSERT_SOME(version);
+  if (version.get() < Version(4, 10, 0)) {
+    LOG(INFO) << "Linux kernel version greater than or equal to 4.10 required";
+    return;
+  }
+
+  AWAIT_READY(DockerArchive::create(GetRegistryPath(), "test_image"));
+
+  slave::Flags flags = CreateSlaveFlags();
+
+  Fetcher fetcher(flags);
+
+  Try<MesosContainerizer*> create =
+    MesosContainerizer::create(flags, true, &fetcher);
+
+  ASSERT_SOME(create);
+
+  Owned<Containerizer> containerizer(create.get());
+
+  ContainerID containerId;
+  containerId.set_value(id::UUID::random().toString());
+
+  // Test that the child process inherits the PR_NO_NEW_PRIVS flag.
+  // Using parameter expansion to parse the process status file
+  // due to minimal docker image. The child process should inherit
+  // the PR_NO_NEW_PRIVS flag. Parse the process status file and
+  // determine if "NoNewPrivs: 1" is found.
+  ExecutorInfo executor = createExecutorInfo(
+      "test_executor",
+      R"~(
+      #!/bin/bash
+      x=$(cat /proc/self/status);
+      y=${x##*NoNewPrivs:};
+      read -a a <<< $y;
+      if [ ${a[0]} == "1" ]; then exit 0; else exit 1; fi
+      )~");
+
+  executor.mutable_container()->CopyFrom(createContainerInfo("test_image"));
+
+  string directory = path::join(flags.work_dir, "sandbox");
+  ASSERT_SOME(os::mkdir(directory));
+
+  Future<Containerizer::LaunchResult> launch = containerizer->launch(
+      containerId,
+      createContainerConfig(None(), executor, directory),
+      map<string, string>(),
+      None());
+
+  AWAIT_ASSERT_EQ(Containerizer::LaunchResult::SUCCESS, launch);
+
+  Future<Option<ContainerTermination>> wait = containerizer->wait(containerId);
+
+  AWAIT_READY(wait);
+  ASSERT_SOME(wait.get());
+  ASSERT_TRUE(wait->get().has_status());
+  EXPECT_WEXITSTATUS_EQ(0, wait->get().status());
+}
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {