xref: /aosp_15_r20/external/cronet/base/process/process_linux.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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