1 // Copyright 2011 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/process/process.h"
6
7 #include <errno.h>
8 #include <linux/magic.h>
9 #include <sys/resource.h>
10 #include <sys/vfs.h>
11
12 #include <cstring>
13
14 #include "base/check.h"
15 #include "base/files/file_util.h"
16 #include "base/location.h"
17 #include "base/logging.h"
18 #include "base/notreached.h"
19 #include "base/posix/can_lower_nice_to.h"
20 #include "base/process/internal_linux.h"
21 #include "base/strings/string_number_conversions.h"
22 #include "base/strings/string_split.h"
23 #include "base/strings/stringprintf.h"
24 #include "base/threading/platform_thread.h"
25 #include "base/threading/platform_thread_internal_posix.h"
26 #include "base/threading/thread_restrictions.h"
27 #include "build/build_config.h"
28 #include "build/chromeos_buildflags.h"
29
30 #if BUILDFLAG(IS_CHROMEOS)
31 #include "base/feature_list.h"
32 #include "base/files/file_enumerator.h"
33 #include "base/files/file_path.h"
34 #include "base/functional/bind.h"
35 #include "base/process/process_handle.h"
36 #include "base/strings/strcat.h"
37 #include "base/strings/string_util.h"
38 #include "base/task/thread_pool.h"
39 #include "base/unguessable_token.h"
40 #endif // BUILDFLAG(IS_CHROMEOS)
41
42 namespace base {
43
44 #if BUILDFLAG(IS_CHROMEOS)
45 BASE_FEATURE(kOneGroupPerRenderer,
46 "OneGroupPerRenderer",
47 #if BUILDFLAG(IS_CHROMEOS_LACROS)
48 FEATURE_ENABLED_BY_DEFAULT);
49 #else
50 FEATURE_DISABLED_BY_DEFAULT);
51 #endif // BUILDFLAG(IS_CHROMEOS_LACROS)
52 #endif // BUILDFLAG(IS_CHROMEOS)
53
54 namespace {
55
56 const int kForegroundPriority = 0;
57
58 #if BUILDFLAG(IS_CHROMEOS)
59 // We are more aggressive in our lowering of background process priority
60 // for chromeos as we have much more control over other processes running
61 // on the machine.
62 //
63 // TODO(davemoore) Refactor this by adding support for higher levels to set
64 // the foregrounding / backgrounding process so we don't have to keep
65 // chrome / chromeos specific logic here.
66 const int kBackgroundPriority = 19;
67 const char kControlPath[] = "/sys/fs/cgroup/cpu%s/cgroup.procs";
68 const char kFullRendererCgroupRoot[] = "/sys/fs/cgroup/cpu/chrome_renderers";
69 const char kForeground[] = "/chrome_renderers/foreground";
70 const char kBackground[] = "/chrome_renderers/background";
71 const char kProcPath[] = "/proc/%d/cgroup";
72 const char kUclampMinFile[] = "cpu.uclamp.min";
73 const char kUclampMaxFile[] = "cpu.uclamp.max";
74
75 constexpr int kCgroupDeleteRetries = 3;
76 constexpr TimeDelta kCgroupDeleteRetryTime(Seconds(1));
77
78 #if BUILDFLAG(IS_CHROMEOS_LACROS)
79 const char kCgroupPrefix[] = "l-";
80 #elif BUILDFLAG(IS_CHROMEOS_ASH)
81 const char kCgroupPrefix[] = "a-";
82 #endif
83
PathIsCGroupFileSystem(const FilePath & path)84 bool PathIsCGroupFileSystem(const FilePath& path) {
85 struct statfs statfs_buf;
86 if (statfs(path.value().c_str(), &statfs_buf) < 0)
87 return false;
88 return statfs_buf.f_type == CGROUP_SUPER_MAGIC;
89 }
90
91 struct CGroups {
92 // Check for cgroups files. ChromeOS supports these by default. It creates
93 // a cgroup mount in /sys/fs/cgroup and then configures two cpu task groups,
94 // one contains at most a single foreground renderer and the other contains
95 // all background renderers. This allows us to limit the impact of background
96 // renderers on foreground ones to a greater level than simple renicing.
97 bool enabled;
98 FilePath foreground_file;
99 FilePath background_file;
100
101 // A unique token for this instance of the browser.
102 std::string group_prefix_token;
103
104 // UCLAMP settings for the foreground cgroups.
105 std::string uclamp_min;
106 std::string uclamp_max;
107
CGroupsbase::__anon02ba8fb90111::CGroups108 CGroups() {
109 foreground_file = FilePath(StringPrintf(kControlPath, kForeground));
110 background_file = FilePath(StringPrintf(kControlPath, kBackground));
111 enabled = PathIsCGroupFileSystem(foreground_file) &&
112 PathIsCGroupFileSystem(background_file);
113
114 if (!enabled || !FeatureList::IsEnabled(kOneGroupPerRenderer)) {
115 return;
116 }
117
118 // Generate a unique token for the full browser process
119 group_prefix_token =
120 StrCat({kCgroupPrefix, UnguessableToken::Create().ToString(), "-"});
121
122 // Reads the ULCAMP settings from the foreground cgroup that will be used
123 // for each renderer's cgroup.
124 FilePath foreground_path = foreground_file.DirName();
125 ReadFileToString(foreground_path.Append(kUclampMinFile), &uclamp_min);
126 ReadFileToString(foreground_path.Append(kUclampMaxFile), &uclamp_max);
127 }
128
129 // Returns the full path to a the cgroup dir of a process using
130 // the supplied token.
GetForegroundCgroupDirbase::__anon02ba8fb90111::CGroups131 static FilePath GetForegroundCgroupDir(const std::string& token) {
132 // Get individualized cgroup if the feature is enabled
133 std::string cgroup_path_str;
134 StrAppend(&cgroup_path_str, {kFullRendererCgroupRoot, "/", token});
135 return FilePath(cgroup_path_str);
136 }
137
138 // Returns the path to the cgroup.procs file of the foreground cgroup.
GetForegroundCgroupFilebase::__anon02ba8fb90111::CGroups139 static FilePath GetForegroundCgroupFile(const std::string& token) {
140 // Processes with an empty token use the default foreground cgroup.
141 if (token.empty()) {
142 return CGroups::Get().foreground_file;
143 }
144
145 FilePath cgroup_path = GetForegroundCgroupDir(token);
146 return cgroup_path.Append("cgroup.procs");
147 }
148
Getbase::__anon02ba8fb90111::CGroups149 static CGroups& Get() {
150 static auto& groups = *new CGroups;
151 return groups;
152 }
153 };
154
155 // Returns true if the 'OneGroupPerRenderer' feature is enabled. The feature
156 // is enabled if the kOneGroupPerRenderer feature flag is enabled and the
157 // system supports the chrome cgroups. Will block if this is the first call
158 // that will read the cgroup configs.
OneGroupPerRendererEnabled()159 bool OneGroupPerRendererEnabled() {
160 return FeatureList::IsEnabled(kOneGroupPerRenderer) && CGroups::Get().enabled;
161 }
162 #else
163 const int kBackgroundPriority = 5;
164 #endif // BUILDFLAG(IS_CHROMEOS)
165
166 } // namespace
167
CreationTime() const168 Time Process::CreationTime() const {
169 int64_t start_ticks = is_current()
170 ? internal::ReadProcSelfStatsAndGetFieldAsInt64(
171 internal::VM_STARTTIME)
172 : internal::ReadProcStatsAndGetFieldAsInt64(
173 Pid(), internal::VM_STARTTIME);
174
175 if (!start_ticks)
176 return Time();
177
178 TimeDelta start_offset = internal::ClockTicksToTimeDelta(start_ticks);
179 Time boot_time = internal::GetBootTime();
180 if (boot_time.is_null())
181 return Time();
182 return Time(boot_time + start_offset);
183 }
184
185 // static
CanSetPriority()186 bool Process::CanSetPriority() {
187 #if BUILDFLAG(IS_CHROMEOS)
188 if (CGroups::Get().enabled)
189 return true;
190 #endif // BUILDFLAG(IS_CHROMEOS)
191
192 static const bool can_reraise_priority =
193 internal::CanLowerNiceTo(kForegroundPriority);
194 return can_reraise_priority;
195 }
196
GetPriority() const197 Process::Priority Process::GetPriority() const {
198 DCHECK(IsValid());
199
200 #if BUILDFLAG(IS_CHROMEOS)
201 if (CGroups::Get().enabled) {
202 // Used to allow reading the process priority from proc on thread launch.
203 ScopedAllowBlocking scoped_allow_blocking;
204 std::string proc;
205 if (ReadFileToString(FilePath(StringPrintf(kProcPath, process_)), &proc)) {
206 return GetProcessPriorityCGroup(proc);
207 }
208 return Priority::kUserBlocking;
209 }
210 #endif // BUILDFLAG(IS_CHROMEOS)
211
212 return GetOSPriority() == kBackgroundPriority ? Priority::kBestEffort
213 : Priority::kUserBlocking;
214 }
215
SetPriority(Priority priority)216 bool Process::SetPriority(Priority priority) {
217 DCHECK(IsValid());
218
219 #if BUILDFLAG(IS_CHROMEOS)
220 // Go through all the threads for a process and set it as [un]backgrounded.
221 // Threads that are created after this call will also be [un]backgrounded by
222 // detecting that the main thread of the process has been [un]backgrounded.
223
224 // Should not be called concurrently with other functions
225 // like SetThreadType().
226 if (PlatformThreadChromeOS::IsThreadsBgFeatureEnabled()) {
227 DCHECK_CALLED_ON_VALID_SEQUENCE(
228 PlatformThreadChromeOS::GetCrossProcessThreadPrioritySequenceChecker());
229
230 int process_id = process_;
231 bool background = priority == Priority::kBestEffort;
232 internal::ForEachProcessTask(
233 process_,
234 [process_id, background](PlatformThreadId tid, const FilePath& path) {
235 PlatformThreadChromeOS::SetThreadBackgrounded(process_id, tid,
236 background);
237 });
238 }
239
240 if (CGroups::Get().enabled) {
241 std::string pid = NumberToString(process_);
242 const FilePath file =
243 priority == Priority::kBestEffort
244 ? CGroups::Get().background_file
245 : CGroups::Get().GetForegroundCgroupFile(unique_token_);
246 return WriteFile(file, pid);
247 }
248 #endif // BUILDFLAG(IS_CHROMEOS)
249
250 if (!CanSetPriority()) {
251 return false;
252 }
253
254 int priority_value = priority == Priority::kBestEffort ? kBackgroundPriority
255 : kForegroundPriority;
256 int result =
257 setpriority(PRIO_PROCESS, static_cast<id_t>(process_), priority_value);
258 DPCHECK(result == 0);
259 return result == 0;
260 }
261
262 #if BUILDFLAG(IS_CHROMEOS)
GetProcessPriorityCGroup(const StringPiece & cgroup_contents)263 Process::Priority GetProcessPriorityCGroup(const StringPiece& cgroup_contents) {
264 // The process can be part of multiple control groups, and for each cgroup
265 // hierarchy there's an entry in the file. We look for a control group
266 // named "/chrome_renderers/background" to determine if the process is
267 // backgrounded. crbug.com/548818.
268 std::vector<StringPiece> lines = SplitStringPiece(
269 cgroup_contents, "\n", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
270 for (const auto& line : lines) {
271 std::vector<StringPiece> fields =
272 SplitStringPiece(line, ":", TRIM_WHITESPACE, SPLIT_WANT_ALL);
273 if (fields.size() != 3U) {
274 NOTREACHED();
275 continue;
276 }
277 if (fields[2] == kBackground)
278 return Process::Priority::kBestEffort;
279 }
280
281 return Process::Priority::kUserBlocking;
282 }
283 #endif // BUILDFLAG(IS_CHROMEOS)
284
285 #if BUILDFLAG(IS_CHROMEOS_ASH)
286 // Reads /proc/<pid>/status and returns the PID in its PID namespace.
287 // If the process is not in a PID namespace or /proc/<pid>/status does not
288 // report NSpid, kNullProcessId is returned.
GetPidInNamespace() const289 ProcessId Process::GetPidInNamespace() const {
290 StringPairs pairs;
291 if (!internal::ReadProcFileToTrimmedStringPairs(process_, "status", &pairs)) {
292 return kNullProcessId;
293 }
294 for (const auto& pair : pairs) {
295 const std::string& key = pair.first;
296 const std::string& value_str = pair.second;
297 if (key == "NSpid") {
298 std::vector<StringPiece> split_value_str = SplitStringPiece(
299 value_str, "\t", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
300 if (split_value_str.size() <= 1) {
301 return kNullProcessId;
302 }
303 int value;
304 // The last value in the list is the PID in the namespace.
305 if (!StringToInt(split_value_str.back(), &value)) {
306 NOTREACHED();
307 return kNullProcessId;
308 }
309 return value;
310 }
311 }
312 return kNullProcessId;
313 }
314 #endif // BUILDFLAG(IS_CHROMEOS_ASH)
315
316 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
IsSeccompSandboxed()317 bool Process::IsSeccompSandboxed() {
318 uint64_t seccomp_value = 0;
319 if (!internal::ReadProcStatusAndGetFieldAsUint64(process_, "Seccomp",
320 &seccomp_value)) {
321 return false;
322 }
323 return seccomp_value > 0;
324 }
325 #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
326
327 #if BUILDFLAG(IS_CHROMEOS)
328 // static
OneGroupPerRendererEnabledForTesting()329 bool Process::OneGroupPerRendererEnabledForTesting() {
330 return OneGroupPerRendererEnabled();
331 }
332
333 // On Chrome OS, each renderer runs in its own cgroup when running in the
334 // foreground. After process creation the cgroup is created using a
335 // unique token.
InitializePriority()336 void Process::InitializePriority() {
337 if (!OneGroupPerRendererEnabled() || !IsValid() || !unique_token_.empty()) {
338 return;
339 }
340
341 // The token has the following format:
342 // {cgroup_prefix}{UnguessableToken}
343 // The cgroup prefix is to distinguish ash from lacros tokens for stale
344 // cgroup cleanup.
345 unique_token_ = StrCat({CGroups::Get().group_prefix_token,
346 UnguessableToken::Create().ToString()});
347
348 FilePath cgroup_path = CGroups::Get().GetForegroundCgroupDir(unique_token_);
349 // Note that CreateDirectoryAndGetError() does not fail if the directory
350 // already exits.
351 if (!CreateDirectoryAndGetError(cgroup_path, nullptr)) {
352 // If creating the directory fails, fall back to use the foreground group.
353 int saved_errno = errno;
354 LOG(ERROR) << "Failed to create cgroup, falling back to foreground"
355 << ", cgroup=" << cgroup_path
356 << ", errno=" << strerror(saved_errno);
357
358 unique_token_.clear();
359 return;
360 }
361
362 if (!CGroups::Get().uclamp_min.empty() &&
363 !WriteFile(cgroup_path.Append(kUclampMinFile),
364 CGroups::Get().uclamp_min)) {
365 LOG(ERROR) << "Failed to write uclamp min file, cgroup_path="
366 << cgroup_path;
367 }
368 if (!CGroups::Get().uclamp_min.empty() &&
369 !WriteFile(cgroup_path.Append(kUclampMaxFile),
370 CGroups::Get().uclamp_max)) {
371 LOG(ERROR) << "Failed to write uclamp max file, cgroup_path="
372 << cgroup_path;
373 }
374 }
375
376 // static
CleanUpProcessScheduled(Process process,int remaining_retries)377 void Process::CleanUpProcessScheduled(Process process, int remaining_retries) {
378 process.CleanUpProcess(remaining_retries);
379 }
380
CleanUpProcessAsync() const381 void Process::CleanUpProcessAsync() const {
382 if (!FeatureList::IsEnabled(kOneGroupPerRenderer) || unique_token_.empty()) {
383 return;
384 }
385
386 ThreadPool::PostTask(FROM_HERE, {MayBlock(), TaskPriority::BEST_EFFORT},
387 BindOnce(&Process::CleanUpProcessScheduled, Duplicate(),
388 kCgroupDeleteRetries));
389 }
390
CleanUpProcess(int remaining_retries) const391 void Process::CleanUpProcess(int remaining_retries) const {
392 if (!OneGroupPerRendererEnabled() || unique_token_.empty()) {
393 return;
394 }
395
396 // Try to delete the cgroup
397 // TODO(1322562): We can use notify_on_release to automoatically delete the
398 // cgroup when the process has left the cgroup.
399 FilePath cgroup = CGroups::Get().GetForegroundCgroupDir(unique_token_);
400 if (!DeleteFile(cgroup)) {
401 auto saved_errno = errno;
402 LOG(ERROR) << "Failed to delete cgroup " << cgroup
403 << ", errno=" << strerror(saved_errno);
404 // If the delete failed, then the process is still potentially in the
405 // cgroup. Move the process to background and schedule a callback to try
406 // again.
407 if (remaining_retries > 0) {
408 std::string pidstr = NumberToString(process_);
409 if (!WriteFile(CGroups::Get().background_file, pidstr)) {
410 // Failed to move the process, LOG a warning but try again.
411 saved_errno = errno;
412 LOG(WARNING) << "Failed to move the process to background"
413 << ", pid=" << pidstr
414 << ", errno=" << strerror(saved_errno);
415 }
416 ThreadPool::PostDelayedTask(FROM_HERE,
417 {MayBlock(), TaskPriority::BEST_EFFORT},
418 BindOnce(&Process::CleanUpProcessScheduled,
419 Duplicate(), remaining_retries - 1),
420 kCgroupDeleteRetryTime);
421 }
422 }
423 }
424
425 // static
CleanUpStaleProcessStates()426 void Process::CleanUpStaleProcessStates() {
427 if (!OneGroupPerRendererEnabled()) {
428 return;
429 }
430
431 FileEnumerator traversal(FilePath(kFullRendererCgroupRoot), false,
432 FileEnumerator::DIRECTORIES);
433 for (FilePath path = traversal.Next(); !path.empty();
434 path = traversal.Next()) {
435 std::string dirname = path.BaseName().value();
436 if (dirname == FilePath(kForeground).BaseName().value() ||
437 dirname == FilePath(kBackground).BaseName().value()) {
438 continue;
439 }
440
441 if (!StartsWith(dirname, kCgroupPrefix) ||
442 StartsWith(dirname, CGroups::Get().group_prefix_token)) {
443 continue;
444 }
445
446 if (!DeleteFile(path)) {
447 auto saved_errno = errno;
448 LOG(ERROR) << "Failed to delete " << path
449 << ", errno=" << strerror(saved_errno);
450 }
451 }
452 }
453 #endif // BUILDFLAG(IS_CHROMEOS)
454
455 } // namespace base
456