Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Crashlytics/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Unreleased
- [fixed] Added ability to conformed to Mach IPC security restrictions. User need to add FirebaseCrashlyticsMachProtectedEnabled=YES to their info.plist to enable this feature. Note: This change would potentially change mach exception types we receive from kernel which might affect issue clustering result. (#15393)

# 12.4.0
- [fixed] Make set development platform APIs to chain on Crashlytics context init promise.

Expand Down
7 changes: 7 additions & 0 deletions Crashlytics/Crashlytics/Components/FIRCLSContext.m
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
initData.previouslyCrashedFileRootPath = [fileManager rootPath];
initData.errorsEnabled = [settings errorReportingEnabled];
initData.customExceptionsEnabled = [settings customExceptionsEnabled];
initData.machProtectedEnabled = [settings machProtectedEnabled];
initData.maxCustomExceptions = [settings maxCustomExceptions];
initData.maxErrorLogSize = [settings errorLogBufferSize];
initData.maxLogSize = [settings logBufferSize];
Expand Down Expand Up @@ -185,6 +186,12 @@
_firclsContext.readonly->machException.path =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportMachExceptionFile);

// from settings checkout behavior
_firclsContext.readonly->machException.behavior = EXCEPTION_DEFAULT;
if (initData.machProtectedEnabled) {
_firclsContext.readonly->machException.behavior = EXCEPTION_IDENTITY_PROTECTED;
}

FIRCLSMachExceptionInit(&_firclsContext.readonly->machException);
});
#endif
Expand Down
6 changes: 5 additions & 1 deletion Crashlytics/Crashlytics/FIRCrashlytics.m
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,14 @@ - (instancetype)initWithApp:(FIRApp *)app

_fileManager = [[FIRCLSFileManager alloc] init];
_googleAppID = app.options.googleAppID;
// getting data collection state from info.plist, user defaults etc. disk I/O on main
_dataArbiter = [[FIRCLSDataCollectionArbiter alloc] initWithApp:app withAppInfo:appInfo];

FIRCLSApplicationIdentifierModel *appModel = [[FIRCLSApplicationIdentifierModel alloc] init];
// read settings cache, disk I/O on main
FIRCLSSettings *settings = [[FIRCLSSettings alloc] initWithFileManager:_fileManager
appIDModel:appModel];
appIDModel:appModel
appInfo:appInfo];

FIRCLSOnDemandModel *onDemandModel =
[[FIRCLSOnDemandModel alloc] initWithFIRCLSSettings:settings fileManager:_fileManager];
Expand Down Expand Up @@ -200,6 +203,7 @@ - (instancetype)initWithApp:(FIRApp *)app
}

_contextInitPromise =
// setup with context init (binary image, register exception handler)
[[[_reportManager startWithProfiling] then:^id _Nullable(NSNumber *_Nullable value) {
if (![value boolValue]) {
FIRCLSErrorLog(@"Crash reporting could not be initialized");
Expand Down
75 changes: 66 additions & 9 deletions Crashlytics/Crashlytics/Handlers/FIRCLSMachException.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@ static bool FIRCLSMachExceptionReply(FIRCLSMachExceptionReadContext* context,
kern_return_t result);
static bool FIRCLSMachExceptionRegister(FIRCLSMachExceptionReadContext* context);
static bool FIRCLSMachExceptionUnregister(FIRCLSMachExceptionOriginalPorts* originalPorts,
exception_mask_t mask);
exception_mask_t mask,
exception_behavior_t behavior);
static bool FIRCLSMachExceptionRecord(FIRCLSMachExceptionReadContext* context,
MachExceptionMessage* message);

static void FIRCLSCrashedThreadLookup(MachExceptionMessage* message, thread_t* crashedThread);
#pragma mark - Initialization
void FIRCLSMachExceptionInit(FIRCLSMachExceptionReadContext* context) {
if (!FIRCLSUnlinkIfExists(context->path)) {
Expand All @@ -58,7 +59,7 @@ void FIRCLSMachExceptionInit(FIRCLSMachExceptionReadContext* context) {

if (!FIRCLSMachExceptionThreadStart(context)) {
FIRCLSSDKLog("Unable to start thread\n");
FIRCLSMachExceptionUnregister(&context->originalPorts, context->mask);
FIRCLSMachExceptionUnregister(&context->originalPorts, context->mask, context->behavior);
}
}

Expand Down Expand Up @@ -213,13 +214,23 @@ static kern_return_t FIRCLSMachExceptionDispatchMessage(FIRCLSMachExceptionReadC

// This will happen if a child process raises an exception, as the exception ports are
// inherited.
if (message->task.name != mach_task_self()) {
mach_port_t actual_port;
kern_return_t kr = KERN_SUCCESS;
if (context->behavior == EXCEPTION_DEFAULT){
actual_port = message->payload.default_payload.task.name;
} else {
// EXCEPTION_IDENTITY_PROTECTED
task_id_token_t token = message->payload.protected_payload.task_id_token.name;
kr = task_identity_token_get_task_port(token, TASK_FLAVOR_CONTROL, &actual_port);
}

if (kr || actual_port != mach_task_self()) {
FIRCLSSDKLog("Mach exception task mis-match, returning failure\n");
return KERN_FAILURE;
}

FIRCLSSDKLog("Unregistering handler\n");
if (!FIRCLSMachExceptionUnregister(&context->originalPorts, context->mask)) {
if (!FIRCLSMachExceptionUnregister(&context->originalPorts, context->mask, context->behavior)) {
FIRCLSSDKLog("Failed to unregister\n");
return KERN_FAILURE;
}
Expand Down Expand Up @@ -296,7 +307,7 @@ static bool FIRCLSMachExceptionRegister(FIRCLSMachExceptionReadContext* context)

// ORing with MACH_EXCEPTION_CODES will produce 64-bit exception data
kr = task_swap_exception_ports(task, context->mask, context->port,
EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE,
context->behavior | MACH_EXCEPTION_CODES, THREAD_STATE_NONE,
context->originalPorts.masks, &context->originalPorts.count,
context->originalPorts.ports, context->originalPorts.behaviors,
context->originalPorts.flavors);
Expand All @@ -315,7 +326,8 @@ static bool FIRCLSMachExceptionRegister(FIRCLSMachExceptionReadContext* context)
}

static bool FIRCLSMachExceptionUnregister(FIRCLSMachExceptionOriginalPorts* originalPorts,
exception_mask_t mask) {
exception_mask_t mask,
exception_behavior_t behavior) {
kern_return_t kr;

// Re-register all the old ports.
Expand All @@ -333,7 +345,7 @@ static bool FIRCLSMachExceptionUnregister(FIRCLSMachExceptionOriginalPorts* orig

// Finally, mark any masks we registered for that do not have an original port as unused.
kr = task_set_exception_ports(mach_task_self(), mask, MACH_PORT_NULL,
EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE);
behavior | MACH_EXCEPTION_CODES, THREAD_STATE_NONE);
if (kr != KERN_SUCCESS) {
FIRCLSSDKLog("unable to unset unregistered mask: 0x%x", mask);
return false;
Expand Down Expand Up @@ -520,13 +532,58 @@ static bool FIRCLSMachExceptionRecord(FIRCLSMachExceptionReadContext* context,

FIRCLSFileWriteSectionEnd(&file);

FIRCLSHandler(&file, message->thread.name, NULL, true);
thread_t crashedThread;
if (context->behavior == EXCEPTION_DEFAULT) {
crashedThread = message->payload.default_payload.thread.name;
} else {
// EXCEPTION_IDENTITY_PROTECTED
FIRCLSCrashedThreadLookup(message, &crashedThread);
}
FIRCLSSDKLog("Crashed threads: %d\n", crashedThread);
FIRCLSHandler(&file, crashedThread, NULL, true);

FIRCLSFileClose(&file);

return true;
}

static void FIRCLSCrashedThreadLookup(MachExceptionMessage* message, thread_t* crashedThread) {
thread_act_array_t threadList;
mach_msg_type_number_t threadCount;

// last 64 bits include thread id info
MachExceptionProtectedThreadInfo protected_thread_info = *(MachExceptionProtectedThreadInfo *) &message->payload.protected_payload.thread_id;
kern_return_t kr = task_threads(mach_task_self(), &threadList, &threadCount);

if (kr != KERN_SUCCESS) {
FIRCLSSDKLogError("Failed to get threads: %d\n", kr);
return;
}
for (int i = 0; i < threadCount; i++) {
thread_t thread = threadList[i];

thread_basic_info_data_t basicInfo;
thread_identifier_info_data_t identifierInfo;
mach_msg_type_number_t infoCount = THREAD_BASIC_INFO_COUNT;

kr = thread_info(thread, THREAD_IDENTIFIER_INFO, (thread_info_t)&identifierInfo, &infoCount);

if (kr == KERN_SUCCESS) {
FIRCLSSDKLog("Thread %d: Thread port: %d, thread id: %llx\n", i, thread, identifierInfo.thread_id);

if (protected_thread_info.thread_id == identifierInfo.thread_id) {
FIRCLSSDKLog("Find crashed thread: %d\n", thread);
*crashedThread = thread;
}
}

// Note: You must deallocate the send right for each thread port
// to prevent port leaks, as task_threads increments the ref count.
mach_port_deallocate(mach_task_self(), thread);
}
vm_deallocate(mach_task_self(), (vm_address_t)threadList, threadCount * sizeof(thread_t));
}

#else

INJECT_STRIP_SYMBOL(cls_mach_exception)
Expand Down
31 changes: 29 additions & 2 deletions Crashlytics/Crashlytics/Handlers/FIRCLSMachException.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,33 @@

#pragma mark Structures
#pragma pack(push, 4)

typedef struct {
mach_msg_port_descriptor_t thread;
mach_msg_port_descriptor_t task;
} MachDefaultPayload;

typedef struct {
mach_msg_port_descriptor_t task_id_token;
mach_msg_port_descriptor_t thread_id;
} MachProtectdPayload;

union MachMessagePayload {
MachDefaultPayload default_payload;
MachProtectdPayload protected_payload;
};

// When set Mach port with default behavior:
// payload_1 including thread port
// payload_2 including task port
// When set Mach port with identity protected behavior:
// payload_1 including task_id_token
// payload_2 including thread_id
typedef struct {
mach_msg_header_t head;
/* start of the kernel processed data */
mach_msg_body_t msgh_body;
mach_msg_port_descriptor_t thread;
mach_msg_port_descriptor_t task;
union MachMessagePayload payload;
/* end of the kernel processed data */
NDR_record_t NDR;
exception_type_t exception;
Expand All @@ -41,6 +62,11 @@ typedef struct {
mach_msg_trailer_t trailer;
} MachExceptionMessage;

typedef struct {
uint64_t pad1;
uint64_t thread_id;
} MachExceptionProtectedThreadInfo;

typedef struct {
mach_msg_header_t head;
NDR_record_t NDR;
Expand All @@ -63,6 +89,7 @@ typedef struct {

exception_mask_t mask;
FIRCLSMachExceptionOriginalPorts originalPorts;
exception_behavior_t behavior;
} FIRCLSMachExceptionReadContext;

#pragma mark - API
Expand Down
1 change: 1 addition & 0 deletions Crashlytics/Crashlytics/Helpers/FIRCLSContextInitData.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, copy) NSString* betaToken;
@property(nonatomic) BOOL errorsEnabled;
@property(nonatomic) BOOL customExceptionsEnabled;
@property(nonatomic) BOOL machProtectedEnabled;
@property(nonatomic) uint32_t maxCustomExceptions;
@property(nonatomic) uint32_t maxErrorLogSize;
@property(nonatomic) uint32_t maxLogSize;
Expand Down
9 changes: 8 additions & 1 deletion Crashlytics/Crashlytics/Models/FIRCLSSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)initWithFileManager:(FIRCLSFileManager *)fileManager
appIDModel:(FIRCLSApplicationIdentifierModel *)appIDModel;
appIDModel:(FIRCLSApplicationIdentifierModel *)appIDModel
appInfo:(NSDictionary *)appInfo;

/**
* Recreates the settings dictionary by re-reading the settings file from persistent storage. This
Expand Down Expand Up @@ -78,6 +79,12 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property(nonatomic, readonly) BOOL customExceptionsEnabled;

/**
* When this is true, Crashlytics will use EXCEPTION_IDENTITY_PROTECTED
* for mach exception handler instead of EXCEPTION_DEFAULT for default
*/
@property(nonatomic) BOOL machProtectedEnabled;

/**
* When this is true, Crashlytics will collect data from MetricKit
*/
Expand Down
16 changes: 15 additions & 1 deletion Crashlytics/Crashlytics/Models/FIRCLSSettings.m
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
NSString *const GoogleAppIDKey = @"google_app_id";
NSString *const BuildInstanceID = @"build_instance_id";
NSString *const AppVersion = @"app_version";
NSString *const FirebaseCrashlyticsMachProtectedEnabledKey =
@"FirebaseCrashlyticsMachProtectedEnabled";

@interface FIRCLSSettings ()

Expand All @@ -47,21 +49,33 @@ @interface FIRCLSSettings ()
@implementation FIRCLSSettings

- (instancetype)initWithFileManager:(FIRCLSFileManager *)fileManager
appIDModel:(FIRCLSApplicationIdentifierModel *)appIDModel {
appIDModel:(FIRCLSApplicationIdentifierModel *)appIDModel
appInfo:(NSDictionary *)appInfo {
return
[self initWithFileManager:fileManager
appIDModel:appIDModel
appInfo:(NSDictionary *)appInfo
deletionQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)];
}

- (instancetype)initWithFileManager:(FIRCLSFileManager *)fileManager
appIDModel:(FIRCLSApplicationIdentifierModel *)appIDModel
appInfo:(NSDictionary *)appInfo
deletionQueue:(dispatch_queue_t)deletionQueue {
self = [super init];
if (!self) {
return nil;
}

// config the mach exception message receiving behavior
self.machProtectedEnabled = false;
id crashlyticsMachProtectedEnabled =
[appInfo objectForKey:FirebaseCrashlyticsMachProtectedEnabledKey];
if ([crashlyticsMachProtectedEnabled isKindOfClass:[NSString class]] ||
[crashlyticsMachProtectedEnabled isKindOfClass:[NSNumber class]]) {
self.machProtectedEnabled = [crashlyticsMachProtectedEnabled boolValue];
}

_fileManager = fileManager;
_appIDModel = appIDModel;

Expand Down
Loading