// 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_apple.h"
#include "base/compiler_specific.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "chrome/app/llvm_profile_util.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 {

#if !defined(HELPER_EXECUTABLE) && defined(OFFICIAL_BUILD) && \
    BUILDFLAG(GOOGLE_CHROME_BRANDING) && defined(ARCH_CPU_X86_64)
// This is for https://crbug.com/40216333, and more generally,
// https://crbug.com/40215211 (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 (UNSAFE_TODO(vsnprintf(message, sizeof(message), format, valist)) >= 0) {
    UNSAFE_TODO(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();

#if defined(RENDERER_HELPER_EXECUTABLE)
  SetLLVMProfileProcessType(ProfileProcessType::kRenderer);
#endif

  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.");
    }
  }
#endif

#if defined(RENDERER_HELPER_EXECUTABLE)
  static constexpr char entry_symbol[] = "ChromeRendererMain";
  static constexpr char rel_path[] = "../../../../Libraries/librenderer.dylib";
#elif defined(HELPER_EXECUTABLE)  // defined(HELPER_EXECUTABLE)
  // The helper lives within the versioned framework directory, so simply
  // go up to find the main dylib.
  static constexpr char entry_symbol[] = "ChromeMain";
  static constexpr char rel_path[] =
      "../../../../" PRODUCT_FULLNAME_STRING " Framework";
#else                             // defined(HELPER_EXECUTABLE)
  static constexpr char entry_symbol[] = "ChromeMain";
  static constexpr char rel_path[] =
      "../Frameworks/" PRODUCT_FULLNAME_STRING
      " Framework.framework/Versions/" CHROME_VERSION_STRING
      "/" PRODUCT_FULLNAME_STRING " Framework";
#endif                            // defined(RENDERER_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]);
  UNSAFE_TODO(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());
  }

  using ChromeMainPtr = int (*)(int, const char**);
  const ChromeMainPtr chrome_main =
      reinterpret_cast<ChromeMainPtr>(dlsym(library, entry_symbol));
  if (!chrome_main) {
    FatalError("dlsym %s: %s.", entry_symbol, dlerror());
  }
  rv = chrome_main(argc, const_cast<const char**>(argv));

  // exit, don't return from main, to avoid the apparent removal of main from
  // stack backtraces under tail call optimization.
  exit(rv);
}
