• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 CgroupController &,const std::string &,const std::string &)105     void Reset(const CgroupController&, const std::string&, const std::string&) override {
106         CHECK(false);
107     }
controller() const108     const CgroupController* 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