• 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 
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