• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::__anon85dbb9a10111::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::__anon85dbb9a10111::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::__anon85dbb9a10111::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::__anon85dbb9a10111::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