#include <notify.h>
#include <sys/sysctl.h>
#include "notifications.h"
#include "server.h"
#include "connection.h"
#include "dictionary.h"
#include "SharedMemoryClient.h"
#include <securityd_client/ucspNotify.h>
#include <security_utilities/casts.h>
#include <Security/SecKeychain.h>
#include <Security/SecItemInternal.h>
Listener::ListenerMap& Listener::listeners = *(new Listener::ListenerMap);
Mutex Listener::setLock(Mutex::recursive);
Listener::Listener(NotificationDomain dom, NotificationMask evs, mach_port_t port)
: domain(dom), events(evs)
{
assert(events);
StLock<Mutex> _(setLock);
listeners.insert(ListenerMap::value_type(port, this));
secinfo("notify", "%p created for domain 0x%x events 0x%x port %d",
this, dom, evs, port);
}
Listener::~Listener()
{
secinfo("notify", "%p destroyed", this);
}
void Listener::notify(NotificationDomain domain,
NotificationEvent event, const CssmData &data)
{
RefPointer<Notification> message = new Notification(domain, event, 0, data);
StLock<Mutex> _(setLock);
sendNotification(message);
}
void Listener::notify(NotificationDomain domain,
NotificationEvent event, uint32 sequence, const CssmData &data, audit_token_t auditToken)
{
Connection ¤t = Server::active().connection();
RefPointer<Notification> message = new Notification(domain, event, sequence, data);
if (current.inSequence(message)) {
StLock<Mutex> _(setLock);
uid_t uid = audit_token_to_euid(auditToken);
gid_t gid = audit_token_to_egid(auditToken);
SharedMemoryListener::createDefaultSharedMemoryListener(uid, gid);
sendNotification(message);
while (RefPointer<Notification> next = current.popNotification())
sendNotification(next);
}
}
void Listener::sendNotification(Notification *message)
{
secdebug("MDSPRIVACY","Listener::sendNotification for uid/euid: %d/%d", getuid(), geteuid());
for (ListenerMap::const_iterator it = listeners.begin();
it != listeners.end(); it++) {
Listener *listener = it->second;
if (listener->domain == kNotificationDomainAll ||
(message->domain == listener->domain && listener->wants(message->event)))
listener->notifyMe(message);
}
}
bool Listener::remove(Port port)
{
typedef ListenerMap::iterator Iterator;
StLock<Mutex> _(setLock);
pair<Iterator, Iterator> range = listeners.equal_range(port);
if (range.first == range.second)
return false;
assert(range.first != listeners.end());
secinfo("notify", "remove port %d", port.port());
#if !defined(NDEBUG)
for (Iterator it = range.first; it != range.second; it++) {
assert(it->first == port);
secinfo("notify", "%p listener removed", it->second.get());
}
#endif //NDEBUG
listeners.erase(range.first, range.second);
port.destroy();
return true; }
Listener::Notification::Notification(NotificationDomain inDomain,
NotificationEvent inEvent, uint32 seq, const CssmData &inData)
: domain(inDomain), event(inEvent), sequence(seq), data(Allocator::standard(), inData)
{
secinfo("notify", "%p notification created domain 0x%x event %d seq %d",
this, domain, event, sequence);
}
Listener::Notification::~Notification()
{
secinfo("notify", "%p notification done domain 0x%x event %d seq %d",
this, domain, event, sequence);
}
std::string Listener::Notification::description() const {
return SharedMemoryCommon::notificationDescription(domain, event) +
", Seq: " + std::to_string(sequence) + ", Data: " + std::to_string(this->size());
}
bool Listener::JitterBuffer::inSequence(Notification *message)
{
if (message->sequence == mNotifyLast + 1) { mNotifyLast++; return true; } else {
secinfo("notify-jit", "%p out of sequence (last %d got %d); buffering",
message, mNotifyLast, message->sequence);
mBuffer[message->sequence] = message; return false; }
}
RefPointer<Listener::Notification> Listener::JitterBuffer::popNotification()
{
JBuffer::iterator it = mBuffer.find(mNotifyLast + 1); if (it == mBuffer.end())
return NULL; else {
RefPointer<Notification> result = it->second; mBuffer.erase(it); secinfo("notify-jit", "%p retrieved from jitter buffer", result.get());
return result; }
}
bool Listener::testPredicate(const std::function<bool(const Listener& listener)> test) {
StLock<Mutex> _(setLock);
for (ListenerMap::const_iterator it = listeners.begin(); it != listeners.end(); it++) {
if (test(*(it->second)))
return true;
}
return false;
}
SharedMemoryListener::SharedMemoryListener(const char* segmentName, SegmentOffsetType segmentSize, uid_t uid, gid_t gid) :
Listener (kNotificationDomainAll, kNotificationAllEvents),
SharedMemoryServer (segmentName, segmentSize, uid, gid),
mActive (false)
{
if (segmentName == NULL)
{
secerror("Attempted to start securityd with a NULL segmentName");
abort();
}
}
SharedMemoryListener::~SharedMemoryListener ()
{
}
bool SharedMemoryListener::findUID(uid_t uid) {
return Listener::testPredicate([uid](const Listener& listener) -> bool {
try {
const SharedMemoryListener& smlListener = dynamic_cast<const SharedMemoryListener&>(listener);
if (smlListener.mUID == uid)
return true;
}
catch (...) {
return false;
}
return false;
}
);
return false;
}
void SharedMemoryListener::createDefaultSharedMemoryListener(uid_t uid, gid_t gid) {
uid_t fuid = SharedMemoryCommon::fixUID(uid);
if (fuid != 0) { if (!SharedMemoryListener::findUID(fuid)) {
secdebug("MDSPRIVACY","creating SharedMemoryListener for uid/gid: %d/%d", fuid, gid);
#ifndef __clang_analyzer__
new SharedMemoryListener(SharedMemoryCommon::kDefaultSecurityMessagesName, kSharedMemoryPoolSize, uid, gid);
#endif // __clang_analyzer__
}
}
}
uint32 SharedMemoryListener::getRecordType(const CssmData& val) const {
if (val.Length < sizeof(uint32))
return 0;
const uint8 *pv = val.Data;
uint32 value = (pv[0] << 24) + (pv[1] << 16) + (pv[2] << 8) + pv[3];
return value;
}
bool SharedMemoryListener::isTrustEvent(Notification *notification) {
bool trustEvent = false;
switch (notification->event) {
case kSecDefaultChangedEvent:
case kSecKeychainListChangedEvent:
case kSecTrustSettingsChangedEvent:
trustEvent = true;
break;
case kSecAddEvent:
case kSecDeleteEvent:
case kSecUpdateEvent:
{
NameValueDictionary dictionary (notification->data);
const NameValuePair *item = dictionary.FindByName(ITEM_KEY);
if (item && (CSSM_DB_RECORDTYPE)getRecordType(item->Value()) == CSSM_DL_DB_RECORD_X509_CERTIFICATE) {
trustEvent = true;
}
}
break;
default:
break;
}
if (trustEvent) {
uint32_t result = notify_post(kSecServerCertificateTrustNotification);
if (result != NOTIFY_STATUS_OK) {
secdebug("MDSPRIVACY","Certificate trust event notification failed: %d", result);
}
}
secdebug("MDSPRIVACY","[%03d] Event is %s trust event", mUID, trustEvent?"a":"not a");
return trustEvent;
}
bool SharedMemoryListener::needsPrivacyFilter(Notification *notification) {
if (notification->domain == kNotificationDomainPCSC || notification->domain == kNotificationDomainCDSA)
return false;
switch (notification->event) {
case kSecLockEvent: case kSecUnlockEvent: case kSecPasswordChangedEvent: case kSecDefaultChangedEvent:
case kSecDataAccessEvent:
case kSecKeychainListChangedEvent:
case kSecTrustSettingsChangedEvent:
return false;
case kSecAddEvent:
case kSecDeleteEvent:
case kSecUpdateEvent:
break;
}
secdebug("MDSPRIVACY","[%03d] Evaluating event %s", mUID, notification->description().c_str());
NameValueDictionary dictionary (notification->data);
const NameValuePair *item = dictionary.FindByName(ITEM_KEY);
if (!item) {
secdebug("MDSPRIVACY","[%03d] Item event did not contain an item", mUID);
return false;
}
pid_t thisPid = 0;
const NameValuePair *pidRef = dictionary.FindByName(PID_KEY);
if (pidRef != 0) {
thisPid = n2h(*reinterpret_cast<pid_t*>(pidRef->Value().data()));
}
uid_t out_euid = 0;
int rx = SharedMemoryListener::get_process_euid(thisPid, out_euid);
if (rx != 0) {
secdebug("MDSPRIVACY","[%03d] get_process_euid failed (rx=%d), filtering out item", mUID, rx);
return true;
}
if (out_euid == mUID) {
return false; }
if (out_euid == 0) {
CSSM_DB_RECORDTYPE recordType = getRecordType(item->Value());
if (recordType == CSSM_DL_DB_RECORD_X509_CERTIFICATE) {
return false;
}
}
secdebug("MDSPRIVACY","[%03d] Filtering event %s", mUID, notification->description().c_str());
return true;
}
const double kServerWait = 0.005;
void SharedMemoryListener::notifyMe(Notification* notification)
{
const void* data = notification->data.data();
size_t length = notification->data.length();
if (length > 16384) return;
isTrustEvent(notification);
if (needsPrivacyFilter(notification)) {
return; }
secdebug("MDSPRIVACY","[%03d] WriteMessage event %s", mUID, notification->description().c_str());
WriteMessage (notification->domain, notification->event, data, int_cast<size_t, UInt32>(length));
if (!mActive)
{
Server::active().setTimer (this, Time::Interval(kServerWait));
mActive = true;
}
}
void SharedMemoryListener::action ()
{
secinfo("notify", "Posted notification to clients.");
secdebug("MDSPRIVACY","[%03d] Posted notification to clients", mUID);
notify_post (mSegmentName.c_str ());
mActive = false;
}
int SharedMemoryListener::get_process_euid(pid_t pid, uid_t& out_euid) {
struct kinfo_proc proc_info = {};
int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
size_t len = sizeof(struct kinfo_proc);
int ret = sysctl(mib, (sizeof(mib)/sizeof(int)), &proc_info, &len, NULL, 0);
out_euid = -1;
if (ret == 0) {
out_euid = proc_info.kp_eproc.e_ucred.cr_uid;
}
return ret;
}