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);
}
|