File: chrome_exe_main_mac.cc

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 (217 lines) | stat: -rw-r--r-- 9,552 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
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
214
215
216
217
// 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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/390223051): Remove C-library calls to fix the errors.
#pragma allow_unsafe_libc_calls
#endif

// 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_apple.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 288kB (since 123.0.6312.10).
//
// 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 56kB 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 arm64 slice will be 16kB-aligned, so as long as the signed x86_64 slice
// ends anywhere in the offset range (272kB, 288kB], the desired alignment will
// be preserved. The x86_64 slice begins at offset 16kB (the fat header precedes
// it, and it is also 16kB-aligned), so the signed x86_64 slice’s size must be
// in the range (256kB, 272kB] in order for the slice’s end to be in the
// required range of offsets. To allow for small amounts of growth and
// shrinkage, the signed x86_64 slice’s size should target the middle of this
// range, or 264kB.
//
// At build time, the signed slice’s size is not known. Although subject to
// change, recent (2025-05, 138.0.7160) code signatures for official builds of
// the correct size introduce 22656 extra bytes beyond the size of the unsigned
// slice. With this signature length in mind, the size target for the unsigned
// x86_64 slice is 264kB - 22656 = 247680.
//
// With an unpadded unsigned size of 27024, (247680 - 27024) = 220656 bytes of
// padding are desirable. The padding can only be introduced with 4kB precision,
// so 216kB of padding is introduced.
//
// If the main executable has a significant change in size, this will need to be
// revised.
//
// If you’re here because of an InvalidAppGeometryException (checked at code
// signing time), recalculate the required padding for the x86_64 slice: take
// the reported signed x86_64 slice’s size reported by lipo -detailed_info and
// subtract 264k (270336) from it. If positive, remove padding in 4kB
// increments, and if negative, add padding in 4kB increments. The objective is
// to arrive at a signed x86_64 slice whose size is as close to 264kB as
// possible.
//
// (Each 4kB page added or removed here will result in slightly more than 4kB
// added or removed from the signed slice: it’s actually 4kB plus 32 bytes, 4128
// bytes total, accounting for both the padding and the additional SHA-256 hash
// incorporated into the code signature. The difference is <1% and can be
// ignored in most cases.)
__attribute__((used)) const char kGrossPaddingForCrbug1300598[216 * 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);
}