0

mac: Introduce libaperitif.dylib

Chrome currently has several .app executables packaged within its bundle
and the the Framework. Each one of these executables statically links
libc++, and in the helpers, a //sandbox target.

Apéritif moves the C++ components of early app initialization into a
shared library, reducing the size of each of the executables. The
executable main file transitions back to being a plain C file rather
than C++ to avoid linking in libc++.

In order to support linking the library to multiple images at different
bundle depths, the linker_driver.py gains the ability to run
install_name_tool as part of the link step.

Disk size changes for an x86_64, official, branded, stripped build:

 17168 out/official/aperitif/Google Chrome.app/Contents/MacOS/Google Chrome
214512 out/official/pristine/Google Chrome.app/Contents/MacOS/Google Chrome

 17240 out/official/aperitif/Google Chrome.app/Contents/Frameworks/Google Chrome Framework.framework/Helpers/Google Chrome Helper (Alerts).app/Contents/MacOS/Google Chrome Helper (Alerts)
 17240 out/official/aperitif/Google Chrome.app/Contents/Frameworks/Google Chrome Framework.framework/Helpers/Google Chrome Helper (GPU).app/Contents/MacOS/Google Chrome Helper (GPU)
 17240 out/official/aperitif/Google Chrome.app/Contents/Frameworks/Google Chrome Framework.framework/Helpers/Google Chrome Helper (Plugin).app/Contents/MacOS/Google Chrome Helper (Plugin)
 17240 out/official/aperitif/Google Chrome.app/Contents/Frameworks/Google Chrome Framework.framework/Helpers/Google Chrome Helper (Renderer).app/Contents/MacOS/Google Chrome Helper (Renderer)
 17240 out/official/aperitif/Google Chrome.app/Contents/Frameworks/Google Chrome Framework.framework/Helpers/Google Chrome Helper.app/Contents/MacOS/Google Chrome Helper
264736 out/official/pristine/Google Chrome.app/Contents/Frameworks/Google Chrome Framework.framework/Helpers/Google Chrome Helper (Alerts).app/Contents/MacOS/Google Chrome Helper (Alerts)
264736 out/official/pristine/Google Chrome.app/Contents/Frameworks/Google Chrome Framework.framework/Helpers/Google Chrome Helper (GPU).app/Contents/MacOS/Google Chrome Helper (GPU)
264736 out/official/pristine/Google Chrome.app/Contents/Frameworks/Google Chrome Framework.framework/Helpers/Google Chrome Helper (Plugin).app/Contents/MacOS/Google Chrome Helper (Plugin)
264736 out/official/pristine/Google Chrome.app/Contents/Frameworks/Google Chrome Framework.framework/Helpers/Google Chrome Helper (Renderer).app/Contents/MacOS/Google Chrome Helper (Renderer)
264736 out/official/pristine/Google Chrome.app/Contents/Frameworks/Google Chrome Framework.framework/Helpers/Google Chrome Helper.app/Contents/MacOS/Google Chrome Helper

338400 out/official/aperitif/Google Chrome.app/Contents/Frameworks/Google Chrome Framework.framework/Libraries/libaperitif.dylib
0 (NA) out/official/pristine/Google Chrome.app/Contents/Frameworks/Google Chrome Framework.framework/Libraries/libaperitif.dylib

179263608 out/official/aperitif/Google Chrome.app/Contents/Frameworks/Google Chrome Framework.framework/Versions/Current/Google Chrome Framework
179263608 out/official/pristine/Google Chrome.app/Contents/Frameworks/Google Chrome Framework.framework/Versions/Current/Google Chrome Framework

As reported by `du -k`:
261464	out/official/aperitif/Google Chrome.app
262524	out/official/pristine/Google Chrome.app

As reported by Finder Get Info:
267,309,919 bytes (267.7 MB on disk)  out/official/aperitif
268,406,343 bytes (268.8 MB on disk)  out/official/pristine

Bug: 1255223
Change-Id: I4e3c0fa542f2f7f1eae5df9b2c71d6840bc4a30e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3430220
Reviewed-by: Avi Drissman <avi@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
Reviewed-by: Andrew Grieve <agrieve@chromium.org>
Commit-Queue: Robert Sesek <rsesek@chromium.org>
Cr-Commit-Position: refs/heads/main@{#978773}
This commit is contained in:
Robert Sesek
2022-03-08 18:24:31 +00:00
committed by Chromium LUCI CQ
parent 90f5399806
commit 31576eb814
18 changed files with 465 additions and 242 deletions

@ -2343,6 +2343,7 @@ def CheckSpamLogging(input_api, output_api):
r"notification_event_dispatcher_impl\.cc$",
r"^content[\\/]common[\\/]gpu[\\/]client[\\/]"
r"gl_helper_benchmark\.cc$",
r"^content[\\/]app[\\/]aperitif_mac\.cc$",
r"^courgette[\\/]courgette_minimal_tool\.cc$",
r"^courgette[\\/]courgette_tool\.cc$",
r"^extensions[\\/]renderer[\\/]logging_native_handler\.cc$",

@ -28,6 +28,15 @@ LINKER_DRIVER_ARG_PREFIX = '-Wcrl,'
# removal of the special driver arguments, described below). Then the driver
# performs additional actions, based on these arguments:
#
# -Wcrl,installnametoolpath,<install_name_tool_path>
# Sets the path to the `install_name_tool` to run with
# -Wcrl,installnametool, in which case `xcrun` is not used to invoke it.
#
# -Wcrl,installnametool,<arguments,...>
# After invoking the linker, this will run install_name_tool on the linker's
# output. |arguments| are comma-separated arguments to be passed to the
# install_name_tool command.
#
# -Wcrl,dsym,<dsym_path_prefix>
# After invoking the linker, this will run `dsymutil` on the linker's
# output, producing a dSYM bundle, stored at dsym_path_prefix. As an
@ -69,6 +78,8 @@ class LinkerDriver(object):
# The first item in the tuple is the argument's -Wcrl,<sub_argument>
# and the second is the function to invoke.
self._actions = [
('installnametoolpath,', self.set_install_name_tool_path),
('installnametool,', self.run_install_name_tool),
('dsymutilpath,', self.set_dsymutil_path),
('dsym,', self.run_dsymutil),
('unstripped,', self.run_save_unstripped),
@ -77,6 +88,7 @@ class LinkerDriver(object):
]
# Linker driver actions can modify the these values.
self._install_name_tool_cmd = ['xcrun', 'install_name_tool']
self._dsymutil_cmd = ['xcrun', 'dsymutil']
self._strip_cmd = ['xcrun', 'strip']
@ -164,6 +176,40 @@ class LinkerDriver(object):
raise ValueError('Unknown linker driver argument: %s' % (arg, ))
def set_install_name_tool_path(self, install_name_tool_path):
"""Linker driver action for -Wcrl,installnametoolpath,<path>.
Sets the invocation command for install_name_tool, which allows the
caller to specify an alternate path. This action is always
processed before the run_install_name_tool action.
Args:
install_name_tool_path: string, The path to the install_name_tool
binary to run
Returns:
No output - this step is run purely for its side-effect.
"""
self._install_name_tool_cmd = [install_name_tool_path]
return []
def run_install_name_tool(self, args_string):
"""Linker driver action for -Wcrl,installnametool,<args>. Invokes
install_name_tool on the linker's output.
Args:
args_string: string, Comma-separated arguments for
`install_name_tool`.
Returns:
No output - this step is run purely for its side-effect.
"""
command = list(self._install_name_tool_cmd)
command.extend(args_string.split(','))
command.append(self._get_linker_output())
subprocess.check_call(command)
return []
def run_dsymutil(self, dsym_path_prefix):
"""Linker driver action for -Wcrl,dsym,<dsym-path-prefix>. Invokes
dsymutil on the linker's output and produces a dsym file at |dsym_file|

@ -170,7 +170,8 @@ template("apple_toolchain") {
# Specify an explicit path for the strip binary.
_strippath = invoker.bin_path + "strip"
linker_driver += " -Wcrl,strippath," + _strippath
_installnametoolpath = invoker.bin_path + "install_name_tool"
linker_driver += " -Wcrl,strippath,${_strippath} -Wcrl,installnametoolpath,${_installnametoolpath}"
# On iOS, the final applications are assembled using lipo (to support fat
# builds). The correct flags are passed to the linker_driver.py script

@ -467,18 +467,27 @@ if (is_win) {
"CHROMIUM_CREATOR=$chrome_mac_creator_code",
]
sources = [ "app/chrome_exe_main_mac.cc" ]
sources = [ "app/chrome_exe_main_mac.c" ]
extra_configs = [ "//build/config/compiler:wexit_time_destructors" ]
# No libc++ needed, theres only C code in this module.
no_default_deps = true
deps = [
":chrome_app_strings_bundle_data",
":chrome_resources",
":chrome_versioned_bundle_data",
"//base/allocator:early_zone_registration_mac",
"//build:branding_buildflags",
"//chrome/common:buildflags",
"//chrome/common:version_header",
"//content/public/app:aperitif",
]
ldflags = [
# Needed for lld: https://github.com/llvm/llvm-project/issues/53550.
"-Wl,-headerpad_max_install_names",
"-Wcrl,installnametool,-change,libaperitif.dylib,@executable_path/../Frameworks/$chrome_framework_name.framework/Versions/$chrome_version_full/Libraries/libaperitif.dylib",
]
if (enable_stripping) {
@ -486,8 +495,8 @@ if (is_win) {
# file. All other global symbols will be marked as private. The default
# //build/config/mac:strip_all config will then remove the remaining
# local and debug symbols.
ldflags = [ "-Wl,-exported_symbols_list," +
rebase_path("app/app.exports", root_build_dir) ]
ldflags += [ "-Wl,-exported_symbols_list," +
rebase_path("app/app.exports", root_build_dir) ]
}
if (is_component_build) {
@ -495,7 +504,7 @@ if (is_win) {
# executable because dlopen() and loading all the dependent dylibs
# is time-consuming, see https://crbug.com/1197495.
deps += [ ":chrome_framework+link" ]
ldflags = [ "-Wl,-rpath,@executable_path/../Frameworks" ]
ldflags += [ "-Wl,-rpath,@executable_path/../Frameworks" ]
# The Framework is packaged inside the .app bundle. But when using the
# component build, all the dependent shared libraries of :chrome_dll are
@ -511,6 +520,39 @@ if (is_win) {
}
}
if (verify_dynamic_libraries) {
action("verify_libraries_aperitif") {
script = "//chrome/tools/build/mac/verify_dynamic_libraries.py"
inputs = [ "$root_out_dir/libaperitif.dylib" ]
outputs = [ "$target_out_dir/run_$target_name.stamp" ]
args = [
"--stamp",
rebase_path(outputs[0], root_out_dir),
"-B",
objdump_path,
"--image",
rebase_path(inputs[0], root_out_dir),
# Do not --allow more libraries here without consulting with the
# security team (security-dev@chromium.org).
"--allow",
"/usr/lib/libsandbox.1.dylib",
"--allow",
"/usr/lib/libSystem.B.dylib",
]
deps = [ "//content/public/app:aperitif" ]
}
}
bundle_data("aperitif_library") {
sources = [ "$root_out_dir/libaperitif.dylib" ]
outputs = [ "{{bundle_contents_dir}}/Libraries/{{source_file_part}}" ]
public_deps = [ "//content/public/app:aperitif" ]
if (verify_dynamic_libraries) {
public_deps += [ ":verify_libraries_aperitif" ]
}
}
if (verify_dynamic_libraries) {
action("verify_libraries_chrome_app") {
script = "//chrome/tools/build/mac/verify_dynamic_libraries.py"
@ -525,6 +567,8 @@ if (is_win) {
rebase_path(inputs[0], root_out_dir),
"--allow",
"/usr/lib/libSystem.B.dylib",
"--allow",
"@executable_path/../Frameworks/$chrome_framework_name.framework/Versions/$chrome_version_full/Libraries/libaperitif.dylib",
]
deps = [ ":chrome_app" ]
}
@ -709,24 +753,30 @@ if (is_win) {
"CHROMIUM_HELPER_BUNDLE_ID_SUFFIX=${invoker.helper_bundle_id_suffix}",
]
sources = [ "app/chrome_exe_main_mac.cc" ]
sources = [ "app/chrome_exe_main_mac.c" ]
extra_configs = [ "//build/config/compiler:wexit_time_destructors" ]
defines = [ "HELPER_EXECUTABLE" ]
# No libc++ needed, theres only C code in this module.
no_default_deps = true
deps = [
"//base/allocator:early_zone_registration_mac",
"//build:branding_buildflags",
"//chrome/common:version_header",
"//sandbox/mac:seatbelt",
"//content/public/app:aperitif",
]
if (defined(invoker.deps)) {
deps += invoker.deps
}
ldflags = []
ldflags = [
# Needed for lld: https://github.com/llvm/llvm-project/issues/53550.
"-Wl,-headerpad_max_install_names",
"-Wcrl,installnametool,-change,libaperitif.dylib,@executable_path/../../../../Libraries/libaperitif.dylib",
]
if (is_component_build) {
# In a component build, the framework is directly linked to the
@ -841,9 +891,9 @@ if (is_win) {
# Do not --allow more libraries here without consulting with the
# security team (security-dev@chromium.org).
"--allow",
"/usr/lib/libsandbox.1.dylib",
"--allow",
"/usr/lib/libSystem.B.dylib",
"--allow",
"@executable_path/../../../../Libraries/libaperitif.dylib",
]
deps = [ ":chrome_helper_app_${_helper_target}" ]
}
@ -1176,6 +1226,7 @@ if (is_win) {
bundle_deps = [
":angle_library",
":aperitif_library",
":chrome_framework_helpers",
":chrome_framework_plugins",
":chrome_framework_resources",
@ -1199,9 +1250,12 @@ if (is_win) {
]
if (!is_component_build) {
# Specify a sensible install_name for static builds. The library is
# dlopen()ed so this is not used to resolve the module.
ldflags += [ "-Wl,-install_name,@executable_path/../Frameworks/$chrome_framework_name.framework/Versions/$chrome_version_full/$chrome_framework_name" ]
ldflags += [
# Specify a sensible install_name for static builds. The library is
# dlopen()ed so this is not used to resolve the module.
"-Wl,-install_name,@executable_path/../Frameworks/$chrome_framework_name.framework/Versions/$chrome_version_full/$chrome_framework_name",
"-Wcrl,installnametool,-change,libaperitif.dylib,@loader_path/Libraries/libaperitif.dylib",
]
} else {
# In the component build, both the :chrome_app and various
# :chrome_helper* targets directly link to the Framework target. Use
@ -1213,7 +1267,10 @@ if (is_win) {
"-Wl,-reexport_library,libchrome_dll.dylib",
]
data_deps = [ ":chrome_dll" ]
data_deps = [
":chrome_dll",
"//content/public/app:aperitif",
]
}
}
@ -1269,6 +1326,7 @@ if (is_win) {
"$root_out_dir/chrome_crashpad_handler",
"$root_out_dir/libEGL.dylib",
"$root_out_dir/libGLESv2.dylib",
"$root_out_dir/libaperitif.dylib",
"$root_out_dir/libswiftshader_libEGL.dylib",
"$root_out_dir/libswiftshader_libGLESv2.dylib",
"$root_out_dir/libvk_swiftshader.dylib",
@ -1305,6 +1363,7 @@ if (is_win) {
":chrome_app",
":chrome_framework",
"//components/crash/core/app:chrome_crashpad_handler",
"//content/public/app:aperitif",
"//third_party/angle:libEGL",
"//third_party/angle:libGLESv2",
"//third_party/breakpad:dump_syms",
@ -1332,6 +1391,7 @@ if (is_win) {
"$root_out_dir/$chrome_framework_name.dSYM",
"$root_out_dir/$chrome_product_full_name.dSYM",
"$root_out_dir/chrome_crashpad_handler.dSYM",
"$root_out_dir/libaperitif.dylib.dSYM",
"$root_out_dir/libEGL.dylib.dSYM",
"$root_out_dir/libGLESv2.dylib.dSYM",
"$root_out_dir/libswiftshader_libEGL.dylib.dSYM",
@ -1346,6 +1406,7 @@ if (is_win) {
":chrome_app",
":chrome_framework",
"//components/crash/core/app:chrome_crashpad_handler",
"//content/public/app:aperitif",
"//third_party/angle:libEGL",
"//third_party/angle:libGLESv2",
"//third_party/swiftshader/src/OpenGL/libEGL:swiftshader_libEGL",

@ -55,8 +55,9 @@ per-file *.plist=file://chrome/browser/ui/cocoa/OWNERS
per-file chrome_crash_reporter_client*=rsesek@chromium.org
per-file chrome_exe_main_mac.cc=kerrnel@chromium.org
per-file chrome_exe_main_mac.cc=mark@chromium.org
per-file chrome_exe_main_mac.c=kerrnel@chromium.org
per-file chrome_exe_main_mac.c=mark@chromium.org
per-file chrome_exe_main_mac.c=rsesek@chromium.org
per-file main_dll_loader_win.cc=brucedawson@chromium.org
per-file main_dll_loader_win.cc=wfh@chromium.org

@ -8,35 +8,17 @@
#include <dlfcn.h>
#include <errno.h>
#include <libgen.h>
#include <limits.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 {
#include "content/public/app/aperitif_mac.h"
typedef int (*ChromeMainPtr)(int, char**);
@ -125,85 +107,57 @@ typedef int (*ChromeMainPtr)(int, char**);
// 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] = {};
static __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();
AperitifInitializePartitionAlloc();
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);
char exec_path[PATH_MAX];
uint32_t exec_path_size = sizeof(exec_path);
int rv = _NSGetExecutablePath(exec_path, &exec_path_size);
if (rv != 0) {
FatalError("_NSGetExecutablePath: get path failed.");
AperitifFatalError("_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.");
}
}
// Start the sandbox before loading the framework.
AperitifInitializeSandbox(exec_path, argc, (const char**)argv);
// 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";
static 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";
static 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());
const char* parent_dir = dirname(exec_path);
if (!parent_dir) {
FatalError("dirname %s: %s.", exec_path.get(), strerror(errno));
AperitifFatalError("dirname %s: %s.", exec_path, 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);
char framework_path[PATH_MAX];
rv = snprintf(framework_path, sizeof(framework_path), "%s/%s", parent_dir,
rel_path);
if (rv < 0 || (size_t)rv >= sizeof(framework_path)) {
AperitifFatalError("snprintf: %d.", rv);
}
void* library =
dlopen(framework_path.get(), RTLD_LAZY | RTLD_LOCAL | RTLD_FIRST);
void* library = dlopen(framework_path, RTLD_LAZY | RTLD_LOCAL | RTLD_FIRST);
if (!library) {
FatalError("dlopen %s: %s.", framework_path.get(), dlerror());
AperitifFatalError("dlopen %s: %s.", framework_path, dlerror());
}
const ChromeMainPtr chrome_main =
reinterpret_cast<ChromeMainPtr>(dlsym(library, "ChromeMain"));
const ChromeMainPtr chrome_main = dlsym(library, "ChromeMain");
if (!chrome_main) {
FatalError("dlsym ChromeMain: %s.", dlerror());
AperitifFatalError("dlsym ChromeMain: %s.", dlerror());
}
rv = chrome_main(argc, argv);

@ -118,6 +118,7 @@ def get_parts(config):
}
dylibs = [
'libaperitif.dylib',
'libEGL.dylib',
'libGLESv2.dylib',
'libswiftshader_libEGL.dylib',

@ -27,6 +27,13 @@ def verify_image_libraries(image_path, allowed_libraries, binary_path):
links against allowed libraries, and otherwise returns False with an error
printed.
"""
# If |image_path| is a dynamic library, allow the LC_DYLIB_ID implicitly.
output = subprocess.check_output(
[binary_path + 'llvm-objdump', '--macho', '--dylib-id', image_path])
dylib_id = output.decode('utf8').split('\n')[1]
if dylib_id:
allowed_libraries.append(dylib_id)
output = subprocess.check_output(
[binary_path + 'llvm-objdump', '--macho', '--dylibs-used', image_path])
output = output.decode('utf8').strip()

@ -133,6 +133,17 @@ source_set("app") {
}
}
if (is_mac) {
source_set("aperitif") {
sources = [ "aperitif_mac.cc" ]
deps = [
"//base/allocator:early_zone_registration_mac",
"//sandbox/mac:seatbelt",
]
visibility = [ "//content/public/app:aperitif" ]
}
}
# See comment at the top of //content/BUILD.gn for how this works.
group("for_content_tests") {
visibility = [ "//content/test/*" ]

@ -0,0 +1,63 @@
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Apéritif is by necessity linked to only a minimal number of libraries.
// Code that executes in this context has the capability of compromising the
// integrity of the sandbox by acquiring resources that would remain available
// to an unprivileged process. Consult with security-dev@chromium.org before
// adding new dependencies to Aperitif.
#include "content/public/app/aperitif_mac.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/cdefs.h>
#include "base/allocator/early_zone_registration_mac.h"
#include "sandbox/mac/seatbelt_exec.h"
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 module cannot depend on Crashpad
// directly.
void abort_report_np(const char* fmt, ...) __abortlike __printflike(1, 2);
void AperitifFatalError(const char* format, ...) {
va_list valist;
va_start(valist, format);
char message[4096];
int rv = vsnprintf(message, sizeof(message), format, valist);
va_end(valist);
if (rv >= 0) {
fprintf(stderr, "aperitif: %s\n", message);
fflush(stderr);
abort_report_np("aperitif: %s", message);
}
abort();
}
void AperitifInitializePartitionAlloc() {
partition_alloc::EarlyMallocZoneRegistration();
}
void AperitifInitializeSandbox(const char* executable_path,
int argc,
const char* const argv[]) {
sandbox::SeatbeltExecServer::CreateFromArgumentsResult seatbelt =
sandbox::SeatbeltExecServer::CreateFromArguments(executable_path, argc,
argv);
if (seatbelt.sandbox_required) {
if (!seatbelt.server) {
AperitifFatalError("Failed to create seatbelt sandbox server.");
}
if (!seatbelt.server->InitializeSandbox()) {
AperitifFatalError("Failed to initialize sandbox.");
}
}
}
} // extern "C"

@ -95,3 +95,33 @@ if (is_component_build) {
]
}
}
if (is_mac) {
import("//build/util/version.gni")
shared_library("aperitif") {
sources = [ "aperitif_mac.h" ]
public_deps = [ "//content/app:aperitif" ]
ldflags = [
"-Wl,-install_name,libaperitif.dylib",
"-compatibility_version",
chrome_dylib_version,
"-current_version",
chrome_dylib_version,
]
if (is_component_build) {
# Typically packaged at Content Shell.app/Contents/Frameworks/Content
# Shell Framework.framework/Versions/C/Libraries/libaperitif.dylib
# so set rpath up to the root out directory.
ldflags += [ "-Wl,-rpath,@loader_path/../../../../../../.." ]
}
allow_circular_includes_from = [
# This target is a pair with the non-public version. They always go
# together and include headers from each other.
"//content/app:aperitif",
]
}
}

@ -0,0 +1,50 @@
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_PUBLIC_APP_APERITIF_MAC_H_
#define CONTENT_PUBLIC_APP_APERITIF_MAC_H_
// The macOS //content Apéritif dynamic library. The main and helper
// executables, compiled from *main_mac.c are plain C files that do not
// directly link to any C++ targets nor system libraries. Not linking to system
// libraries is important for sandboxing (see
// //sandbox/mac/seatbelt_sandbox_design.md), and not including C++ keeps the
// executable size to a minimum, because the project uses its own version of
// libc++. Minimizing the size of the executables is important because
// //content embedders need to distribute several duplicate helper executables
// that each have distinct codesigning entitlements (see
// //content/public/app/mac_helpers.gni).
//
// Apéritif is a dynamic library that contains C++ functionality that needs to
// run in the executable prior to loading the main Framework. This primary
// purpose is to engage the sandbox in helper executables prior to any system
// library static initializers running.
#define CONTENT_APERITIF_EXPORT __attribute__((visibility("default")))
#ifdef __cplusplus
extern "C" {
#endif
// Helper function to record an error message and abort.
void CONTENT_APERITIF_EXPORT __attribute__((noreturn, format(printf, 1, 2)))
AperitifFatalError(const char* format, ...);
// Performs early initialization of the Mac allocator zone.
void CONTENT_APERITIF_EXPORT AperitifInitializePartitionAlloc();
// Engages the sandbox if the command line arguments specify that the process
// is to be sandboxed.
void CONTENT_APERITIF_EXPORT
AperitifInitializeSandbox(const char* executable_path,
int argc,
const char* const argv[]);
#ifdef __cplusplus
} // extern "C"
#endif
#undef CONTENT_APERITIF_EXPORT
#endif // CONTENT_PUBLIC_APP_APERITIF_MAC_H_

@ -358,7 +358,8 @@ int LaunchTests(TestLauncherDelegate* launcher_delegate,
#elif BUILDFLAG(IS_MAC)
sandbox::SeatbeltExecServer::CreateFromArgumentsResult seatbelt =
sandbox::SeatbeltExecServer::CreateFromArguments(
command_line->GetProgram().value().c_str(), argc, argv);
command_line->GetProgram().value().c_str(), argc,
const_cast<const char**>(argv));
if (seatbelt.sandbox_required) {
CHECK(seatbelt.server->InitializeSandbox());
}

@ -480,39 +480,9 @@ if (is_android) {
deps = [ "//content/shell/android:content_shell_apk" ]
}
} else if (is_mac) {
tweak_info_plist("content_shell_plist") {
group("content_shell") {
testonly = true
info_plist = "app/app-Info.plist"
args = [
"--scm=1",
"--version",
content_shell_version,
]
}
mac_app_bundle("content_shell") {
testonly = true
output_name = content_shell_product_name
sources = [ "app/shell_main_mac.cc" ]
defines = [ "SHELL_PRODUCT_NAME=\"$content_shell_product_name\"" ]
# Must have minimal dependencies. In particular cannot depend on //base.
# Depending on //base leads to loading //base twice (once in the exe, once
# in the main framework).
deps = [
":content_shell_framework_bundle_data",
":content_shell_resources_bundle_data",
# Despite its path, this does not add a //base dependency, see comments in
# the .cc file.
"//base/allocator:early_zone_registration_mac",
"//sandbox/mac:seatbelt",
]
info_plist_target = ":content_shell_plist"
data_deps = [ ":content_shell_app" ]
if (is_component_build) {
ldflags = [ "-Wl,-rpath,@executable_path/../Frameworks" ]
}
deps = [ ":content_shell_mac_app" ]
}
} else {
executable("content_shell") {
@ -589,6 +559,51 @@ if (is_android) {
}
if (is_mac) {
content_shell_framework_name = "$content_shell_product_name Framework"
content_shell_helper_name = "$content_shell_product_name Helper"
tweak_info_plist("content_shell_plist") {
testonly = true
info_plist = "app/app-Info.plist"
args = [
"--scm=1",
"--version",
content_shell_version,
]
}
mac_app_bundle("content_shell_mac_app") {
testonly = true
output_name = content_shell_product_name
sources = [ "app/shell_main_mac.c" ]
defines = [ "SHELL_PRODUCT_NAME=\"$content_shell_product_name\"" ]
# No libc++ needed, theres only C code in this module.
no_default_deps = true
# Must have minimal dependencies. In particular cannot depend on //base.
# Depending on //base leads to loading //base twice (once in the exe, once
# in the main framework).
deps = [
":content_shell_framework_bundle_data",
":content_shell_resources_bundle_data",
"//content/public/app:aperitif",
]
info_plist_target = ":content_shell_plist"
data_deps = [ ":content_shell_app" ]
ldflags = [
# Needed for lld: https://github.com/llvm/llvm-project/issues/53550.
"-Wl,-headerpad_max_install_names",
"-Wcrl,installnametool,-change,libaperitif.dylib,@executable_path/../Frameworks/$content_shell_framework_name.framework/Libraries/libaperitif.dylib",
]
if (is_component_build) {
ldflags += [ "-Wl,-rpath,@executable_path/../Frameworks" ]
data_deps += [ "//content/public/app:aperitif" ]
}
}
bundle_data("content_shell_framework_resources") {
testonly = true
sources = [ "$root_out_dir/content_shell.pak" ]
@ -629,9 +644,6 @@ if (is_mac) {
}
}
content_shell_framework_name = "$content_shell_product_name Framework"
content_shell_helper_name = "$content_shell_product_name Helper"
bundle_data("content_shell_framework_helpers") {
testonly = true
sources = [ "$root_out_dir/chrome_crashpad_handler" ]
@ -681,11 +693,13 @@ if (is_mac) {
":content_shell_angle_library",
":content_shell_app",
"//content/public/app",
"//content/public/app:aperitif",
"//content/public/common",
"//third_party/icu:icudata",
]
bundle_deps = [
":content_shell_aperitif_library",
":content_shell_framework_helpers",
":content_shell_framework_resources",
":content_shell_swiftshader_library",
@ -695,10 +709,12 @@ if (is_mac) {
deps += [ ":content_shell_framework_plugins" ]
}
ldflags = [ "-Wcrl,installnametool,-change,libaperitif.dylib,@loader_path/Libraries/libaperitif.dylib" ]
if (!is_component_build) {
# Specify a sensible install_name for static builds. The library is
# dlopen()ed so this is not used to resolve the module.
ldflags = [ "-Wl,-install_name,@executable_path/../Frameworks/$output_name.framework/$output_name" ]
ldflags += [ "-Wl,-install_name,@executable_path/../Frameworks/$output_name.framework/$output_name" ]
} else {
# Both the main :content_shell and :content_shell_helper_app executables
# need to link the framework. Because they are at different directory
@ -706,7 +722,7 @@ if (is_mac) {
# install_name_tool on one of the executables. However install_name_tool
# only operates in-place, which is problematic to express in GN. Instead,
# use rpath-based loading.
ldflags =
ldflags +=
[ "-Wl,-install_name,@rpath/$output_name.framework/$output_name" ]
# Set up the rpath for the framework so that it can find dylibs in the
@ -740,7 +756,7 @@ if (is_mac) {
output_name = content_shell_helper_name + invoker.helper_name_suffix
sources = [ "app/shell_main_mac.cc" ]
sources = [ "app/shell_main_mac.c" ]
defines = [
"HELPER_EXECUTABLE",
"SHELL_PRODUCT_NAME=\"$content_shell_product_name\"",
@ -749,15 +765,21 @@ if (is_mac) {
"CONTENT_SHELL_HELPER_SUFFIX=${invoker.helper_name_suffix}",
"CONTENT_SHELL_HELPER_BUNDLE_ID_SUFFIX=${invoker.helper_bundle_id_suffix}",
]
deps = [
"//base/allocator:early_zone_registration_mac",
"//sandbox/mac:seatbelt",
]
deps = [ "//content/public/app:aperitif" ]
# No libc++ needed, theres only C code in this module.
no_default_deps = true
info_plist_target = ":content_shell_helper_plist"
ldflags = [
# Needed for lld: https://github.com/llvm/llvm-project/issues/53550.
"-Wl,-headerpad_max_install_names",
"-Wcrl,installnametool,-change,libaperitif.dylib,@executable_path/../../../../Libraries/libaperitif.dylib",
]
if (is_component_build) {
ldflags = [
ldflags += [
# The helper is in Content Shell.app/Contents/Frameworks/
# Content Shell Framework.framework/Versions/C/Helpers/
# Content Shell Helper.app/Contents/MacOS/
@ -844,6 +866,12 @@ if (is_mac) {
deps = [ ":content_shell_swiftshader_binaries" ]
}
}
bundle_data("content_shell_aperitif_library") {
sources = [ "$root_out_dir/libaperitif.dylib" ]
outputs = [ "{{bundle_contents_dir}}/Libraries/{{source_file_part}}" ]
public_deps = [ "//content/public/app:aperitif" ]
}
}
mojom("content_browsertests_mojom") {

@ -0,0 +1,68 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <dlfcn.h>
#include <errno.h>
#include <libgen.h>
#include <mach-o/dyld.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include "content/public/app/aperitif_mac.h"
typedef int (*ContentMainPtr)(int, char**);
int main(int argc, char* argv[]) {
AperitifInitializePartitionAlloc();
char exec_path[PATH_MAX];
uint32_t exec_path_size = sizeof(exec_path);
int rv = _NSGetExecutablePath(exec_path, &exec_path_size);
if (rv != 0) {
AperitifFatalError("_NSGetExecutablePath: get path failed.");
}
#if defined(HELPER_EXECUTABLE)
AperitifInitializeSandbox(exec_path, argc, (const char**)argv);
// The Helper app is in the versioned framework directory, so just go up to
// the version folder to locate the dylib.
static const char rel_path[] = "../../../../" SHELL_PRODUCT_NAME " Framework";
#else
static const char rel_path[] =
"../Frameworks/" SHELL_PRODUCT_NAME
" Framework.framework/" SHELL_PRODUCT_NAME " 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);
if (!parent_dir) {
AperitifFatalError("dirname %s: %s", exec_path, strerror(errno));
}
char framework_path[PATH_MAX];
rv = snprintf(framework_path, sizeof(framework_path), "%s/%s", parent_dir,
rel_path);
if (rv < 0 || (size_t)rv >= sizeof(framework_path)) {
AperitifFatalError("snprintf: %d", rv);
}
void* library = dlopen(framework_path, RTLD_LAZY | RTLD_LOCAL | RTLD_FIRST);
if (!library) {
AperitifFatalError("dlopen %s: %s", framework_path, dlerror());
}
const ContentMainPtr content_main = dlsym(library, "ContentMain");
if (!content_main) {
AperitifFatalError("dlsym ContentMain: %s", dlerror());
}
rv = content_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);
}

@ -1,102 +0,0 @@
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <dlfcn.h>
#include <errno.h>
#include <libgen.h>
#include <mach-o/dyld.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory>
#include "base/allocator/early_zone_registration_mac.h"
#if defined(HELPER_EXECUTABLE)
#include "sandbox/mac/seatbelt_exec.h" // nogncheck
#endif // defined(HELPER_EXECUTABLE)
namespace {
using ContentMainPtr = int (*)(int, char**);
} // namespace
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) {
fprintf(stderr, "_NSGetExecutablePath: get length failed\n");
abort();
}
std::unique_ptr<char[]> exec_path(new char[exec_path_size]);
rv = _NSGetExecutablePath(exec_path.get(), &exec_path_size);
if (rv != 0) {
fprintf(stderr, "_NSGetExecutablePath: get path failed\n");
abort();
}
#if defined(HELPER_EXECUTABLE)
sandbox::SeatbeltExecServer::CreateFromArgumentsResult seatbelt =
sandbox::SeatbeltExecServer::CreateFromArguments(exec_path.get(), argc,
argv);
if (seatbelt.sandbox_required) {
if (!seatbelt.server) {
fprintf(stderr, "Failed to create seatbelt sandbox server.\n");
abort();
}
if (!seatbelt.server->InitializeSandbox()) {
fprintf(stderr, "Failed to initialize sandbox.\n");
abort();
}
}
// The Helper app is in the versioned framework directory, so just go up to
// the version folder to locate the dylib.
const char rel_path[] = "../../../../" SHELL_PRODUCT_NAME " Framework";
#else
const char rel_path[] =
"../Frameworks/" SHELL_PRODUCT_NAME
" Framework.framework/" SHELL_PRODUCT_NAME " 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) {
fprintf(stderr, "dirname %s: %s\n", exec_path.get(), strerror(errno));
abort();
}
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) {
fprintf(stderr, "dlopen %s: %s\n", framework_path.get(), dlerror());
abort();
}
const ContentMainPtr content_main =
reinterpret_cast<ContentMainPtr>(dlsym(library, "ContentMain"));
if (!content_main) {
fprintf(stderr, "dlsym ContentMain: %s\n", dlerror());
abort();
}
rv = content_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);
}

@ -169,7 +169,7 @@ sandbox::SeatbeltExecServer::CreateFromArgumentsResult::
sandbox::SeatbeltExecServer::CreateFromArgumentsResult
SeatbeltExecServer::CreateFromArguments(const char* executable_path,
int argc,
char** argv) {
const char* const* argv) {
CreateFromArgumentsResult result;
int seatbelt_client_fd = -1;
for (int i = 1; i < argc; ++i) {

@ -97,8 +97,10 @@ class SEATBELT_EXPORT SeatbeltExecServer {
bool sandbox_required = false;
std::unique_ptr<SeatbeltExecServer> server;
};
static CreateFromArgumentsResult
CreateFromArguments(const char* executable_path, int argc, char** argv);
static CreateFromArgumentsResult CreateFromArguments(
const char* executable_path,
int argc,
const char* const* argv);
// Reads the policy from the client, applies the profile, and returns whether
// or not the operation succeeds.