xref: /aosp_15_r20/system/core/libprocessgroup/task_profiles_test.cpp (revision 00c7fec1bb09f3284aad6a6f96d2f63dfc3650ad)
1  /*
2   * Copyright (C) 2022 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 "task_profiles.h"
18  #include <android-base/logging.h>
19  #include <android-base/strings.h>
20  #include <gtest/gtest.h>
21  #include <mntent.h>
22  #include <processgroup/processgroup.h>
23  #include <stdio.h>
24  #include <unistd.h>
25  
26  #include <fstream>
27  
28  using ::android::base::ERROR;
29  using ::android::base::LogFunction;
30  using ::android::base::LogId;
31  using ::android::base::LogSeverity;
32  using ::android::base::SetLogger;
33  using ::android::base::Split;
34  using ::android::base::VERBOSE;
35  using ::testing::TestWithParam;
36  using ::testing::Values;
37  
38  namespace {
39  
IsCgroupV2MountedRw()40  bool IsCgroupV2MountedRw() {
41      std::unique_ptr<FILE, int (*)(FILE*)> mnts(setmntent("/proc/mounts", "re"), endmntent);
42      if (!mnts) {
43          LOG(ERROR) << "Failed to open /proc/mounts";
44          return false;
45      }
46      struct mntent* mnt;
47      while ((mnt = getmntent(mnts.get()))) {
48          if (strcmp(mnt->mnt_type, "cgroup2") != 0) {
49              continue;
50          }
51          const std::vector<std::string> options = Split(mnt->mnt_opts, ",");
52          return std::count(options.begin(), options.end(), "ro") == 0;
53      }
54      return false;
55  }
56  
57  class ScopedLogCapturer {
58    public:
59      struct log_args {
60          LogId log_buffer_id;
61          LogSeverity severity;
62          std::string tag;
63          std::string file;
64          unsigned int line;
65          std::string message;
66      };
67  
68      // Constructor. Installs a new logger and saves the currently active logger.
ScopedLogCapturer()69      ScopedLogCapturer() {
70          saved_severity_ = SetMinimumLogSeverity(android::base::VERBOSE);
71          saved_logger_ = SetLogger([this](LogId log_buffer_id, LogSeverity severity, const char* tag,
72                                           const char* file, unsigned int line, const char* message) {
73              if (saved_logger_) {
74                  saved_logger_(log_buffer_id, severity, tag, file, line, message);
75              }
76              log_.emplace_back(log_args{.log_buffer_id = log_buffer_id,
77                                         .severity = severity,
78                                         .tag = tag,
79                                         .file = file,
80                                         .line = line,
81                                         .message = message});
82          });
83      }
84      // Destructor. Restores the original logger and log level.
~ScopedLogCapturer()85      ~ScopedLogCapturer() {
86          SetLogger(std::move(saved_logger_));
87          SetMinimumLogSeverity(saved_severity_);
88      }
89      ScopedLogCapturer(const ScopedLogCapturer&) = delete;
90      ScopedLogCapturer& operator=(const ScopedLogCapturer&) = delete;
91      // Returns the logged lines.
Log() const92      const std::vector<log_args>& Log() const { return log_; }
93  
94    private:
95      LogSeverity saved_severity_;
96      LogFunction saved_logger_;
97      std::vector<log_args> log_;
98  };
99  
100  // cgroup attribute at the top level of the cgroup hierarchy.
101  class ProfileAttributeMock : public IProfileAttribute {
102    public:
ProfileAttributeMock(const std::string & file_name)103      ProfileAttributeMock(const std::string& file_name) : file_name_(file_name) {}
104      ~ProfileAttributeMock() override = default;
Reset(const CgroupControllerWrapper &,const std::string &,const std::string &)105      void Reset(const CgroupControllerWrapper&, const std::string&, const std::string&) override {
106          CHECK(false);
107      }
controller() const108      const CgroupControllerWrapper* controller() const override {
109          CHECK(false);
110          return {};
111      }
file_name() const112      const std::string& file_name() const override { return file_name_; }
GetPathForProcess(uid_t,pid_t pid,std::string * path) const113      bool GetPathForProcess(uid_t, pid_t pid, std::string* path) const override {
114          return GetPathForTask(pid, path);
115      }
GetPathForTask(int,std::string * path) const116      bool GetPathForTask(int, std::string* path) const override {
117  #ifdef __ANDROID__
118          CHECK(CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, path));
119          CHECK_GT(path->length(), 0);
120          if (path->rbegin()[0] != '/') {
121              *path += "/";
122          }
123  #else
124          // Not Android.
125          *path = "/sys/fs/cgroup/";
126  #endif
127          *path += file_name_;
128          return true;
129      };
130  
GetPathForUID(uid_t,std::string *) const131      bool GetPathForUID(uid_t, std::string*) const override { return false; }
132  
133    private:
134      const std::string file_name_;
135  };
136  
137  struct TestParam {
138      const char* attr_name;
139      const char* attr_value;
140      bool optional_attr;
141      bool result;
142      LogSeverity log_severity;
143      const char* log_prefix;
144      const char* log_suffix;
145  };
146  
147  class SetAttributeFixture : public TestWithParam<TestParam> {
148    public:
149      ~SetAttributeFixture() = default;
150  };
151  
TEST_P(SetAttributeFixture,SetAttribute)152  TEST_P(SetAttributeFixture, SetAttribute) {
153      // Treehugger runs host tests inside a container either without cgroupv2
154      // support or with the cgroup filesystem mounted read-only.
155      if (!IsCgroupV2MountedRw()) {
156          GTEST_SKIP();
157          return;
158      }
159      const TestParam params = GetParam();
160      ScopedLogCapturer captured_log;
161      ProfileAttributeMock pa(params.attr_name);
162      SetAttributeAction a(&pa, params.attr_value, params.optional_attr);
163      EXPECT_EQ(a.ExecuteForProcess(getuid(), getpid()), params.result);
164      auto log = captured_log.Log();
165      if (params.log_prefix || params.log_suffix) {
166          ASSERT_EQ(log.size(), 1);
167          EXPECT_EQ(log[0].severity, params.log_severity);
168          if (params.log_prefix) {
169              EXPECT_EQ(log[0].message.find(params.log_prefix), 0);
170          }
171          if (params.log_suffix) {
172              EXPECT_NE(log[0].message.find(params.log_suffix), std::string::npos);
173          }
174      } else {
175          ASSERT_EQ(log.size(), 0);
176      }
177  }
178  
179  class TaskProfileFixture : public TestWithParam<TestParam> {
180    public:
181      ~TaskProfileFixture() = default;
182  };
183  
TEST_P(TaskProfileFixture,TaskProfile)184  TEST_P(TaskProfileFixture, TaskProfile) {
185      // Treehugger runs host tests inside a container without cgroupv2 support.
186      if (!IsCgroupV2MountedRw()) {
187          GTEST_SKIP();
188          return;
189      }
190      const TestParam params = GetParam();
191      ProfileAttributeMock pa(params.attr_name);
192      // Test simple profile with one action
193      std::shared_ptr<TaskProfile> tp = std::make_shared<TaskProfile>("test_profile");
194      tp->Add(std::make_unique<SetAttributeAction>(&pa, params.attr_value, params.optional_attr));
195      EXPECT_EQ(tp->IsValidForProcess(getuid(), getpid()), params.result);
196      EXPECT_EQ(tp->IsValidForTask(getpid()), params.result);
197      // Test aggregate profile
198      TaskProfile tp2("meta_profile");
199      std::vector<std::shared_ptr<TaskProfile>> profiles = {tp};
200      tp2.Add(std::make_unique<ApplyProfileAction>(profiles));
201      EXPECT_EQ(tp2.IsValidForProcess(getuid(), getpid()), params.result);
202      EXPECT_EQ(tp2.IsValidForTask(getpid()), params.result);
203  }
204  
205  // Test the four combinations of optional_attr {false, true} and cgroup attribute { does not exist,
206  // exists }.
207  INSTANTIATE_TEST_SUITE_P(
208          SetAttributeTestSuite, SetAttributeFixture,
209          Values(
210                  // Test that attempting to write into a non-existing cgroup attribute fails and also
211                  // that an error message is logged.
212                  TestParam{.attr_name = "no-such-attribute",
213                            .attr_value = ".",
214                            .optional_attr = false,
215                            .result = false,
216                            .log_severity = ERROR,
217                            .log_prefix = "No such cgroup attribute"},
218                  // Test that attempting to write into an optional non-existing cgroup attribute
219                  // results in the return value 'true' and also that no messages are logged.
220                  TestParam{.attr_name = "no-such-attribute",
221                            .attr_value = ".",
222                            .optional_attr = true,
223                            .result = true},
224                  // Test that attempting to write an invalid value into an existing optional cgroup
225                  // attribute fails and also that it causes an error
226                  // message to be logged.
227                  TestParam{.attr_name = "cgroup.procs",
228                            .attr_value = "-1",
229                            .optional_attr = true,
230                            .result = false,
231                            .log_severity = ERROR,
232                            .log_prefix = "Failed to write",
233                            .log_suffix = geteuid() == 0 ? "Invalid argument" : "Permission denied"},
234                  // Test that attempting to write into an existing optional read-only cgroup
235                  // attribute fails and also that it causes an error message to be logged.
236                  TestParam{
237                          .attr_name = "cgroup.controllers",
238                          .attr_value = ".",
239                          .optional_attr = false,
240                          .result = false,
241                          .log_severity = ERROR,
242                          .log_prefix = "Failed to write",
243                          .log_suffix = geteuid() == 0 ? "Invalid argument" : "Permission denied"}));
244  
245  // Test TaskProfile IsValid calls.
246  INSTANTIATE_TEST_SUITE_P(
247          TaskProfileTestSuite, TaskProfileFixture,
248          Values(
249                  // Test operating on non-existing cgroup attribute fails.
250                  TestParam{.attr_name = "no-such-attribute",
251                            .attr_value = ".",
252                            .optional_attr = false,
253                            .result = false},
254                  // Test operating on optional non-existing cgroup attribute succeeds.
255                  TestParam{.attr_name = "no-such-attribute",
256                            .attr_value = ".",
257                            .optional_attr = true,
258                            .result = true},
259                  // Test operating on existing cgroup attribute succeeds.
260                  TestParam{.attr_name = "cgroup.procs",
261                            .attr_value = ".",
262                            .optional_attr = false,
263                            .result = true},
264                  // Test operating on optional existing cgroup attribute succeeds.
265                  TestParam{.attr_name = "cgroup.procs",
266                            .attr_value = ".",
267                            .optional_attr = true,
268                            .result = true}));
269  }  // namespace
270