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