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::__anon99bcfb850111::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::__anon99bcfb850111::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::__anon99bcfb850111::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::__anon99bcfb850111::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 PlatformThreadChromeOS::DcheckCrossProcessThreadPrioritySequence();
244
245 int process_id = process_;
246 bool background = priority == Priority::kBestEffort;
247 internal::ForEachProcessTask(
248 process_,
249 [process_id, background](PlatformThreadId tid, const FilePath& path) {
250 PlatformThreadChromeOS::SetThreadBackgrounded(process_id, tid,
251 background);
252 });
253 }
254
255 if (CGroups::Get().enabled) {
256 std::string pid = NumberToString(process_);
257 const FilePath file =
258 priority == Priority::kBestEffort
259 ? CGroups::Get().background_file
260 : CGroups::Get().GetForegroundCgroupFile(unique_token_);
261 return WriteFile(file, pid);
262 }
263 #endif // BUILDFLAG(IS_CHROMEOS)
264
265 if (!CanSetPriority()) {
266 return false;
267 }
268
269 int priority_value = priority == Priority::kBestEffort ? kBackgroundPriority
270 : kForegroundPriority;
271 int result =
272 setpriority(PRIO_PROCESS, static_cast<id_t>(process_), priority_value);
273 DPCHECK(result == 0);
274 return result == 0;
275 }
276
277 #if BUILDFLAG(IS_CHROMEOS)
GetProcessPriorityCGroup(std::string_view cgroup_contents)278 Process::Priority GetProcessPriorityCGroup(std::string_view cgroup_contents) {
279 // The process can be part of multiple control groups, and for each cgroup
280 // hierarchy there's an entry in the file. We look for a control group
281 // named "/chrome_renderers/background" to determine if the process is
282 // backgrounded. crbug.com/548818.
283 std::vector<std::string_view> lines = SplitStringPiece(
284 cgroup_contents, "\n", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
285 for (const auto& line : lines) {
286 std::vector<std::string_view> fields =
287 SplitStringPiece(line, ":", TRIM_WHITESPACE, SPLIT_WANT_ALL);
288 if (fields.size() != 3U) {
289 NOTREACHED();
290 }
291 if (fields[2] == kBackground)
292 return Process::Priority::kBestEffort;
293 }
294
295 return Process::Priority::kUserBlocking;
296 }
297 #endif // BUILDFLAG(IS_CHROMEOS)
298
299 #if BUILDFLAG(IS_CHROMEOS_ASH)
300 // Reads /proc/<pid>/status and returns the PID in its PID namespace.
301 // If the process is not in a PID namespace or /proc/<pid>/status does not
302 // report NSpid, kNullProcessId is returned.
GetPidInNamespace() const303 ProcessId Process::GetPidInNamespace() const {
304 StringPairs pairs;
305 if (!internal::ReadProcFileToTrimmedStringPairs(process_, "status", &pairs)) {
306 return kNullProcessId;
307 }
308 for (const auto& pair : pairs) {
309 const std::string& key = pair.first;
310 const std::string& value_str = pair.second;
311 if (key == "NSpid") {
312 std::vector<std::string_view> split_value_str = SplitStringPiece(
313 value_str, "\t", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
314 if (split_value_str.size() <= 1) {
315 return kNullProcessId;
316 }
317 int value;
318 // The last value in the list is the PID in the namespace.
319 if (!StringToInt(split_value_str.back(), &value)) {
320 NOTREACHED();
321 }
322 return value;
323 }
324 }
325 return kNullProcessId;
326 }
327 #endif // BUILDFLAG(IS_CHROMEOS_ASH)
328
329 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
IsSeccompSandboxed()330 bool Process::IsSeccompSandboxed() {
331 uint64_t seccomp_value = 0;
332 if (!internal::ReadProcStatusAndGetFieldAsUint64(process_, "Seccomp",
333 &seccomp_value)) {
334 return false;
335 }
336 return seccomp_value > 0;
337 }
338 #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
339
340 #if BUILDFLAG(IS_CHROMEOS)
341 // static
SetProcessPriorityDelegate(ProcessPriorityDelegate * delegate)342 void Process::SetProcessPriorityDelegate(ProcessPriorityDelegate* delegate) {
343 // A component cannot override a delegate set by another component, thus
344 // disallow setting a delegate when one already exists.
345 DCHECK_NE(!!g_process_priority_delegate, !!delegate);
346
347 g_process_priority_delegate = delegate;
348 }
349
350 // static
OneGroupPerRendererEnabledForTesting()351 bool Process::OneGroupPerRendererEnabledForTesting() {
352 return OneGroupPerRendererEnabled();
353 }
354
InitializePriority()355 void Process::InitializePriority() {
356 if (g_process_priority_delegate) {
357 g_process_priority_delegate->InitializeProcessPriority(process_);
358 return;
359 }
360
361 if (!OneGroupPerRendererEnabled() || !IsValid() || !unique_token_.empty()) {
362 return;
363 }
364 // On Chrome OS, each renderer runs in its own cgroup when running in the
365 // foreground. After process creation the cgroup is created using a
366 // unique token.
367
368 // The token has the following format:
369 // {cgroup_prefix}{UnguessableToken}
370 // The cgroup prefix is to distinguish ash from lacros tokens for stale
371 // cgroup cleanup.
372 unique_token_ = StrCat({CGroups::Get().group_prefix_token,
373 UnguessableToken::Create().ToString()});
374
375 FilePath cgroup_path = CGroups::Get().GetForegroundCgroupDir(unique_token_);
376 // Note that CreateDirectoryAndGetError() does not fail if the directory
377 // already exits.
378 if (!CreateDirectoryAndGetError(cgroup_path, nullptr)) {
379 // If creating the directory fails, fall back to use the foreground group.
380 int saved_errno = errno;
381 LOG(ERROR) << "Failed to create cgroup, falling back to foreground"
382 << ", cgroup=" << cgroup_path
383 << ", errno=" << strerror(saved_errno);
384
385 unique_token_.clear();
386 return;
387 }
388
389 if (!CGroups::Get().uclamp_min.empty() &&
390 !WriteFile(cgroup_path.Append(kUclampMinFile),
391 CGroups::Get().uclamp_min)) {
392 LOG(ERROR) << "Failed to write uclamp min file, cgroup_path="
393 << cgroup_path;
394 }
395 if (!CGroups::Get().uclamp_min.empty() &&
396 !WriteFile(cgroup_path.Append(kUclampMaxFile),
397 CGroups::Get().uclamp_max)) {
398 LOG(ERROR) << "Failed to write uclamp max file, cgroup_path="
399 << cgroup_path;
400 }
401 }
402
ForgetPriority()403 void Process::ForgetPriority() {
404 if (g_process_priority_delegate) {
405 g_process_priority_delegate->ForgetProcessPriority(process_);
406 return;
407 }
408 }
409
410 // static
CleanUpProcessScheduled(Process process,int remaining_retries)411 void Process::CleanUpProcessScheduled(Process process, int remaining_retries) {
412 process.CleanUpProcess(remaining_retries);
413 }
414
CleanUpProcessAsync() const415 void Process::CleanUpProcessAsync() const {
416 if (!FeatureList::IsEnabled(kOneGroupPerRenderer) || unique_token_.empty()) {
417 return;
418 }
419
420 ThreadPool::PostTask(FROM_HERE, {MayBlock(), TaskPriority::BEST_EFFORT},
421 BindOnce(&Process::CleanUpProcessScheduled, Duplicate(),
422 kCgroupDeleteRetries));
423 }
424
CleanUpProcess(int remaining_retries) const425 void Process::CleanUpProcess(int remaining_retries) const {
426 if (!OneGroupPerRendererEnabled() || unique_token_.empty()) {
427 return;
428 }
429
430 // Try to delete the cgroup
431 // TODO(crbug.com/40224348): We can use notify_on_release to automoatically
432 // delete the cgroup when the process has left the cgroup.
433 FilePath cgroup = CGroups::Get().GetForegroundCgroupDir(unique_token_);
434 if (!DeleteFile(cgroup)) {
435 auto saved_errno = errno;
436 LOG(ERROR) << "Failed to delete cgroup " << cgroup
437 << ", errno=" << strerror(saved_errno);
438 // If the delete failed, then the process is still potentially in the
439 // cgroup. Move the process to background and schedule a callback to try
440 // again.
441 if (remaining_retries > 0) {
442 std::string pidstr = NumberToString(process_);
443 if (!WriteFile(CGroups::Get().background_file, pidstr)) {
444 // Failed to move the process, LOG a warning but try again.
445 saved_errno = errno;
446 LOG(WARNING) << "Failed to move the process to background"
447 << ", pid=" << pidstr
448 << ", errno=" << strerror(saved_errno);
449 }
450 ThreadPool::PostDelayedTask(FROM_HERE,
451 {MayBlock(), TaskPriority::BEST_EFFORT},
452 BindOnce(&Process::CleanUpProcessScheduled,
453 Duplicate(), remaining_retries - 1),
454 kCgroupDeleteRetryTime);
455 }
456 }
457 }
458
459 // static
CleanUpStaleProcessStates()460 void Process::CleanUpStaleProcessStates() {
461 if (!OneGroupPerRendererEnabled()) {
462 return;
463 }
464
465 FileEnumerator traversal(FilePath(kFullRendererCgroupRoot), false,
466 FileEnumerator::DIRECTORIES);
467 for (FilePath path = traversal.Next(); !path.empty();
468 path = traversal.Next()) {
469 std::string dirname = path.BaseName().value();
470 if (dirname == FilePath(kForeground).BaseName().value() ||
471 dirname == FilePath(kBackground).BaseName().value()) {
472 continue;
473 }
474
475 if (!StartsWith(dirname, kCgroupPrefix) ||
476 StartsWith(dirname, CGroups::Get().group_prefix_token)) {
477 continue;
478 }
479
480 if (!DeleteFile(path)) {
481 auto saved_errno = errno;
482 LOG(ERROR) << "Failed to delete " << path
483 << ", errno=" << strerror(saved_errno);
484 }
485 }
486 }
487 #endif // BUILDFLAG(IS_CHROMEOS)
488
489 } // namespace base
490