forked from chromium/chromium
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathchrome_exe_main_mac.cc
213 lines (192 loc) · 9.83 KB
/
chrome_exe_main_mac.cc
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// The entry point for all Mac Chromium processes, including the outer app
// bundle (browser) and helper app (renderer, plugin, and friends).
#include <dlfcn.h>
#include <errno.h>
#include <libgen.h>
#include <mach-o/dyld.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <memory>
#include "base/allocator/early_zone_registration_mac.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "chrome/common/chrome_version.h"
#if defined(HELPER_EXECUTABLE)
#include "sandbox/mac/seatbelt_exec.h" // nogncheck
#endif
extern "C" {
// abort_report_np() records the message in a special section that both the
// system CrashReporter and Crashpad collect in crash reports. Using a Crashpad
// Annotation would be preferable, but this executable cannot depend on
// Crashpad directly.
void abort_report_np(const char* fmt, ...);
}
namespace {
typedef int (*ChromeMainPtr)(int, char**);
#if !defined(HELPER_EXECUTABLE) && defined(OFFICIAL_BUILD) && \
BUILDFLAG(GOOGLE_CHROME_BRANDING) && defined(ARCH_CPU_X86_64)
// This is for https://crbug.com/1300598, and more generally,
// https://crbug.com/1297588 (and all of the associated bugs). It's horrible!
//
// When the main executable is updated on disk while the application is running,
// and the offset of the Mach-O image at the main executable's path changes from
// the offset that was determined when the executable was loaded, SecCode ceases
// to be able to work with the executable. This may be triggered when the
// product is updated on disk but the application has not yet relaunched. This
// affects SecCodeCopySelf and SecCodeCopyGuestWithAttributes. Bugs are evident
// even when validation (SecCodeCheckValidity) is not attempted.
//
// Practically, this is only a concern for fat (universal) files, because the
// offset of a Mach-O image in a thin (single-architecture) file is always 0.
// The branded product always ships a fat executable, and because some uses of
// SecCode are in OS code beyond Chrome's control, an effort is made to freeze
// the geometry of the branded (BUILDFLAG(GOOGLE_CHROME_BRANDING))
// for-public-release (defined(OFFICIAL_BUILD)) main executable.
//
// The fat file is produced by installer/mac/universalizer.py. The x86_64 slice
// always precedes the arm64 slice: lipo, as used by universalizer.py, always
// places the arm64 slice last. See Xcode 12.0
// https://github.com/apple-oss-distributions/cctools/blob/cctools-973.0.1/misc/lipo.c#L2672
// cmp_qsort, used by create_fat at #L962. universalizer.py ensures that the
// first slice in the file is located at a constant offset (16kB since
// 98.0.4758.80), but if the first slice's size changes, it can affect the
// offset of the second slice, the arm64 one, triggering SecCode-related bugs
// for arm64 users across updates.
//
// As quite a hack of a workaround, the offset of the arm64 slice within the fat
// main executable is influenced to land at the desired location by introducing
// padding to the x86_64 slice that precedes it. The arm64 slice needs to remain
// at offset 304kB (since 98.0.4758.80). The signed x86_64 slice has size 287296
// bytes in 98.0.4758.80, but has shrunk since then, and before the introduction
// of any padding, would now be 216784 bytes long. To make up the 70512-byte
// difference, 68kB (69632 bytes) of padding is added to the x86_64 slice to
// ensure that its size is stable, causing the arm64 slice to land where it
// needs to be when universalized. This padding needs to be added to the thin
// form of the x86_64 image before being fed to universalizer.py. Why 69632
// bytes and not 70512? To keep it an even multiple of linker pages (not machine
// pages: linker pages are 4kB for lld targeting x86_64 and 16kB for ld64
// targeting x86_64, but Chrome uses lld). In any case, I'll make up almost all
// of the 880-byte difference with one more weird trick below.
//
// There are several terrible ways to insert this padding into the x86_64 image.
// Best would be something that considers the size of the x86_64 image without
// padding, and inserts the precise amount required. It may be possible to do
// this after linking, but the options that have been attempted so far were not
// successful. So this quick and very dirty 68kB buffer is added to increase the
// size of __TEXT,__const in a way that no tool could possibly see as suspicious
// after link time. The variable is marked with the "used" attribute to prevent
// the compiler from issuing warnings about the referenced variable, to prevent
// the compiler from removing it under optimization, and to set the
// S_ATTR_NO_DEAD_STRIP section attribute to prevent the linker from removing it
// under -dead_strip. Note that the standardized [[maybe_unused]] attribute only
// suppresses the warning, but does not prevent the compiler or linker from
// removing it.
//
// The introduction of this fixed 68kB of padding causes the unsigned linker
// output to grow by 68kB precisely, but the signed output will grow by slightly
// more. This is because the code signature's code directory contains SHA-1 and
// SHA-256 hashes of each 4kB code signing page (note, not machine pages or
// linker pages) in the image, adding 20 and 32 bytes each (macOS 12.0.1
// https://github.com/apple-oss-distributions/Security/blob/main/OSX/libsecurity_codesigning/lib/signer.cpp#L298
// Security::CodeSigning::SecCodeSigner::Signer::prepare). For the 68kB
// addition, the code signature grows by (68 / 4) * (20 + 32) = 884 bytes, thus
// the total size of the linker output grows by 68kB + 884 = 70516 bytes. It is
// not possible to control this any more granularly: if the buffer were sized at
// 68kB - 884 = 68748 bytes, it would either cause no change in the space
// allocated to the __TEXT segment (due to padding for alignment) or would cause
// the segment to shrink by a linker page (note, not a code signing or machine
// page) which would which would cause the linker output to shrink by the same
// amount and would be absolutely undesirable. Luckily, the net growth of 70516
// bytes is almost at the target of 70512 on the nose. In any event, having the
// signed x86_64 slice sized at 287300 bytes instead of 287296 should not be a
// problem. Subtle differences in characteristics including the code signature
// itself can easily produce differences of that magnitude. It's necessary for
// the size to wind up in the range (278528, 294912], and as long as that's met,
// the 16kB alignment for the arm64 slice that follows it in the fat file will
// cause it to appear at the desired 304kB.
//
// If the main executable has a significant change in size, this will need to be
// revised. Hopefully a more elegant solution will become apparent before that's
// required.
__attribute__((used)) const char kGrossPaddingForCrbug1300598[68 * 1024] = {};
#endif
[[noreturn]] void FatalError(const char* format, ...) {
va_list valist;
va_start(valist, format);
char message[4096];
if (vsnprintf(message, sizeof(message), format, valist) >= 0) {
fputs(message, stderr);
abort_report_np("%s", message);
}
va_end(valist);
abort();
}
} // namespace
__attribute__((visibility("default"))) int main(int argc, char* argv[]) {
partition_alloc::EarlyMallocZoneRegistration();
uint32_t exec_path_size = 0;
int rv = _NSGetExecutablePath(NULL, &exec_path_size);
if (rv != -1) {
FatalError("_NSGetExecutablePath: get length failed.");
}
std::unique_ptr<char[]> exec_path(new char[exec_path_size]);
rv = _NSGetExecutablePath(exec_path.get(), &exec_path_size);
if (rv != 0) {
FatalError("_NSGetExecutablePath: get path failed.");
}
#if defined(HELPER_EXECUTABLE)
sandbox::SeatbeltExecServer::CreateFromArgumentsResult seatbelt =
sandbox::SeatbeltExecServer::CreateFromArguments(exec_path.get(), argc,
argv);
if (seatbelt.sandbox_required) {
if (!seatbelt.server) {
FatalError("Failed to create seatbelt sandbox server.");
}
if (!seatbelt.server->InitializeSandbox()) {
FatalError("Failed to initialize sandbox.");
}
}
// The helper lives within the versioned framework directory, so simply
// go up to find the main dylib.
const char rel_path[] = "../../../../" PRODUCT_FULLNAME_STRING " Framework";
#else
const char rel_path[] = "../Frameworks/" PRODUCT_FULLNAME_STRING
" Framework.framework/Versions/" CHROME_VERSION_STRING
"/" PRODUCT_FULLNAME_STRING " Framework";
#endif // defined(HELPER_EXECUTABLE)
// Slice off the last part of the main executable path, and append the
// version framework information.
const char* parent_dir = dirname(exec_path.get());
if (!parent_dir) {
FatalError("dirname %s: %s.", exec_path.get(), strerror(errno));
}
const size_t parent_dir_len = strlen(parent_dir);
const size_t rel_path_len = strlen(rel_path);
// 2 accounts for a trailing NUL byte and the '/' in the middle of the paths.
const size_t framework_path_size = parent_dir_len + rel_path_len + 2;
std::unique_ptr<char[]> framework_path(new char[framework_path_size]);
snprintf(framework_path.get(), framework_path_size, "%s/%s", parent_dir,
rel_path);
void* library =
dlopen(framework_path.get(), RTLD_LAZY | RTLD_LOCAL | RTLD_FIRST);
if (!library) {
FatalError("dlopen %s: %s.", framework_path.get(), dlerror());
}
const ChromeMainPtr chrome_main =
reinterpret_cast<ChromeMainPtr>(dlsym(library, "ChromeMain"));
if (!chrome_main) {
FatalError("dlsym ChromeMain: %s.", dlerror());
}
rv = chrome_main(argc, argv);
// exit, don't return from main, to avoid the apparent removal of main from
// stack backtraces under tail call optimization.
exit(rv);
}