gecko-dev/xpcom/base/nsMacUtilsImpl.cpp
Haik Aftandilian d302444d67 Bug 1498742 - Part 2 - Start the GMP sandbox earlier during process startup r=jya,cpearce
Change the Mac GMP process launch to include sandboxing params on the command line to allow the sandbox to be started earlier during GMP process launch. Content, extension, and RDD processes have already been changed to start the sandbox earlier.

Update GMPProcessParent to override GeckoChildProcessHost methods used to construct sandboxing parameters. Pass the plugin path as a sandbox parameter so that the sandbox rules can whitelist the plugin directory which is now read after the sandbox is enabled in the plugin process. On development builds, pass "testingReadPath" params so directories needed during automated tests can be whitelisted.

Update Mac sandboxing code to detect GMP sandbox params on the command line and enable the sandbox with additional arguments needed for early sandbox start.

Allow reverting to the old implementation by setting security.sandbox.gmp.mac.earlyinit to false.

Differential Revision: https://phabricator.services.mozilla.com/D34085

--HG--
extra : moz-landing-system : lando
2019-06-14 22:55:27 +00:00

444 lines
13 KiB
C++

/* -*- 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/. */
#include "nsMacUtilsImpl.h"
#include "base/command_line.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/ContentChild.h"
#include "nsDirectoryServiceDefs.h"
#include "nsCOMPtr.h"
#include "nsIFile.h"
#include "nsIProperties.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
#include "prenv.h"
#if defined(MOZ_SANDBOX)
# include "mozilla/SandboxSettings.h"
#endif
#include <CoreFoundation/CoreFoundation.h>
#include <CoreServices/CoreServices.h>
#include <sys/sysctl.h>
NS_IMPL_ISUPPORTS(nsMacUtilsImpl, nsIMacUtils)
using mozilla::StaticMutexAutoLock;
using mozilla::Unused;
#if defined(MOZ_SANDBOX)
StaticAutoPtr<nsCString> nsMacUtilsImpl::sCachedAppPath;
StaticMutex nsMacUtilsImpl::sCachedAppPathMutex;
#endif
// Info.plist key associated with the developer repo path
#define MAC_DEV_REPO_KEY "MozillaDeveloperRepoPath"
// Info.plist key associated with the developer repo object directory
#define MAC_DEV_OBJ_KEY "MozillaDeveloperObjPath"
// Initialize with Unknown until we've checked if TCSM is available to set
Atomic<nsMacUtilsImpl::TCSMStatus> nsMacUtilsImpl::sTCSMStatus(TCSM_Unknown);
nsresult nsMacUtilsImpl::GetArchString(nsAString& aArchString) {
if (!mBinaryArchs.IsEmpty()) {
aArchString.Assign(mBinaryArchs);
return NS_OK;
}
aArchString.Truncate();
bool foundPPC = false, foundX86 = false, foundPPC64 = false,
foundX86_64 = false;
CFBundleRef mainBundle = ::CFBundleGetMainBundle();
if (!mainBundle) {
return NS_ERROR_FAILURE;
}
CFArrayRef archList = ::CFBundleCopyExecutableArchitectures(mainBundle);
if (!archList) {
return NS_ERROR_FAILURE;
}
CFIndex archCount = ::CFArrayGetCount(archList);
for (CFIndex i = 0; i < archCount; i++) {
CFNumberRef arch =
static_cast<CFNumberRef>(::CFArrayGetValueAtIndex(archList, i));
int archInt = 0;
if (!::CFNumberGetValue(arch, kCFNumberIntType, &archInt)) {
::CFRelease(archList);
return NS_ERROR_FAILURE;
}
if (archInt == kCFBundleExecutableArchitecturePPC) {
foundPPC = true;
} else if (archInt == kCFBundleExecutableArchitectureI386) {
foundX86 = true;
} else if (archInt == kCFBundleExecutableArchitecturePPC64) {
foundPPC64 = true;
} else if (archInt == kCFBundleExecutableArchitectureX86_64) {
foundX86_64 = true;
}
}
::CFRelease(archList);
// The order in the string must always be the same so
// don't do this in the loop.
if (foundPPC) {
mBinaryArchs.AppendLiteral("ppc");
}
if (foundX86) {
if (!mBinaryArchs.IsEmpty()) {
mBinaryArchs.Append('-');
}
mBinaryArchs.AppendLiteral("i386");
}
if (foundPPC64) {
if (!mBinaryArchs.IsEmpty()) {
mBinaryArchs.Append('-');
}
mBinaryArchs.AppendLiteral("ppc64");
}
if (foundX86_64) {
if (!mBinaryArchs.IsEmpty()) {
mBinaryArchs.Append('-');
}
mBinaryArchs.AppendLiteral("x86_64");
}
aArchString.Assign(mBinaryArchs);
return (aArchString.IsEmpty() ? NS_ERROR_FAILURE : NS_OK);
}
NS_IMETHODIMP
nsMacUtilsImpl::GetArchitecturesInBinary(nsAString& aArchString) {
return GetArchString(aArchString);
}
// True when running under binary translation (Rosetta).
NS_IMETHODIMP
nsMacUtilsImpl::GetIsTranslated(bool* aIsTranslated) {
#ifdef __ppc__
static bool sInitialized = false;
// Initialize sIsNative to 1. If the sysctl fails because it doesn't
// exist, then translation is not possible, so the process must not be
// running translated.
static int32_t sIsNative = 1;
if (!sInitialized) {
size_t sz = sizeof(sIsNative);
sysctlbyname("sysctl.proc_native", &sIsNative, &sz, nullptr, 0);
sInitialized = true;
}
*aIsTranslated = !sIsNative;
#else
// Translation only exists for ppc code. Other architectures aren't
// translated.
*aIsTranslated = false;
#endif
return NS_OK;
}
#if defined(MOZ_SANDBOX)
// Get the path to the .app directory (aka bundle) for the parent process.
// When executing in the child process, this is the outer .app (such as
// Firefox.app) and not the inner .app containing the child process
// executable. We don't rely on the actual .app extension to allow for the
// bundle being renamed.
bool nsMacUtilsImpl::GetAppPath(nsCString& aAppPath) {
StaticMutexAutoLock lock(sCachedAppPathMutex);
if (sCachedAppPath) {
aAppPath.Assign(*sCachedAppPath);
return true;
}
nsAutoCString appPath;
nsAutoCString appBinaryPath(
(CommandLine::ForCurrentProcess()->argv()[0]).c_str());
// The binary path resides within the .app dir in Contents/MacOS,
// e.g., Firefox.app/Contents/MacOS/firefox. Search backwards in
// the binary path for the end of .app path.
auto pattern = NS_LITERAL_CSTRING("/Contents/MacOS/");
nsAutoCString::const_iterator start, end;
appBinaryPath.BeginReading(start);
appBinaryPath.EndReading(end);
if (RFindInReadable(pattern, start, end)) {
end = start;
appBinaryPath.BeginReading(start);
// If we're executing in a child process, get the parent .app path
// by searching backwards once more. The child executable resides
// in Firefox.app/Contents/MacOS/plugin-container/Contents/MacOS.
if (!XRE_IsParentProcess()) {
if (RFindInReadable(pattern, start, end)) {
end = start;
appBinaryPath.BeginReading(start);
} else {
return false;
}
}
appPath.Assign(Substring(start, end));
} else {
return false;
}
nsCOMPtr<nsIFile> app;
nsresult rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(appPath), true,
getter_AddRefs(app));
if (NS_FAILED(rv)) {
return false;
}
rv = app->Normalize();
if (NS_FAILED(rv)) {
return false;
}
app->GetNativePath(aAppPath);
if (!sCachedAppPath) {
sCachedAppPath = new nsCString(aAppPath);
if (NS_IsMainThread()) {
nsMacUtilsImpl::ClearCachedAppPathOnShutdown();
} else {
NS_DispatchToMainThread(NS_NewRunnableFunction(
"nsMacUtilsImpl::ClearCachedAppPathOnShutdown",
[] { nsMacUtilsImpl::ClearCachedAppPathOnShutdown(); }));
}
}
return true;
}
nsresult nsMacUtilsImpl::ClearCachedAppPathOnShutdown() {
MOZ_ASSERT(NS_IsMainThread());
ClearOnShutdown(&sCachedAppPath);
return NS_OK;
}
# if defined(DEBUG)
// If XPCOM_MEM_BLOAT_LOG or XPCOM_MEM_LEAK_LOG is set to a log file
// path, return the path to the parent directory (where sibling log
// files will be saved.)
nsresult nsMacUtilsImpl::GetBloatLogDir(nsCString& aDirectoryPath) {
nsAutoCString bloatLog(PR_GetEnv("XPCOM_MEM_BLOAT_LOG"));
if (bloatLog.IsEmpty()) {
bloatLog = PR_GetEnv("XPCOM_MEM_LEAK_LOG");
}
if (!bloatLog.IsEmpty() && bloatLog != "1" && bloatLog != "2") {
return GetDirectoryPath(bloatLog.get(), aDirectoryPath);
}
return NS_OK;
}
// Given a path to a file, return the directory which contains it.
nsresult nsMacUtilsImpl::GetDirectoryPath(const char* aPath,
nsCString& aDirectoryPath) {
nsresult rv = NS_OK;
nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = file->InitWithNativePath(nsDependentCString(aPath));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> directoryFile;
rv = file->GetParent(getter_AddRefs(directoryFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = directoryFile->Normalize();
NS_ENSURE_SUCCESS(rv, rv);
if (NS_FAILED(directoryFile->GetNativePath(aDirectoryPath))) {
MOZ_CRASH("Failed to get path for an nsIFile");
}
return NS_OK;
}
# endif /* DEBUG */
#endif /* MOZ_SANDBOX */
/* static */
bool nsMacUtilsImpl::IsTCSMAvailable() {
if (sTCSMStatus == TCSM_Unknown) {
uint32_t oldVal = 0;
size_t oldValSize = sizeof(oldVal);
int rv = sysctlbyname("kern.tcsm_available", &oldVal, &oldValSize, NULL, 0);
TCSMStatus newStatus;
if (rv < 0 || oldVal == 0) {
newStatus = TCSM_Unavailable;
} else {
newStatus = TCSM_Available;
}
// The value of sysctl kern.tcsm_available is the same for all
// threads within the same process. If another thread raced with us
// and initialized sTCSMStatus first (changing it from
// TCSM_Unknown), we can continue without needing to update it
// again. Hence, we ignore compareExchange's return value.
Unused << sTCSMStatus.compareExchange(TCSM_Unknown, newStatus);
}
return (sTCSMStatus == TCSM_Available);
}
/* static */
nsresult nsMacUtilsImpl::EnableTCSM() {
uint32_t newVal = 1;
int rv = sysctlbyname("kern.tcsm_enable", NULL, 0, &newVal, sizeof(newVal));
if (rv < 0) {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
/*
* Intentionally return void so that failures will be ignored in non-debug
* builds. This method uses new sysctls which may not be as thoroughly tested
* and we don't want to cause crashes handling the failure due to an OS bug.
*/
/* static */
void nsMacUtilsImpl::EnableTCSMIfAvailable() {
if (IsTCSMAvailable()) {
if (NS_FAILED(EnableTCSM())) {
NS_WARNING("Failed to enable TCSM");
}
MOZ_ASSERT(IsTCSMEnabled());
}
}
#if defined(DEBUG)
/* static */
bool nsMacUtilsImpl::IsTCSMEnabled() {
uint32_t oldVal = 0;
size_t oldValSize = sizeof(oldVal);
int rv = sysctlbyname("kern.tcsm_enable", &oldVal, &oldValSize, NULL, 0);
return (rv == 0) && (oldVal != 0);
}
#endif
// Returns 0 on error.
/* static */
uint32_t nsMacUtilsImpl::GetPhysicalCPUCount() {
uint32_t oldVal = 0;
size_t oldValSize = sizeof(oldVal);
int rv = sysctlbyname("hw.physicalcpu_max", &oldVal, &oldValSize, NULL, 0);
if (rv == -1) {
return 0;
}
return oldVal;
}
/*
* Helper function to read a string value for a given key from the .app's
* Info.plist.
*/
static nsresult GetStringValueFromBundlePlist(const nsAString& aKey,
nsAutoCString& aValue) {
CFBundleRef mainBundle = CFBundleGetMainBundle();
if (mainBundle == nullptr) {
return NS_ERROR_FAILURE;
}
// Read this app's bundle Info.plist as a dictionary
CFDictionaryRef bundleInfoDict = CFBundleGetInfoDictionary(mainBundle);
if (bundleInfoDict == nullptr) {
return NS_ERROR_FAILURE;
}
nsAutoCString keyAutoCString = NS_ConvertUTF16toUTF8(aKey);
CFStringRef key = CFStringCreateWithCString(
kCFAllocatorDefault, keyAutoCString.get(), kCFStringEncodingUTF8);
if (key == nullptr) {
return NS_ERROR_FAILURE;
}
CFStringRef value = (CFStringRef)CFDictionaryGetValue(bundleInfoDict, key);
CFRelease(key);
if (value == nullptr) {
return NS_ERROR_FAILURE;
}
CFIndex valueLength = CFStringGetLength(value);
if (valueLength == 0) {
return NS_ERROR_FAILURE;
}
const char* valueCString =
CFStringGetCStringPtr(value, kCFStringEncodingUTF8);
if (valueCString) {
aValue.Assign(valueCString);
return NS_OK;
}
CFIndex maxLength =
CFStringGetMaximumSizeForEncoding(valueLength, kCFStringEncodingUTF8) + 1;
char* valueBuffer = static_cast<char*>(moz_xmalloc(maxLength));
if (!CFStringGetCString(value, valueBuffer, maxLength,
kCFStringEncodingUTF8)) {
free(valueBuffer);
return NS_ERROR_FAILURE;
}
aValue.Assign(valueBuffer);
free(valueBuffer);
return NS_OK;
}
/*
* Helper function for reading a path string from the .app's Info.plist
* and returning a directory object for that path with symlinks resolved.
*/
static nsresult GetDirFromBundlePlist(const nsAString& aKey, nsIFile** aDir) {
nsresult rv;
nsAutoCString dirPath;
rv = GetStringValueFromBundlePlist(aKey, dirPath);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> dir;
rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(dirPath), false,
getter_AddRefs(dir));
NS_ENSURE_SUCCESS(rv, rv);
rv = dir->Normalize();
NS_ENSURE_SUCCESS(rv, rv);
bool isDirectory = false;
rv = dir->IsDirectory(&isDirectory);
NS_ENSURE_SUCCESS(rv, rv);
if (!isDirectory) {
return NS_ERROR_FILE_NOT_DIRECTORY;
}
dir.swap(*aDir);
return NS_OK;
}
nsresult nsMacUtilsImpl::GetRepoDir(nsIFile** aRepoDir) {
#if defined(MOZ_SANDBOX)
MOZ_ASSERT(mozilla::IsDevelopmentBuild());
#endif
return GetDirFromBundlePlist(NS_LITERAL_STRING(MAC_DEV_REPO_KEY), aRepoDir);
}
nsresult nsMacUtilsImpl::GetObjDir(nsIFile** aObjDir) {
#if defined(MOZ_SANDBOX)
MOZ_ASSERT(mozilla::IsDevelopmentBuild());
#endif
return GetDirFromBundlePlist(NS_LITERAL_STRING(MAC_DEV_OBJ_KEY), aObjDir);
}