diff --git a/browser/app/nsBrowserApp.cpp b/browser/app/nsBrowserApp.cpp --- a/browser/app/nsBrowserApp.cpp +++ b/browser/app/nsBrowserApp.cpp @@ -193,19 +193,31 @@ static bool HasFlag(int argc, char* argv[], const char* s) { } return false; } #endif constinit Bootstrap::UniquePtr gBootstrap; static int do_main(int argc, char* argv[], char* envp[]) { + // Allow profile downgrade for Zotero + setenv("MOZ_ALLOW_DOWNGRADE", "1", 1); + // Don't create dedicated profile (default-esr) + setenv("MOZ_LEGACY_PROFILES", "1", 1); + // Allow firefox.exe to launch XULRunner apps via -app // Note that -app must be the *first* argument. - const char* appDataFile = getenv("XUL_APP_FILE"); + UniqueFreePtr iniPath = BinaryPath::GetApplicationIni(); + if (!iniPath) { + Output("Couldn't find application.ini.\n"); + return 255; + } + char *appDataFile = iniPath.get(); + + if ((!appDataFile || !*appDataFile) && (argc > 1 && IsFlag(argv[1], "app"))) { if (argc == 2) { Output("Incorrect number of arguments passed to -app"); return 255; } appDataFile = argv[2]; char appEnv[MAXPATHLEN]; diff --git a/xpcom/build/BinaryPath.h b/xpcom/build/BinaryPath.h --- a/xpcom/build/BinaryPath.h +++ b/xpcom/build/BinaryPath.h @@ -126,16 +126,56 @@ class BinaryPath { } else { rv = NS_ERROR_FAILURE; } CFRelease(executableURL); return rv; } + static nsresult GetApplicationIni(char aResult[MAXPATHLEN]) + { + // Works even if we're not bundled. + CFBundleRef appBundle = CFBundleGetMainBundle(); + if (!appBundle) { + return NS_ERROR_FAILURE; + } + + CFURLRef iniURL = CFBundleCopyResourceURL(appBundle, CFSTR("application.ini"), + NULL, CFSTR("app")); + if (!iniURL) { + return NS_ERROR_FAILURE; + } + + nsresult rv; + if (CFURLGetFileSystemRepresentation(iniURL, false, (UInt8*)aResult, + MAXPATHLEN)) { + // Sanitize path in case the app was launched from Terminal via + // './firefox' for example. + size_t readPos = 0; + size_t writePos = 0; + while (aResult[readPos] != '\0') { + if (aResult[readPos] == '.' && aResult[readPos + 1] == '/') { + readPos += 2; + } else { + aResult[writePos] = aResult[readPos]; + readPos++; + writePos++; + } + } + aResult[writePos] = '\0'; + rv = NS_OK; + } else { + rv = NS_ERROR_FAILURE; + } + + CFRelease(iniURL); + return rv; + } + #elif defined(ANDROID) static nsresult Get(char aResult[MAXPATHLEN]) { // On Android, we use the MOZ_ANDROID_LIBDIR variable that is set by the // Java bootstrap code. const char* libDir = getenv("MOZ_ANDROID_LIBDIR"); if (!libDir) { return NS_ERROR_FAILURE; } @@ -281,16 +321,29 @@ class BinaryPath { if (NS_FAILED(Get(path))) { return nullptr; } UniqueFreePtr result; result.reset(strdup(path)); return result; } +#if defined(XP_MACOSX) + static UniqueFreePtr GetApplicationIni() + { + char path[MAXPATHLEN]; + if (NS_FAILED(GetApplicationIni(path))) { + return nullptr; + } + UniqueFreePtr result; + result.reset(strdup(path)); + return result; + } +#endif + #ifdef MOZILLA_INTERNAL_API static nsresult GetFile(nsIFile** aResult) { nsCOMPtr lf; # ifdef XP_WIN wchar_t exePath[MAXPATHLEN]; nsresult rv = GetW(exePath); # else char exePath[MAXPATHLEN]; diff --git a/mozglue/build/AudioQueueSanitizer.cpp b/mozglue/build/AudioQueueSanitizer.cpp new file mode 100644 index 000000000000..9efa3255c472 --- /dev/null +++ b/mozglue/build/AudioQueueSanitizer.cpp @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Work around a use-after-free in the macOS 27 system speech synthesizer. +// +// When an utterance finishes, the AudioQueue player in the private +// TextToSpeech framework (used by both NSSpeechSynthesizer and +// AVSpeechSynthesizer) enqueues a short de-click ramp that starts from a +// sample read out of a buffer it has already freed. With the system +// allocator, which zeroes freed memory, that sample is 0 and the ramp is +// silent. mozjemalloc poisons freed memory with 0xE5 instead, which reads +// as -1.36e23 in float32, so every utterance ends with a ~10 ms full-scale +// burst. +// +// We can't fix the framework, so we interpose AudioQueueEnqueueBuffer and +// zero any float samples that are non-finite or far outside the nominal +// [-1, 1] range before they reach CoreAudio. Real audio never gets close +// to kMaxSampleMagnitude, so this only ever affects garbage. +// +// dyld only honors __interpose tuples in images loaded at launch, which is +// why this needs to be in mozglue rather than in XUL (loaded with dlopen) +// alongside OSXSpeechSynthesizerService. + +#include +#include + +namespace { + +constexpr float kMaxSampleMagnitude = 16.0f; + +void SanitizeBuffer(AudioQueueRef aQueue, AudioQueueBufferRef aBuffer) { + AudioStreamBasicDescription format; + UInt32 size = sizeof(format); + if (AudioQueueGetProperty(aQueue, kAudioQueueProperty_StreamDescription, + &format, &size) != noErr) { + return; + } + if (format.mFormatID != kAudioFormatLinearPCM || + !(format.mFormatFlags & kAudioFormatFlagIsFloat) || + format.mBitsPerChannel != 32) { + return; + } + + float* samples = static_cast(aBuffer->mAudioData); + UInt32 count = aBuffer->mAudioDataByteSize / sizeof(float); + for (UInt32 i = 0; i < count; i++) { + // Written so that NaN also fails the comparison. + if (!(fabsf(samples[i]) <= kMaxSampleMagnitude)) { + samples[i] = 0.0f; + } + } +} + +OSStatus SanitizingEnqueueBuffer( + AudioQueueRef aQueue, AudioQueueBufferRef aBuffer, UInt32 aNumPackets, + const AudioStreamPacketDescription* aPacketDescs) { + SanitizeBuffer(aQueue, aBuffer); + return AudioQueueEnqueueBuffer(aQueue, aBuffer, aNumPackets, aPacketDescs); +} + +OSStatus SanitizingEnqueueBufferWithParameters( + AudioQueueRef aQueue, AudioQueueBufferRef aBuffer, UInt32 aNumPackets, + const AudioStreamPacketDescription* aPacketDescs, UInt32 aTrimFrames, + UInt32 aTrimFramesAtEnd, UInt32 aNumParamValues, + const AudioQueueParameterEvent* aParamValues, + const AudioTimeStamp* aStartTime, AudioTimeStamp* aActualStartTime) { + SanitizeBuffer(aQueue, aBuffer); + return AudioQueueEnqueueBufferWithParameters( + aQueue, aBuffer, aNumPackets, aPacketDescs, aTrimFrames, + aTrimFramesAtEnd, aNumParamValues, aParamValues, aStartTime, + aActualStartTime); +} + +struct Interpose { + const void* mReplacement; + const void* mReplacee; +}; + +__attribute__((used, section("__DATA,__interpose"))) const Interpose + kInterposes[] = { + {reinterpret_cast(&SanitizingEnqueueBuffer), + reinterpret_cast(&AudioQueueEnqueueBuffer)}, + {reinterpret_cast(&SanitizingEnqueueBufferWithParameters), + reinterpret_cast( + &AudioQueueEnqueueBufferWithParameters)}, +}; + +} // namespace diff --git a/mozglue/build/moz.build b/mozglue/build/moz.build index f8f01e999e54..111fe5af1b0d 100644 --- a/mozglue/build/moz.build +++ b/mozglue/build/moz.build @@ -41,6 +41,14 @@ if CONFIG["OS_TARGET"] == "WINNT": "winmm.dll", ] +if CONFIG["OS_TARGET"] == "Darwin" and FORCE_SHARED_LIB: + SOURCES += [ + "AudioQueueSanitizer.cpp", + ] + OS_LIBS += [ + "-framework AudioToolbox", + ] + if CONFIG["MOZ_WIDGET_TOOLKIT"]: if CONFIG["MOZ_MEMORY"] and FORCE_SHARED_LIB: pass