SecTranslocateLSNotification.cpp [plain text]
#include <string>
#include <exception>
#include <dlfcn.h>
#include <dispatch/dispatch.h>
#include <CoreServices/CoreServicesPriv.h>
#include <security_utilities/cfutilities.h>
#include <security_utilities/unix++.h>
#include <security_utilities/logging.h>
#include "SecTranslocate.h"
#include "SecTranslocateLSNotification.hpp"
#include "SecTranslocateUtilities.hpp"
#include "SecTranslocateShared.hpp"
#define LS_FRAMEWORK_PATH "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/LaunchServices"
namespace Security {
namespace SecTranslocate {
typedef void (^LSNotificationHandler_t) (LSNotificationCode, CFAbsoluteTime, CFTypeRef, LSASNRef, LSSessionID, LSNotificationID);
typedef LSNotificationID (*ScheduleNotificationOnQueueWithBlock_t) (LSSessionID, CFTypeRef, dispatch_queue_t,LSNotificationHandler_t);
typedef OSStatus (*ModifyNotification_t)(LSNotificationID, UInt32, const LSNotificationCode *, UInt32, const LSNotificationCode *, CFTypeRef, CFTypeRef);
typedef OSStatus (*UnscheduleNotificationFunction_t)(LSNotificationID);
typedef LSASNRef (*ASNCreateWithPid_t)(CFAllocatorRef, int);
typedef uint64_t (*ASNToUInt64_t)(LSASNRef);
typedef Boolean (*IsApplicationRunning_t)(LSSessionID, LSASNRef);
class LaunchServicesProxy
{
public:
static LaunchServicesProxy* get();
inline LSNotificationID scheduleNotificationOnQueueWithBlock (LSSessionID s, CFTypeRef r, dispatch_queue_t q,LSNotificationHandler_t b) const
{ return pScheduleNotificationOnQueueWithBlock ? pScheduleNotificationOnQueueWithBlock(s,r,q,b) : (LSNotificationID)kLSNotificationInvalidID; };
inline OSStatus modifyNotification(LSNotificationID i, UInt32 c, const LSNotificationCode * s, UInt32 l, const LSNotificationCode *n, CFTypeRef a, CFTypeRef r) const
{ return pModifyNotification ? pModifyNotification(i,c,s,l,n,a,r) : kLSUnknownErr; };
inline OSStatus unscheduleNotificationFunction(LSNotificationID i) const
{ return pUnscheduleNotificationFunction ? pUnscheduleNotificationFunction(i) : kLSUnknownErr; };
inline LSASNRef asnCreateWithPid(CFAllocatorRef a, int p) const
{ return pASNCreateWithPid ? pASNCreateWithPid(a,p) : NULL; };
inline uint64_t asnToUInt64 (LSASNRef a) const
{ return pASNToUInt64 ? pASNToUInt64(a) : 0; };
inline CFStringRef bundlePathKey() const { return pBundlePathKey ? *pBundlePathKey : NULL;};
inline Boolean isApplicationRunning(LSSessionID i, LSASNRef a) const {return pIsApplicationRunning ? pIsApplicationRunning(i,a): false;};
private:
LaunchServicesProxy();
void* handle;
ScheduleNotificationOnQueueWithBlock_t pScheduleNotificationOnQueueWithBlock;
ModifyNotification_t pModifyNotification;
UnscheduleNotificationFunction_t pUnscheduleNotificationFunction;
ASNCreateWithPid_t pASNCreateWithPid;
ASNToUInt64_t pASNToUInt64;
CFStringRef *pBundlePathKey;
IsApplicationRunning_t pIsApplicationRunning;
};
LaunchServicesProxy::LaunchServicesProxy()
{
handle = checkedDlopen(LS_FRAMEWORK_PATH, RTLD_LAZY | RTLD_NOLOAD);
pScheduleNotificationOnQueueWithBlock = (ScheduleNotificationOnQueueWithBlock_t) checkedDlsym(handle, "_LSScheduleNotificationOnQueueWithBlock");
pModifyNotification = (ModifyNotification_t) checkedDlsym(handle, "_LSModifyNotification");
pUnscheduleNotificationFunction = (UnscheduleNotificationFunction_t) checkedDlsym(handle, "_LSUnscheduleNotificationFunction");
pASNCreateWithPid = (ASNCreateWithPid_t) checkedDlsym(handle, "_LSASNCreateWithPid");
pASNToUInt64 = (ASNToUInt64_t) checkedDlsym(handle, "_LSASNToUInt64");
pBundlePathKey = (CFStringRef*) checkedDlsym(handle, "_kLSBundlePathKey");
pIsApplicationRunning = (IsApplicationRunning_t) checkedDlsym(handle, "_LSIsApplicationRunning");
}
LaunchServicesProxy* LaunchServicesProxy::get()
{
static dispatch_once_t initialized;
static LaunchServicesProxy* me = NULL;
__block exception_ptr exception(0);
dispatch_once(&initialized, ^{
try
{
me = new LaunchServicesProxy();
}
catch (...)
{
Syslog::critical("SecTranslocate: error while creating LaunchServicesProxy");
exception = current_exception();
}
});
if (me == NULL)
{
if(exception)
{
rethrow_exception(exception); }
else
{
Syslog::critical("SecTranslocate: LaunchServicesProxy initialization has failed");
UnixError::throwMe(EINVAL);
}
}
return me;
}
LSNotificationMonitor::LSNotificationMonitor(dispatch_queue_t q): notificationQ(q)
{
if (notificationQ == NULL)
{
Syslog::critical("SecTranslocate::LSNotificationMonitor initialized without a queue.");
UnixError::throwMe(EINVAL);
}
dispatch_retain(notificationQ);
}
LSNotificationMonitor::~LSNotificationMonitor()
{
dispatch_release(notificationQ);
}
string LSNotificationMonitor::stringIfTranslocated(CFStringRef appPath)
{
if(appPath == NULL)
{
Syslog::error("SecTranslocate: no appPath provided");
return "";
}
CFRef<CFURLRef> appURL = makeCFURL(appPath);
bool isTranslocated = false;
string out = cfString(appURL);
if (!SecTranslocateIsTranslocatedURL(appURL, &isTranslocated, NULL))
{
Syslog::error("SecTranslocate: path for asn doesn't exist or isn't accessible: %s",out.c_str());
return "";
}
if(!isTranslocated)
{
Syslog::error("SecTranslocate: asn is not translocated: %s",out.c_str());
return "";
}
return out;
}
void LSNotificationMonitor::checkIn(pid_t pid)
{
dispatch_async(notificationQ, ^(){
try
{
LaunchServicesProxy* lsp = LaunchServicesProxy::get();
CFRef<LSASNRef> asn = lsp->asnCreateWithPid(kCFAllocatorDefault, pid);
if(lsp->isApplicationRunning(kLSDefaultSessionID, asn))
{
LSNotificationID nid = lsp->scheduleNotificationOnQueueWithBlock(kLSDefaultSessionID,
cfEmptyArray(),
notificationQ,
^ (LSNotificationCode notification,
CFAbsoluteTime notificationTime,
CFTypeRef dataRef,
LSASNRef affectedASNRef,
LSSessionID session,
LSNotificationID notificationID){
if( notification == kLSNotifyApplicationDeath && dataRef)
{
this->asnDied(dataRef);
}
lsp->unscheduleNotificationFunction(notificationID);
});
LSNotificationCode notificationCode = kLSNotifyApplicationDeath;
lsp->modifyNotification(nid, 1, ¬ificationCode, 0, NULL, asn, NULL);
}
else
{
Syslog::warning("SecTranslocate: pid %d checked in, but it is not running",pid);
}
}
catch(...)
{
Syslog::error("SecTranslocate: checkin failed for pid %d",pid);
}
});
}
void LSNotificationMonitor::asnDied(CFTypeRef data) const
{
string path;
try
{
CFDictionaryRef dict = NULL;
if(CFGetTypeID(data) == CFDictionaryGetTypeID())
{
dict = (CFDictionaryRef)data;
}
else
{
Syslog::error("SecTranslocate: no data dictionary at app death");
return;
}
LaunchServicesProxy* lsp = LaunchServicesProxy::get();
path = stringIfTranslocated((CFStringRef)CFDictionaryGetValue(dict,lsp->bundlePathKey()));
}
catch(...)
{
Syslog::error("SecTranslocate: asn death processing failed");
return;
}
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, 5LL * NSEC_PER_SEC);
dispatch_after(when, notificationQ, ^() {
try
{
if(path.empty())
{
destroyTranslocatedPathsForUserOnVolume();
}
else
{
destroyTranslocatedPathForUser(path);
}
}
catch(...)
{
Syslog::error("SecTranslocate: problem deleting translocation after app death: %s", path.c_str());
}
});
}
} }