diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 038b623d613..522c182d038 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -456,6 +456,8 @@ FILE: ../../../flutter/runtime/dart_vm_unittests.cc FILE: ../../../flutter/runtime/embedder_resources.cc FILE: ../../../flutter/runtime/embedder_resources.h FILE: ../../../flutter/runtime/fixtures/runtime_test.dart +FILE: ../../../flutter/runtime/ptrace_ios.cc +FILE: ../../../flutter/runtime/ptrace_ios.h FILE: ../../../flutter/runtime/runtime_controller.cc FILE: ../../../flutter/runtime/runtime_controller.h FILE: ../../../flutter/runtime/runtime_delegate.cc diff --git a/common/settings.h b/common/settings.h index 092970a6123..d39c2ea6ea3 100644 --- a/common/settings.h +++ b/common/settings.h @@ -88,6 +88,7 @@ struct Settings { std::vector dart_entrypoint_args; // Isolate settings + bool enable_checked_mode = false; bool start_paused = false; bool trace_skia = false; bool trace_startup = false; diff --git a/runtime/BUILD.gn b/runtime/BUILD.gn index 5cfd7d5af82..cb404cbee4c 100644 --- a/runtime/BUILD.gn +++ b/runtime/BUILD.gn @@ -58,6 +58,8 @@ source_set("runtime") { "dart_vm_lifecycle.h", "embedder_resources.cc", "embedder_resources.h", + "ptrace_ios.cc", + "ptrace_ios.h", "runtime_controller.cc", "runtime_controller.h", "runtime_delegate.cc", diff --git a/runtime/dart_vm.cc b/runtime/dart_vm.cc index 78d8cb42f9a..d68230e1721 100644 --- a/runtime/dart_vm.cc +++ b/runtime/dart_vm.cc @@ -24,6 +24,7 @@ #include "flutter/lib/ui/dart_ui.h" #include "flutter/runtime/dart_isolate.h" #include "flutter/runtime/dart_service_isolate.h" +#include "flutter/runtime/ptrace_ios.h" #include "flutter/runtime/start_up.h" #include "third_party/dart/runtime/include/bin/dart_io_api.h" #include "third_party/tonic/converter/dart_converter.h" @@ -307,11 +308,15 @@ DartVM::DartVM(std::shared_ptr vm_data, } #endif // !OS_FUCHSIA -#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG +#if (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG) +#if !OS_IOS || TARGET_OS_SIMULATOR // Debug mode uses the JIT, disable code page write protection to avoid // memory page protection changes before and after every compilation. PushBackAll(&args, kDartWriteProtectCodeArgs, fml::size(kDartWriteProtectCodeArgs)); +#else + EnsureDebuggedIOS(settings_); +#endif #endif if (enable_asserts) { diff --git a/runtime/ptrace_ios.cc b/runtime/ptrace_ios.cc new file mode 100644 index 00000000000..8d4dd2182fa --- /dev/null +++ b/runtime/ptrace_ios.cc @@ -0,0 +1,103 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// It is __imperative__ that the functions in this file are __not__ included in +// release or profile builds. +// +// They call into the "private" ptrace() API to ensure that the current process +// is being ptrace()-d. Only debug builds rely on ptrace(), and the ptrace() API +// is not allowed for use in the App Store, so we must exclude it from profile- +// and release-builds. +// +// When an app is launched from a host workstation (e.g. via Xcode or +// "ios-deploy"), the process is already ptrace()-d by debugserver. However, +// when an app is launched from the home screen, it is not, so for debug builds +// we initialize the ptrace() relationship via PT_TRACE_ME if necessary. +// +// Please see the following documents for more details: +// - go/decommissioning-dbc +// - go/decommissioning-dbc-engine +// - go/decommissioning-dbc-tools +#include "flutter/common/settings.h" +#include "flutter/fml/build_config.h" // For OS_IOS. + +#if OS_IOS && (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG) + +// These headers should only be needed in debug mode. +#include +#include + +#define PT_TRACE_ME 0 +#define PT_SIGEXC 12 +extern "C" int ptrace(int request, pid_t pid, caddr_t addr, int data); + +static bool DebuggedIOS(const flutter::Settings& vm_settings) { + // Only the Flutter CLI passes "--enable-checked-mode". Therefore, if the flag + // is present, we have been launched by "ios-deploy" via "debugserver". + // + // We choose this flag because it is always passed to launch debug builds. + if (vm_settings.enable_checked_mode) { + return true; + } + + // Use "sysctl()" to check if we're currently being debugged (e.g. by Xcode). + // We could also check "getppid() != 1" (launchd), but this is more direct. + const pid_t self = getpid(); + int mib[5] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, self, 0}; + + auto proc = std::make_unique(); + size_t proc_size = sizeof(struct kinfo_proc); + if (sysctl(mib, 4, proc.get(), &proc_size, nullptr, 0) < 0) { + FML_LOG(ERROR) << "Could not execute sysctl() to get current process info: " + << strerror(errno); + return false; + } + + return proc->kp_proc.p_flag & P_TRACED; +} + +void EnsureDebuggedIOS(const flutter::Settings& vm_settings) { + if (DebuggedIOS(vm_settings)) { + return; + } + + if (ptrace(PT_TRACE_ME, 0, nullptr, 0) == -1) { + FML_LOG(ERROR) << "Could not call ptrace(PT_TRACE_ME): " << strerror(errno); + // No use trying PT_SIGEXC -- it's only needed if PT_TRACE_ME succeeds. + return; + } + if (ptrace(PT_SIGEXC, 0, nullptr, 0) == -1) { + FML_LOG(ERROR) << "Could not call ptrace(PT_SIGEXC): " << strerror(errno); + } + + // The previous operation causes this process to not be reaped after it + // terminates (even if PT_SIGEXC fails). Issue a warning to the console every + // (approximiately) maxproc/10 leaks. See the links above for an explanation + // of this issue. + size_t maxproc = 0; + size_t maxproc_size = sizeof(size_t); + const int sysctl_result = + sysctlbyname("kern.maxproc", &maxproc, &maxproc_size, nullptr, 0); + if (sysctl_result < 0) { + FML_LOG(ERROR) + << "Could not execute sysctl() to determine process count limit: " + << strerror(errno); + } + + const char* warning = + "Launching a debug-mode app from the home screen may cause problems.\n" + "Please compile a profile-/release-build, launch your app via \"flutter " + "run\", or see https://github.com/flutter/flutter/wiki/" + "PID-leak-in-iOS-debug-builds-launched-from-home-screen for details."; + + if (vm_settings.verbose_logging // used for testing and also informative + || sysctl_result < 0 // could not determine maximum process count + || maxproc / 10 == 0 // avoid division (%) by 0 + || getpid() % (maxproc / 10) == 0) // warning every ~maxproc/10 leaks + { + FML_LOG(ERROR) << warning; + } +} + +#endif // OS_IOS && (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG) diff --git a/runtime/ptrace_ios.h b/runtime/ptrace_ios.h new file mode 100644 index 00000000000..92a17737a8f --- /dev/null +++ b/runtime/ptrace_ios.h @@ -0,0 +1,18 @@ +// Copyright 2013 The Flutter 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 FLUTTER_RUNTIME_PTRACE_IOS_H_ +#define FLUTTER_RUNTIME_PTRACE_IOS_H_ + +#include "flutter/common/settings.h" + +#if OS_IOS && (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG) + +// Ensure that the current process is or was ptrace()-d at some point in its +// life. Can only be used within debug builds for iOS. +void EnsureDebuggedIOS(const flutter::Settings& vm_settings); + +#endif + +#endif // FLUTTER_RUNTIME_PTRACE_IOS_H_ diff --git a/shell/common/switches.cc b/shell/common/switches.cc index 74e1292d681..e7d892414c5 100644 --- a/shell/common/switches.cc +++ b/shell/common/switches.cc @@ -223,6 +223,9 @@ Settings SettingsFromCommandLine(const fml::CommandLine& command_line) { settings.start_paused = command_line.HasOption(FlagForSwitch(Switch::StartPaused)); + settings.enable_checked_mode = + command_line.HasOption(FlagForSwitch(Switch::EnableCheckedMode)); + settings.enable_dart_profiling = command_line.HasOption(FlagForSwitch(Switch::EnableDartProfiling)); diff --git a/shell/common/switches.h b/shell/common/switches.h index 290d56d31d4..60a9392eeb4 100644 --- a/shell/common/switches.h +++ b/shell/common/switches.h @@ -115,6 +115,7 @@ DEF_SWITCH(DisableServiceAuthCodes, DEF_SWITCH(StartPaused, "start-paused", "Start the application paused in the Dart debugger.") +DEF_SWITCH(EnableCheckedMode, "enable-checked-mode", "Enable checked mode.") DEF_SWITCH(TraceStartup, "trace-startup", "Trace early application lifecycle. Automatically switches to an "