/* * Copyright (C) 2021 The Android Open Source Project * * 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. */ #include #include #include #include #include #include #include #include #include #include #include "android-base/file.h" #include "android-base/logging.h" #include "android-base/scopeguard.h" #include "android-base/strings.h" #include "base/common_art_test.h" #include "base/file_utils.h" #include "base/globals.h" #include "base/macros.h" #include "base/os.h" #include "base/scoped_cap.h" #include "exec_utils.h" #include "fmt/format.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "system/thread_defs.h" #ifdef ART_TARGET_ANDROID #include "android-modules-utils/sdk_level.h" #endif namespace art { namespace { using ::android::base::make_scope_guard; using ::android::base::ScopeGuard; using ::android::base::Split; using ::testing::Contains; using ::testing::ElementsAre; using ::testing::HasSubstr; using ::testing::Not; // clang-tidy incorrectly complaints about the using declaration while the user-defined literal is // actually being used. using ::fmt::literals::operator""_format; // NOLINT constexpr uid_t kRoot = 0; constexpr uid_t kNobody = 9999; // This test executes a few Linux system commands such as "ls", which are linked against system // libraries. In many ART gtests we set LD_LIBRARY_PATH to make the test binaries link to libraries // from the ART module first, and if that setting is propagated to the system commands they may also // try to link to those libraries instead of the system ones they are built against. This is // particularly noticeable when 32-bit tests run on a 64-bit system. Hence we need to set // LD_LIBRARY_PATH to an empty string here. // TODO(b/247108425): Remove this when ART gtests no longer use LD_LIBRARY_PATH. constexpr const char* kEmptyLdLibraryPath = "--env=LD_LIBRARY_PATH="; std::string GetArtBin(const std::string& name) { return "{}/bin/{}"_format(GetArtRoot(), name); } std::string GetBin(const std::string& name) { return "{}/bin/{}"_format(GetAndroidRoot(), name); } // Executes the command, waits for it to finish, and keeps it in a waitable state until the current // scope exits. std::pair>> ScopedExecAndWait( std::vector& args) { std::vector execv_args; execv_args.reserve(args.size() + 1); for (std::string& arg : args) { execv_args.push_back(arg.data()); } execv_args.push_back(nullptr); pid_t pid = fork(); if (pid == 0) { execv(execv_args[0], execv_args.data()); UNREACHABLE(); } else if (pid > 0) { siginfo_t info; CHECK_EQ(TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, WEXITED | WNOWAIT)), 0); CHECK_EQ(info.si_code, CLD_EXITED); CHECK_EQ(info.si_status, 0); std::function cleanup([=] { siginfo_t info; CHECK_EQ(TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, WEXITED)), 0); }); return std::make_pair(pid, make_scope_guard(std::move(cleanup))); } else { LOG(FATAL) << "Failed to call fork"; UNREACHABLE(); } } // Grants the current process the given root capability. void SetCap(cap_flag_t flag, cap_value_t value) { ScopedCap cap(cap_get_proc()); CHECK_NE(cap.Get(), nullptr); cap_value_t caps[]{value}; CHECK_EQ(cap_set_flag(cap.Get(), flag, /*ncap=*/1, caps, CAP_SET), 0); CHECK_EQ(cap_set_proc(cap.Get()), 0); } // Returns true if the given process has the given root capability. bool GetCap(pid_t pid, cap_flag_t flag, cap_value_t value) { ScopedCap cap(cap_get_pid(pid)); CHECK_NE(cap.Get(), nullptr); cap_flag_value_t flag_value; CHECK_EQ(cap_get_flag(cap.Get(), value, flag, &flag_value), 0); return flag_value == CAP_SET; } class ArtExecTest : public testing::Test { protected: void SetUp() override { testing::Test::SetUp(); if (!kIsTargetAndroid) { GTEST_SKIP() << "art_exec is for device only"; } if (getuid() != kRoot) { GTEST_SKIP() << "art_exec requires root"; } art_exec_bin_ = GetArtBin("art_exec"); } std::string art_exec_bin_; }; TEST_F(ArtExecTest, Command) { std::string error_msg; int ret = ExecAndReturnCode({art_exec_bin_, "--", GetBin("sh"), "-c", "exit 123"}, &error_msg); ASSERT_EQ(ret, 123) << error_msg; } TEST_F(ArtExecTest, SetTaskProfiles) { // The condition is always true because ArtExecTest is run on device only. #ifdef ART_TARGET_ANDROID if (!android::modules::sdklevel::IsAtLeastU()) { GTEST_SKIP() << "This test depends on a libartpalette API that is only available on U+"; } #endif std::string filename = "/data/local/tmp/art-exec-test-XXXXXX"; ScratchFile scratch_file(new File(mkstemp(filename.data()), filename, /*check_usage=*/false)); ASSERT_GE(scratch_file.GetFd(), 0); std::vector args{art_exec_bin_, "--set-task-profile=ProcessCapacityHigh", kEmptyLdLibraryPath, "--", GetBin("sh"), "-c", "cat /proc/self/cgroup > " + filename}; auto [pid, scope_guard] = ScopedExecAndWait(args); std::string cgroup; ASSERT_TRUE(android::base::ReadFileToString(filename, &cgroup)); EXPECT_THAT(cgroup, HasSubstr(":cpuset:/foreground\n")); } TEST_F(ArtExecTest, SetPriority) { std::vector args{ art_exec_bin_, "--set-priority=background", kEmptyLdLibraryPath, "--", GetBin("true")}; auto [pid, scope_guard] = ScopedExecAndWait(args); EXPECT_EQ(getpriority(PRIO_PROCESS, pid), ANDROID_PRIORITY_BACKGROUND); } TEST_F(ArtExecTest, DropCapabilities) { // Switch to a non-root user, but still keep the CAP_FOWNER capability available and inheritable. // The order of the following calls matters. CHECK_EQ(cap_setuid(kNobody), 0); SetCap(CAP_INHERITABLE, CAP_FOWNER); SetCap(CAP_EFFECTIVE, CAP_FOWNER); ASSERT_EQ(cap_set_ambient(CAP_FOWNER, CAP_SET), 0); // Make sure the test is set up correctly (i.e., the child process should normally have the // inherited root capability: CAP_FOWNER). { std::vector args{art_exec_bin_, kEmptyLdLibraryPath, "--", GetBin("true")}; auto [pid, scope_guard] = ScopedExecAndWait(args); ASSERT_TRUE(GetCap(pid, CAP_EFFECTIVE, CAP_FOWNER)); } { std::vector args{ art_exec_bin_, "--drop-capabilities", kEmptyLdLibraryPath, "--", GetBin("true")}; auto [pid, scope_guard] = ScopedExecAndWait(args); EXPECT_FALSE(GetCap(pid, CAP_EFFECTIVE, CAP_FOWNER)); } } TEST_F(ArtExecTest, CloseFds) { std::unique_ptr file1(OS::OpenFileForReading("/dev/zero")); std::unique_ptr file2(OS::OpenFileForReading("/dev/zero")); std::unique_ptr file3(OS::OpenFileForReading("/dev/zero")); ASSERT_NE(file1, nullptr); ASSERT_NE(file2, nullptr); ASSERT_NE(file3, nullptr); std::string filename = "/data/local/tmp/art-exec-test-XXXXXX"; ScratchFile scratch_file(new File(mkstemp(filename.data()), filename, /*check_usage=*/false)); ASSERT_GE(scratch_file.GetFd(), 0); std::vector args{art_exec_bin_, "--keep-fds={}:{}"_format(file3->Fd(), file2->Fd()), kEmptyLdLibraryPath, "--", GetBin("sh"), "-c", "(" "readlink /proc/self/fd/{} || echo;" "readlink /proc/self/fd/{} || echo;" "readlink /proc/self/fd/{} || echo;" ") > {}"_format(file1->Fd(), file2->Fd(), file3->Fd(), filename)}; ScopedExecAndWait(args); std::string open_fds; ASSERT_TRUE(android::base::ReadFileToString(filename, &open_fds)); // `file1` should be closed, while the other two should be open. There's a blank line at the end. EXPECT_THAT(Split(open_fds, "\n"), ElementsAre(Not("/dev/zero"), "/dev/zero", "/dev/zero", "")); } TEST_F(ArtExecTest, Env) { std::string filename = "/data/local/tmp/art-exec-test-XXXXXX"; ScratchFile scratch_file(new File(mkstemp(filename.data()), filename, /*check_usage=*/false)); ASSERT_GE(scratch_file.GetFd(), 0); std::vector args{art_exec_bin_, "--env=FOO=BAR", kEmptyLdLibraryPath, "--", GetBin("sh"), "-c", "env > " + filename}; ScopedExecAndWait(args); std::string envs; ASSERT_TRUE(android::base::ReadFileToString(filename, &envs)); EXPECT_THAT(Split(envs, "\n"), Contains("FOO=BAR")); } } // namespace } // namespace art