Jiakai Zhang | 2b905c3 | 2022-07-20 15:49:34 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2021 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #include <sys/capability.h> |
| 18 | #include <sys/resource.h> |
| 19 | #include <sys/types.h> |
| 20 | #include <sys/wait.h> |
| 21 | #include <unistd.h> |
| 22 | |
| 23 | #include <csignal> |
| 24 | #include <filesystem> |
| 25 | #include <functional> |
| 26 | #include <string> |
| 27 | #include <utility> |
| 28 | |
| 29 | #include "android-base/file.h" |
| 30 | #include "android-base/logging.h" |
| 31 | #include "android-base/scopeguard.h" |
Jiakai Zhang | 7b6aef7 | 2022-12-12 19:30:44 +0000 | [diff] [blame] | 32 | #include "android-base/strings.h" |
Jiakai Zhang | 2b905c3 | 2022-07-20 15:49:34 +0100 | [diff] [blame] | 33 | #include "base/common_art_test.h" |
| 34 | #include "base/file_utils.h" |
| 35 | #include "base/globals.h" |
| 36 | #include "base/macros.h" |
| 37 | #include "base/os.h" |
| 38 | #include "base/scoped_cap.h" |
| 39 | #include "exec_utils.h" |
| 40 | #include "fmt/format.h" |
| 41 | #include "gmock/gmock.h" |
| 42 | #include "gtest/gtest.h" |
| 43 | #include "system/thread_defs.h" |
| 44 | |
| 45 | namespace art { |
| 46 | namespace { |
| 47 | |
| 48 | using ::android::base::make_scope_guard; |
| 49 | using ::android::base::ScopeGuard; |
Jiakai Zhang | 7b6aef7 | 2022-12-12 19:30:44 +0000 | [diff] [blame] | 50 | using ::android::base::Split; |
Jiakai Zhang | 7b6aef7 | 2022-12-12 19:30:44 +0000 | [diff] [blame] | 51 | using ::testing::Contains; |
Jiakai Zhang | 7bc7ffe | 2023-02-02 00:17:04 +0800 | [diff] [blame] | 52 | using ::testing::ElementsAre; |
Jiakai Zhang | 2b905c3 | 2022-07-20 15:49:34 +0100 | [diff] [blame] | 53 | using ::testing::HasSubstr; |
Jiakai Zhang | 7b6aef7 | 2022-12-12 19:30:44 +0000 | [diff] [blame] | 54 | using ::testing::Not; |
Jiakai Zhang | 2b905c3 | 2022-07-20 15:49:34 +0100 | [diff] [blame] | 55 | |
| 56 | // clang-tidy incorrectly complaints about the using declaration while the user-defined literal is |
| 57 | // actually being used. |
| 58 | using ::fmt::literals::operator""_format; // NOLINT |
| 59 | |
| 60 | constexpr uid_t kRoot = 0; |
| 61 | constexpr uid_t kNobody = 9999; |
| 62 | |
Jiakai Zhang | d49058e | 2023-01-10 10:55:31 +0000 | [diff] [blame] | 63 | // This test executes a few Linux system commands such as "ls", which are linked against system |
| 64 | // libraries. In many ART gtests we set LD_LIBRARY_PATH to make the test binaries link to libraries |
| 65 | // from the ART module first, and if that setting is propagated to the system commands they may also |
| 66 | // try to link to those libraries instead of the system ones they are built against. This is |
| 67 | // particularly noticeable when 32-bit tests run on a 64-bit system. Hence we need to set |
| 68 | // LD_LIBRARY_PATH to an empty string here. |
| 69 | // TODO(b/247108425): Remove this when ART gtests no longer use LD_LIBRARY_PATH. |
| 70 | constexpr const char* kEmptyLdLibraryPath = "--env=LD_LIBRARY_PATH="; |
| 71 | |
Jiakai Zhang | 2b905c3 | 2022-07-20 15:49:34 +0100 | [diff] [blame] | 72 | std::string GetArtBin(const std::string& name) { return "{}/bin/{}"_format(GetArtRoot(), name); } |
| 73 | |
| 74 | std::string GetBin(const std::string& name) { return "{}/bin/{}"_format(GetAndroidRoot(), name); } |
| 75 | |
| 76 | // Executes the command, waits for it to finish, and keeps it in a waitable state until the current |
| 77 | // scope exits. |
| 78 | std::pair<pid_t, ScopeGuard<std::function<void()>>> ScopedExecAndWait( |
| 79 | std::vector<std::string>& args) { |
| 80 | std::vector<char*> execv_args; |
| 81 | execv_args.reserve(args.size() + 1); |
| 82 | for (std::string& arg : args) { |
| 83 | execv_args.push_back(arg.data()); |
| 84 | } |
| 85 | execv_args.push_back(nullptr); |
| 86 | |
| 87 | pid_t pid = fork(); |
| 88 | if (pid == 0) { |
| 89 | execv(execv_args[0], execv_args.data()); |
| 90 | UNREACHABLE(); |
| 91 | } else if (pid > 0) { |
| 92 | siginfo_t info; |
| 93 | CHECK_EQ(TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, WEXITED | WNOWAIT)), 0); |
| 94 | CHECK_EQ(info.si_code, CLD_EXITED); |
| 95 | CHECK_EQ(info.si_status, 0); |
| 96 | std::function<void()> cleanup([=] { |
| 97 | siginfo_t info; |
| 98 | CHECK_EQ(TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, WEXITED)), 0); |
| 99 | }); |
| 100 | return std::make_pair(pid, make_scope_guard(std::move(cleanup))); |
| 101 | } else { |
| 102 | LOG(FATAL) << "Failed to call fork"; |
| 103 | UNREACHABLE(); |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | // Grants the current process the given root capability. |
| 108 | void SetCap(cap_flag_t flag, cap_value_t value) { |
| 109 | ScopedCap cap(cap_get_proc()); |
| 110 | CHECK_NE(cap.Get(), nullptr); |
| 111 | cap_value_t caps[]{value}; |
| 112 | CHECK_EQ(cap_set_flag(cap.Get(), flag, /*ncap=*/1, caps, CAP_SET), 0); |
| 113 | CHECK_EQ(cap_set_proc(cap.Get()), 0); |
| 114 | } |
| 115 | |
| 116 | // Returns true if the given process has the given root capability. |
| 117 | bool GetCap(pid_t pid, cap_flag_t flag, cap_value_t value) { |
| 118 | ScopedCap cap(cap_get_pid(pid)); |
| 119 | CHECK_NE(cap.Get(), nullptr); |
| 120 | cap_flag_value_t flag_value; |
| 121 | CHECK_EQ(cap_get_flag(cap.Get(), value, flag, &flag_value), 0); |
| 122 | return flag_value == CAP_SET; |
| 123 | } |
| 124 | |
| 125 | class ArtExecTest : public testing::Test { |
| 126 | protected: |
| 127 | void SetUp() override { |
| 128 | testing::Test::SetUp(); |
| 129 | if (!kIsTargetAndroid) { |
| 130 | GTEST_SKIP() << "art_exec is for device only"; |
| 131 | } |
| 132 | if (getuid() != kRoot) { |
| 133 | GTEST_SKIP() << "art_exec requires root"; |
| 134 | } |
| 135 | art_exec_bin_ = GetArtBin("art_exec"); |
| 136 | } |
| 137 | |
| 138 | std::string art_exec_bin_; |
| 139 | }; |
| 140 | |
| 141 | TEST_F(ArtExecTest, Command) { |
| 142 | std::string error_msg; |
| 143 | int ret = ExecAndReturnCode({art_exec_bin_, "--", GetBin("sh"), "-c", "exit 123"}, &error_msg); |
| 144 | ASSERT_EQ(ret, 123) << error_msg; |
| 145 | } |
| 146 | |
| 147 | TEST_F(ArtExecTest, SetTaskProfiles) { |
| 148 | std::string filename = "/data/local/tmp/art-exec-test-XXXXXX"; |
| 149 | ScratchFile scratch_file(new File(mkstemp(filename.data()), filename, /*check_usage=*/false)); |
| 150 | ASSERT_GE(scratch_file.GetFd(), 0); |
| 151 | |
| 152 | std::vector<std::string> args{art_exec_bin_, |
Jiakai Zhang | c433077 | 2022-08-01 19:41:24 +0100 | [diff] [blame] | 153 | "--set-task-profile=ProcessCapacityHigh", |
Jiakai Zhang | d49058e | 2023-01-10 10:55:31 +0000 | [diff] [blame] | 154 | kEmptyLdLibraryPath, |
Jiakai Zhang | 2b905c3 | 2022-07-20 15:49:34 +0100 | [diff] [blame] | 155 | "--", |
| 156 | GetBin("sh"), |
| 157 | "-c", |
| 158 | "cat /proc/self/cgroup > " + filename}; |
| 159 | auto [pid, scope_guard] = ScopedExecAndWait(args); |
| 160 | std::string cgroup; |
| 161 | ASSERT_TRUE(android::base::ReadFileToString(filename, &cgroup)); |
Jiakai Zhang | c433077 | 2022-08-01 19:41:24 +0100 | [diff] [blame] | 162 | EXPECT_THAT(cgroup, HasSubstr(":cpuset:/foreground\n")); |
Jiakai Zhang | 2b905c3 | 2022-07-20 15:49:34 +0100 | [diff] [blame] | 163 | } |
| 164 | |
| 165 | TEST_F(ArtExecTest, SetPriority) { |
Jiakai Zhang | d49058e | 2023-01-10 10:55:31 +0000 | [diff] [blame] | 166 | std::vector<std::string> args{ |
| 167 | art_exec_bin_, "--set-priority=background", kEmptyLdLibraryPath, "--", GetBin("true")}; |
Jiakai Zhang | 2b905c3 | 2022-07-20 15:49:34 +0100 | [diff] [blame] | 168 | auto [pid, scope_guard] = ScopedExecAndWait(args); |
| 169 | EXPECT_EQ(getpriority(PRIO_PROCESS, pid), ANDROID_PRIORITY_BACKGROUND); |
| 170 | } |
| 171 | |
| 172 | TEST_F(ArtExecTest, DropCapabilities) { |
| 173 | // Switch to a non-root user, but still keep the CAP_FOWNER capability available and inheritable. |
| 174 | // The order of the following calls matters. |
| 175 | CHECK_EQ(cap_setuid(kNobody), 0); |
| 176 | SetCap(CAP_INHERITABLE, CAP_FOWNER); |
| 177 | SetCap(CAP_EFFECTIVE, CAP_FOWNER); |
| 178 | ASSERT_EQ(cap_set_ambient(CAP_FOWNER, CAP_SET), 0); |
| 179 | |
| 180 | // Make sure the test is set up correctly (i.e., the child process should normally have the |
| 181 | // inherited root capability: CAP_FOWNER). |
| 182 | { |
Jiakai Zhang | d49058e | 2023-01-10 10:55:31 +0000 | [diff] [blame] | 183 | std::vector<std::string> args{art_exec_bin_, kEmptyLdLibraryPath, "--", GetBin("true")}; |
Jiakai Zhang | 2b905c3 | 2022-07-20 15:49:34 +0100 | [diff] [blame] | 184 | auto [pid, scope_guard] = ScopedExecAndWait(args); |
| 185 | ASSERT_TRUE(GetCap(pid, CAP_EFFECTIVE, CAP_FOWNER)); |
| 186 | } |
| 187 | |
| 188 | { |
Jiakai Zhang | d49058e | 2023-01-10 10:55:31 +0000 | [diff] [blame] | 189 | std::vector<std::string> args{ |
| 190 | art_exec_bin_, "--drop-capabilities", kEmptyLdLibraryPath, "--", GetBin("true")}; |
Jiakai Zhang | 2b905c3 | 2022-07-20 15:49:34 +0100 | [diff] [blame] | 191 | auto [pid, scope_guard] = ScopedExecAndWait(args); |
| 192 | EXPECT_FALSE(GetCap(pid, CAP_EFFECTIVE, CAP_FOWNER)); |
| 193 | } |
| 194 | } |
| 195 | |
Jiakai Zhang | 7b6aef7 | 2022-12-12 19:30:44 +0000 | [diff] [blame] | 196 | TEST_F(ArtExecTest, CloseFds) { |
| 197 | std::unique_ptr<File> file1(OS::OpenFileForReading("/dev/zero")); |
| 198 | std::unique_ptr<File> file2(OS::OpenFileForReading("/dev/zero")); |
| 199 | std::unique_ptr<File> file3(OS::OpenFileForReading("/dev/zero")); |
| 200 | ASSERT_NE(file1, nullptr); |
| 201 | ASSERT_NE(file2, nullptr); |
| 202 | ASSERT_NE(file3, nullptr); |
| 203 | |
| 204 | std::string filename = "/data/local/tmp/art-exec-test-XXXXXX"; |
| 205 | ScratchFile scratch_file(new File(mkstemp(filename.data()), filename, /*check_usage=*/false)); |
| 206 | ASSERT_GE(scratch_file.GetFd(), 0); |
| 207 | |
| 208 | std::vector<std::string> args{art_exec_bin_, |
| 209 | "--keep-fds={}:{}"_format(file3->Fd(), file2->Fd()), |
Jiakai Zhang | d49058e | 2023-01-10 10:55:31 +0000 | [diff] [blame] | 210 | kEmptyLdLibraryPath, |
Jiakai Zhang | 7b6aef7 | 2022-12-12 19:30:44 +0000 | [diff] [blame] | 211 | "--", |
| 212 | GetBin("sh"), |
| 213 | "-c", |
Jiakai Zhang | 7bc7ffe | 2023-02-02 00:17:04 +0800 | [diff] [blame] | 214 | "(" |
| 215 | "readlink /proc/self/fd/{} || echo;" |
| 216 | "readlink /proc/self/fd/{} || echo;" |
| 217 | "readlink /proc/self/fd/{} || echo;" |
| 218 | ") > {}"_format(file1->Fd(), file2->Fd(), file3->Fd(), filename)}; |
Jiakai Zhang | 7b6aef7 | 2022-12-12 19:30:44 +0000 | [diff] [blame] | 219 | |
Jiakai Zhang | d49058e | 2023-01-10 10:55:31 +0000 | [diff] [blame] | 220 | ScopedExecAndWait(args); |
Jiakai Zhang | 7b6aef7 | 2022-12-12 19:30:44 +0000 | [diff] [blame] | 221 | |
| 222 | std::string open_fds; |
| 223 | ASSERT_TRUE(android::base::ReadFileToString(filename, &open_fds)); |
| 224 | |
Jiakai Zhang | 7bc7ffe | 2023-02-02 00:17:04 +0800 | [diff] [blame] | 225 | // `file1` should be closed, while the other two should be open. There's a blank line at the end. |
| 226 | EXPECT_THAT(Split(open_fds, "\n"), ElementsAre(Not("/dev/zero"), "/dev/zero", "/dev/zero", "")); |
Jiakai Zhang | 7b6aef7 | 2022-12-12 19:30:44 +0000 | [diff] [blame] | 227 | } |
| 228 | |
Jiakai Zhang | d49058e | 2023-01-10 10:55:31 +0000 | [diff] [blame] | 229 | TEST_F(ArtExecTest, Env) { |
| 230 | std::string filename = "/data/local/tmp/art-exec-test-XXXXXX"; |
| 231 | ScratchFile scratch_file(new File(mkstemp(filename.data()), filename, /*check_usage=*/false)); |
| 232 | ASSERT_GE(scratch_file.GetFd(), 0); |
| 233 | |
| 234 | std::vector<std::string> args{art_exec_bin_, |
| 235 | "--env=FOO=BAR", |
| 236 | kEmptyLdLibraryPath, |
| 237 | "--", |
| 238 | GetBin("sh"), |
| 239 | "-c", |
| 240 | "env > " + filename}; |
| 241 | |
| 242 | ScopedExecAndWait(args); |
| 243 | |
| 244 | std::string envs; |
| 245 | ASSERT_TRUE(android::base::ReadFileToString(filename, &envs)); |
| 246 | |
| 247 | EXPECT_THAT(Split(envs, "\n"), Contains("FOO=BAR")); |
| 248 | } |
| 249 | |
Jiakai Zhang | 2b905c3 | 2022-07-20 15:49:34 +0100 | [diff] [blame] | 250 | } // namespace |
| 251 | } // namespace art |