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