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 <[email protected]> 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 {
