File: code_sign_clone_manager.h

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (170 lines) | stat: -rw-r--r-- 7,952 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_MAC_CODE_SIGN_CLONE_MANAGER_H_
#define CHROME_BROWSER_MAC_CODE_SIGN_CLONE_MANAGER_H_

#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/callback_helpers.h"
#include "base/timer/timer.h"
#include "content/public/common/main_function_params.h"

namespace code_sign_clone_manager {

BASE_DECLARE_FEATURE(kMacAppCodeSignClone);
BASE_DECLARE_FEATURE(kMacAppCodeSignCloneRenameAsBundle);

//
// Manages a temporary copy-on-write clone of an app bundle. The temporary clone
// crucially has its main executable replaced with a hard link to the source's
// main executable. This is intended for use by the browser app to keep in-use
// files available on the filesystem after a staged update. This is in service
// of keeping the app's code signature statically valid and in agreement with
// its dynamic code signature after a staged update. See
// https://crbug.com/338582873 for more details.
//
// During initialization, the app bundle in `src_path` will be APFS
// copy-on-write cloned to a temporary directory. The main executable in the
// clone is then replaced with a hard link to the `src_path` main executable.
// The hard linked main executable is crucial in passing dynamic code signature
// validation after an update is staged. For dynamic validation, we need to keep
// a reachable path to the main executable alive on the filesystem, which is why
// it is hard linked. To support support static code signature validation, the
// hard linked main executable needs to be surrounded by a bundle structure that
// validates. The rest of the cloned files make up this bundle structure and
// support static code signature validation.
//
// Creation of this clone is best effort, there are no guarantees of success.
// Some failure cases include running the source app bundle from:
//   * not-APFS (because filesystem clones are only supported on APFS)
//   * a read-only filesystem (because this needs to write to the same
//   filesystem that the app is running from)
//   * a non-root filesystem (because the clone is built in
//   `/private/var/folders/...`, which is on the root filesystem)
//
// Upon destruction, the temporary directory hosting the clone will be deleted.
//
// Clone creation and deletion will take place in the background and will not
// block. Background clone creation will take place in the calling process on a
// background thread to prevent blocking startup. Care has been taken to
// optimize clone creation to avoid resource contention on startup. Background
// clone deletion will take place from a helper process. This is to prevent
// blocking browser shutdown, with the added benefit of keeping in-use files
// available on the filesystem until the very end of the browser process.
//
// Cloning and hard linking the main executable was chosen as it is the least
// expensive out of the explored options (tested with Google Chrome M125 on an
// M1 Max Mac).
//
// `clonefile` + `link` the main executable: ~10ms
// Hard linking the whole app tree: best approach ~60ms
//   * `-[NSFileManager linkItemAtURL:toURL:error:]`: ~120ms
//   * `base::FileEnumerator`: ~80ms
//   * `readdir`, `getattrlistbulk`, `fts` only `stat`ing dirs: ~60ms
//
// In the common case, both options (`clonefile` vs. hard link-based) take up no
// extra disk space, aside from directory entries, while `clonefile` involves
// the least amount of touching of the disk. The only disadvantage of
// `clonefile` is that it’s currently APFS-only. The purely hard link-based
// approaches would be more broadly applicable (on HFS+ for example). This is
// not a major concern given how ubiquitous APFS on macOS has become.
//
// Each instance of Chrome will create a new temporary clone of itself at
// startup even if one already exists. In this way each instance of Chrome
// maintains a reference to its specific in-use files, keeping them accessible
// on the filesystem until exit. Once the last instance of Chrome that maintains
// a reference to the in-use files exits and its cleanup helper runs, the files
// will become inaccessible on the filesystem and their disk blocks will be
// freed.
//
// The clone is given a ".bundle" extension to avoid Launch Services issues; see
// https://crbug.com/381199182 for more details.
//
// Example path to the cloned app bundle:
//   /private/var/folders/c4/ygf_t4gn0tx0k1y1hm32hh6w00b_4p/X/org.chromium.Chromium.code_sign_clone/code_sign_clone.tKdILk/Chromium.app.bundle
//
// Each clone contains an instance-specific snapshot of an on-disk
// representation of Chrome. The bundles are verifiable by both dynamic and
// static code signature checks.
//
class CodeSignCloneManager {
 public:
  using CloneCallback = base::OnceCallback<void(base::FilePath)>;

  //
  // `src_path` is the path to the app bundle to be cloned.
  //
  // `main_executable_name` is the name of the main executable to be hard
  // linked. The name must be an entry in the "Contents/MacOS" directory within
  // the `src_path`.
  //
  // `callback` is called after the clone has been created or if an error
  // occurs.
  //
  CodeSignCloneManager(const base::FilePath& src_path,
                       const base::FilePath& main_executable_name,
                       CloneCallback callback = base::DoNothing());
  CodeSignCloneManager(const CodeSignCloneManager&) = delete;
  CodeSignCloneManager& operator=(const CodeSignCloneManager&) = delete;
  ~CodeSignCloneManager();

  static void SetTemporaryDirectoryPathForTesting(const base::FilePath& path);
  static void ClearTemporaryDirectoryPathForTesting();
  static void SetDirhelperPathForTesting(const base::FilePath& path);
  static void ClearDirhelperPathForTesting();
  static base::FilePath GetCloneTemporaryDirectoryForTesting();
  bool get_needs_cleanup_for_testing() { return needs_cleanup_; }

 private:
  void Clone(const base::FilePath& src_path,
             const base::FilePath& main_executable_name,
             CloneCallback callback);
  void StartCloneExistsTimer(const base::FilePath& clone_app_path,
                             const base::FilePath& main_executable_name);
  void StopCloneExistsTimer();
  void CloneExistsTimerFire(const base::FilePath& clone_app_path,
                            const base::FilePath& main_executable_name);

  std::string unique_temp_dir_suffix_;
  base::RepeatingTimer clone_exists_timer_;
  scoped_refptr<base::SequencedTaskRunner> task_runner_;
  bool needs_cleanup_ = false;
};

namespace internal {

// The entry point into the background clone cleanup process. This is not a user
// API.
int ChromeCodeSignCloneCleanupMain(content::MainFunctionParams main_parameters);

//
// Checks if a file is open more than once, globally (across all processes on a
// host), including the calling process.
//
// Usage of this check is fairly niche. It does not provide any information
// about which process has a reference to an open file. For that see
// `proc_listpidspath` (The linked header is from macOS 14.5).
// https://github.com/apple-oss-distributions/xnu/blob/xnu-10063.121.3/libsyscall/wrappers/libproc/libproc.h
//
// This function simply checks if a file is exclusively opened. Performance
// concerns may be a reason to use this function over `proc_listpidspath`, which
// needs to loop over the process tree.
//
// Note: File descriptors that have been `dup`ed or inherited only count as
// being opened once for this check’s purpose.
//
enum class FileOpenMoreThanOnce {
  kNo = 0,
  kYes = 1,
  kError = 2,
};
FileOpenMoreThanOnce IsFileOpenMoreThanOnce(const base::FilePath& file_path);
FileOpenMoreThanOnce IsFileOpenMoreThanOnce(int file_descriptor);

}  // namespace internal

}  // namespace code_sign_clone_manager

#endif  // CHROME_BROWSER_MAC_CODE_SIGN_CLONE_MANAGER_H_