With my previous post I took a look at the SystemPolicy.framework
and how it kept track of 32-bit applications that had been run. In the process of looking into that I ended up looking into the internals of syspolicyd
. Way back in macOS 10.10.5 syspolicyd
was part of the security_systemkeychain source code that Apple releases with each version of macOS. Unfortunately since that time syspolicyd
was moved out of the security_systemkeychain package and closed sourced. This post details the internals of syspolicyd
as it is today in macOS 10.14.x and covers both what services it provides and what clients connect and use its functionality.
Overview
syspolicyd
was originally introduced in macOS 10.7.3 with the Gatekeeper feature. Its original purpose was to act as the centralized daemon for answering Gatekeeper questions. Today it still serves that purpose but its scope has greatly expanded. In addition to assessing applications before running, the daemon also handles authorizing the loading of KEXTs as well as tracking legacy applications that the user has run. In Mojave syspolicyd
has expanded again and is responsible for handling software notarization checks as well. We’ll start with a very high level look at the daemon startup process and then dive deeper into each of syspolicyd
’s subsystems.
Startup
The startup process of the syspolicyd
daemon is not very complex. The entire main function was outlined in my previous article. Here it is in its entirety:
int main() {
KextManagerService *kextManagerService = [[KextManagerService alloc] init];
[kextManagerService registerActivities];
[kextManagerService startXPCService];
ExecManagerService *execManagerService = [[ExecManagerService globalManager] retain];
[execManagerService registerActivities];
[execManagerService startMIGService];
[execManagerService startXPCService];
createSystemPolicyDatabase();
registerSystemPolicyService();
NSRunLoop *mainRunLoop = [[NSRunLoop mainRunLoop] retain];
[mainRunLoop run];
[mainRunLoop release];
[execManagerService release];
[kextManagerService release];
return 0x1;
}
Conceptually you can think of syspolicyd
as being composed on three main subsystems:
When launchd starts up syspolicyd
each subsystem goes through and initializes itself. They set up database connections, XPC services and activities and even a MIG service. Each subsystem is detailed out in the sections below
Gatekeeper
Gatekeeper is a technology that helps ensure you only run software from the app store and trusted developers. It’s responsible for checking the code signing attributes on applications downloaded from the internet.
Services
com.apple.security.syspolicy
Based on the previously open sourced syspolicyd.cpp code, we can easily get a sense of how this XPC service works. Essentially the service checks the incoming XPC message dictionary for what function
is sent over. If the function
sent over is known then the corresponding function implementation is called. Otherwise if the function
isn’t passed or is unknown then an error is returned to the XPC client. The following function
values are currently defined as of macOS Mojave:
Most of these functions call into the Security.framework
to do the real work. Which can be somewhat confusing since we’ll see later that the Security.framework
is one of the primary clients of this service. The framework is set up in such a way that when regular processes call into its APIs it will send an XPC request to this service and when this service runs it will run the PolicyEngine
directly inside of syspolicyd
using the frameworks code.
assess
Calls into SecAssessmentCreate
passing in the kSecAssessmentFlagDirect
flag so that the policy engine runs in process. The PolicyEngine::evaluate
method is then called. This method asks the system for its assessment of a proposed operation. A CFURL is passed in describing the file central to the operation. The operation defaults to assessing whether the file can be executed or not but there are also options for evaluating if software can be installed or a document can be opened.
update
Calls into SecAssessmentCopyUpdate
which will then in turn call into PolicyEngine::update
. This method is responsible for making changes to the system policy configuration. The configuration gets stored in a sqlite3 database in /var/db/SystemPolicy
.
record
This method is called into through SecAssessmentControl
with the operation value being passed in as ui-record-reject-local
. This method will do a check to ensure the caller holds the com.apple.private.assessment.recording
entitlement before calling into PolicyEngine::recordFailure
. This writes the failure out to the /var/db/.LastGKReject
file.
cancel
Cancels an in progress assessment request.
check-dev-id
This method will check whether Gatekeeper allows apps from identified developers. SecAssessmentControl
is called with the operation ui-get-devid-local
passed in. This will in turn run the following query on the /var/db/SystemPolicy
database.
SELECT disabled
FROM authority
WHERE label = 'Developer ID';
Even though there can be multiple records returned from this query, the PolicyEngine
simply returns false if the value of disabled is 1 otherwise it returns true.
check-notarized
This is similar to check-dev-id
. It will check whether Gatekeeper allows notarized applications to run. This again calls SecAssessmentControl
this time passing in the ui-get-notarized-local
option. This in turn runs the following query:
SELECT disabled
FROM authority
WHERE label = 'Notarized Developer ID';
If disabled is 1 then the PolicyEngine
returns false otherwise it returns true.
ticket-register
When the system is validating binaries it will attempt to register stapled notarization tickets it finds with the system. This service function ultimately handles those calls. The data passed in gets turned into a GKTicket
object and then validated. If the ticket is validated then the following query is run to insert the information into the /var/db/SystemPolicyConfiguration/Tickets
sqlite3 database.
INSERT INTO tickets (hash, hash_type, timestamp, flags)
VALUES (?1, ?2, ?3, ?4)
ticket-lookup
This function is responsible for retrieving ticket information. It can either retrieve the information from the locally stored copy in /var/db/SystemPolicyConfiguration/Tickets
or it can query a web service at Apple to lookup that information. In some cases when the Security.framework
wants to check if a ticket is revoked it will force the lookup in the web service provided by Apple. Here’s an example of the query checking for a local copy of the ticket:
SELECT tickets.flags
FROM hashes INNER JOIN tickets ON hashes.ticket_id = tickets.id
WHERE hashes.hash = ?1 AND hashes.hash_type = ?2
And here’s an example of a lookup of a ticket in the web service:
$ curl --data '{"records":[{"recordName":"2/2/4dca04a3465b95866423323d7f3e1e31ad3ac0ef"}]}' \
https://api.apple-cloudkit.com/database/1/com.apple.gk.ticket-delivery/production/public/records/lookup
{
"records" : [ {
"recordName" : "2/2/4dca04a3465b95866423323d7f3e1e31ad3ac0ef",
"recordType" : "DeveloperIDTicket",
"fields" : {
"signedTicket" : {
"value" : "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",
"type" : "BYTES"
}
},
"pluginFields" : { },
"recordChangeTag" : "jnz4hhh6",
"created" : {
"timestamp" : 1541108961982,
"userRecordName" : "_d28c74d190a3782e89496b0a13437fef",
"deviceID" : "2"
},
"modified" : {
"timestamp" : 1541307799566,
"userRecordName" : "_d28c74d190a3782e89496b0a13437fef",
"deviceID" : "2"
},
"deleted" : false
} ]
}
Clients
Security.framework
The Security.framework
is the primary client of this service with all of its SecAssessment APIs. As mentioned above if you call into these APIs outside of syspolicyd
then the framework will use XPC to request the information from syspolicyd
. Here’s a short overview of the SecAssessment APIs:
API | Description |
---|---|
SecAssessmentCreate | Ask the system for its assessment of a proposed operation. |
SecAssessmentCopyUpdate | Make changes to the system policy configuration. |
SecAssessmentControl | Miscellaneous system policy operations. |
SecAssessmentTicketRegister | Registering stapled ticket with system. |
SecAssessmentTicketLookup | Check whether software is notarized. |
Luckily the Security.framework
is still open sourced, so you can look more into the internals of these APIs here:
https://opensource.apple.com/tarballs/Security/Security-58286.220.15.tar.gz
Additionally if you’re looking for a short sample of how the Security.framework
makes the XPC calls to call into the Gatekeeper XPC service, I have some short example code here.
This means that if we really want to find out what clients use this service we should search the system for users of the SecAssessment APIs. I used ripgrep on a Mojave system to search for binaries that used SecAssessment
and found the following items:
- /Applications/Utilities/Script Editor.app/Contents/MacOS/Script Editor
- /System/Library/CoreServices/CoreServicesUIAgent.app/Contents/MacOS/CoreServicesUIAgent
- /System/Library/CoreServices/Installer.app/Contents/MacOS/Installer
- /System/Library/Frameworks/Automator.framework/Versions/A/Automator
- /System/Library/Frameworks/JavaVM.framework/Versions/A/Frameworks/JavaRuntimeSupport.framework/Versions/A/JavaRuntimeSupport
- /System/Library/Frameworks/Security.framework/Versions/A/Security
- /System/Library/PreferencePanes/Security.prefPane/Contents/MacOS/Security
- /System/Library/PrivateFrameworks/ConfigurationProfiles.framework/XPCServices/SystemPolicyService.xpc/Contents/MacOS/SystemPolicyService
- /System/Library/PrivateFrameworks/PackageKit.framework/Versions/A/PackageKit
- /System/Library/PrivateFrameworks/SystemAdministration.framework/XPCServices/writeconfig.xpc/Contents/MacOS/writeconfig
- /System/Library/PrivateFrameworks/XprotectFramework.framework/Versions/A/XPCServices/XprotectService.xpc/Contents/MacOS/XprotectService
- /usr/libexec/mdmclient
- /usr/libexec/syspolicyd
- /usr/sbin/spctl
KextManager
The KextManager subsystem plays a role in user-approved kernel extension loading. The following Objective-C classes make up the core of the KextManager subsystem:
- KextManagerService
- KextManagerPolicy
- KextManagerDatabase
- KextManagerUI
The diagram below shows the flow of a typical request to load a kernel extension all the way to the point of notifying the user about it.
Services
com.apple.security.syspolicy.kext
This XPC service is defined in the syspolicyd
launchd plist. It’s the primary way clients can check on and update what kernel extensions have been approved to load. Since it’s written in Objective-C it makes use of the NSXPCInterface
and NSXPCListener
classes. The NSXPCInterface
is defined to implement the following protocol:
@protocol KernelExtensionManager
- (void)copyPendingApprovalsWithReply:(void (^)(NSArray *, NSError *))arg1;
- (void)copyCurrentPolicyWithReply:(void (^)(NSArray *, NSError *))arg1;
- (void)removeMDMPayload:(NSString *)arg1 withReply:(void (^)(BOOL, NSError *))arg2;
- (void)installMDMPayload:(NSString *)arg1 withTeams:(NSArray *)arg2 andExtensions:(NSDictionary *)arg3 withReply:(void (^)(BOOL, NSError *))arg4;
- (void)setUserApprovalAllowed:(BOOL)arg1 withReply:(void (^)(BOOL, NSError *))arg2;
- (void)updatePolicyItems:(NSArray *)arg1 withReply:(void (^)(BOOL, NSError *))arg2;
- (void)teamIdentifierIsAllowed:(NSString *)arg1 withReply:(void (^)(BOOL, NSError *))arg2;
- (void)canLoadKernelExtensionInCache:(NSString *)arg1 withReply:(void (^)(BOOL, NSError *))arg2;
- (void)canLoadKernelExtension:(NSString *)arg1 withReply:(void (^)(BOOL, NSError *))arg2;
@end
The KextManagerService
class is then responsible for implementing this protocol. Since KEXT loading is a sensitive system operation there are a handful of different permissions and entitlements needed to call into this XPC service. I’ll break down my description of the services functionality by the entitlements needed to call into the different methods.
No entitlements needed
-
copyPendingApprovalsWithReply:
This method will retrieve the list of KEXTs that attempted to load but have not been approved yet from the/var/db/SystemPolicyConfiguration/KextPolicy
sqlite3 database. -
copyCurrentPolicyWithReply:
Provides a list of all KEXTs stored in the/var/db/SystemPolicyConfiguration/KextPolicy
database. -
teamIdentifierIsAllowed:withReply:
Determines if a specific team identifier is allowed by the/var/db/SystemPolicyConfiguration/KextPolicy
database.
com.apple.rootless.kext-secure-management
canLoadKernelExtensionInCache:withReply:
canLoadKernelExtension:withReply:
Both of these methods end up doing the same thing. They call into theKextManagerPolicy
object calling thecanLoadKernelExtensionAtURL:isCacheLoad:
selector. These are the main methods responsible for determining if a KEXT can be loaded. It will check if the KEXT is allowed by MDM or approved by the user.
com.apple.private.security.syspolicy.kext-policy-management
updatePolicyItems:withReply:
This method gets used when a KEXT is actually approved to update the record in the/var/db/SystemPolicyConfiguration/KextPolicy
database.
com.apple.private.security.syspolicy.mdm-kext-policy-management
-
setUserApprovalAllowed:withReply:
Allows MDM to determine whether a user can approve a KEXT from the System Preferences or only the MDM whitelist is allowed. -
installMDMPayload:withTeams:andExtensions:withReply:
Parses the MDM KEXT policy payload and updates thekext_policy_mdm
table in the/var/db/SystemPolicyConfiguration/KextPolicy
database. -
removeMDMPayload:withReply:
Removes an existing MDM KEXT Policy from the/var/db/SystemPolicyConfiguration/KextPolicy
database.
Clients
If you attempt to find clients of this service by searching for users of the KernelExtensionManager
protocol you’re only going to find a single client, the SystemPolicy.framework
.
SystemPolicy.framework
So in order to find the true clients we need to search the system for the binaries making use of the SPKernelExtensionPolicy
class from the SystemPolicy.framework
. Doing that results in the following list:
- /System/Library/PreferencePanes/Security.prefPane/Contents/MacOS/Security
- /System/Library/PrivateFrameworks/ConfigurationProfiles.framework/XPCServices/ExecutionPolicyService.xpc/Contents/MacOS/ExecutionPolicyService
- /System/Library/SystemProfiler/SPDisabledApplicationsReporter.spreporter/Contents/MacOS/SPDisabledApplicationsReporter
- /sbin/kextload
- /usr/bin/kextutil
- /usr/libexec/amfid
- /usr/libexec/kextd
- /usr/libexec/syspolicyd
- /usr/sbin/kextcache
- /usr/sbin/spctl
ExecManager
As opposed to the KextManager
subsystem the ExecManager
subsystem actually has multiple services that it provides. One is an XPC service that has a similar architecture to the KextManagerService
and the other is a MIG service that provides communication with the kernel. The core of the subsystem is made up of the following Objective-C classes:
- ExecManagerService
- ExecManagerPolicy
- ExecManagerDatabase
Unlike the KextManagerPolicy
which has a helper class to notify the user through the UI, the ExecManagerPolicy
class coordinates with the CoreServices.framework
to notify the user of the launch of deprecated 32-bit executables.
Services
com.apple.security.AppleSystemPolicy.mig
Looking at the launchd plist for syspolicyd
we see that this service is actually a MachService
. Additionally it’s defined to provide its service using HostSpecialPort
29. Looking at the ExecManagerService
class we see that there’s a method called startMIGService
that is responsible for calling bootstrap_check_in
to notify launchd the service is available. I created a short Hopper script called MIG Detect.py to find and label the different MIG messages that were being used.
0x10003ad70: MIG Subsystem 18600: 4 messages
0x10003ad98: MIG msg 18600
0x10003adc0: MIG msg 18601
0x10003ade8: MIG msg 18602
0x10003ae10: MIG msg 18603
notify_32bit_exec
notify_32bit_mmap
Both of these functions take similar actions. They call into the ExecManagerService
and call the notify32bitExecForUser
method. This will in turn save the information into the /var/db/SystemPolicyConfiguration/ExecPolicy
sqlite3 database in the legacy_exec_history_v4
table.
log_platform_binary
This method will call into the ExecManagerService
and call the logOldPlatformBinary
method. This will in turn create a AWDSystemPolicyOldPlatformBinary
object and then call AWDPostMetric
which is actually part of the private WirelessDiagnostics.framework
. This private framework apparently collects more than just wireless diagnostics. Additionally a record gets added into the old_platform_cache
table in the /var/db/SystemPolicyConfiguration/ExecPolicy
database noting that the metric has been logged.
log_executable
This method will call into the ExecManagerService
and call the logExecutable
method. This eventually saves the information into the scan_targets_v2
table in the /var/db/SystemPolicyConfiguration/ExecPolicy
database. These records gets used from periodic XPC activities to measure performance metrics.
com.apple.security.syspolicy.exec
This XPC service is defined in the syspolicyd
launchd plist. Similar to the KextManager
subsystem it’s an Objective-C XPC service. The NSXPCInterface
is defined to implement the following interface:
@protocol ExecManager
- (void)copyLegacyExecutionHistoryWithReply:(void (^)(NSArray *, NSError *))arg1;
@end
There’s not much to this XPC service. It simply provides a way for clients to retrieve a list of 32-bit applications that have been executed. No special entitlements are needed to call into this service.
Clients
AppleSystemPolicy.kext
As far as I could tell this is the only client of the syspolicyd
MIG service. Since syspolicyd
provides this as a host special port the kernel extension does the following when it’s main IOService
class is initialized:
bool AppleSystemPolicy::init(OSDictionary *dict)
{
kern_return_t kr;
if (OSCompareAndSwapPtr(nullptr, this, &AppleSystemPolicy::_instance)) {
// Initializes instance variables
kr = host_get_special_port(host_priv_self(), HOST_LOCAL_NODE, HOST_SYSPOLICYD_PORT, &daemonPort);
if (kr != KERN_SUCCESS) {
os_log("Unable to retrieve syspolicyd port, failing: %d", kr);
return false;
}
// Calls sysctl_register_oid to share performance stats with user space
initPerformanceInfo();
if (!super::init(dict)) {
IOLog("Failed to initilize parent class");
return false;
}
return true;
}
else {
IOLog("AppleSystemPolicy already initialized!");
return false;
}
}
The kernel extension then has it’s start method called which does the following:
bool AppleSystemPolicy::start(IOService *provider)
{
registerMACPolicy();
if (!super::start(provider)) {
os_log("Failed to start AppleSystemPolicy parent");
return false;
}
else {
os_log("AppleSystemPolicy has been successfully started");
return true;
}
}
The following are the MACF operations that get hooked:
- mpo_proc_notify_exec_complete
- mpo_file_check_library_validation
- mpo_file_check_mmap
All three of these hooks are simply used to inspect executables that are about to run or libraries that will be mapped into memory and check if they are 32-bit. If they are then a MIG call is made to inform syspolicyd
of the information. If you want to see an example of what the MIG call itself looks like you can take a look at this small sample app I created called aspmig.c. It manually calls the notify_32bit_exec
MIG service.
If you want to look at the statistics of how long the MACF hooks take to run you can use the sysctl
command line utility.
$ sysctl -a | grep asp
security.mac.asp.cache_entry_count: 3417
security.mac.asp.cache_allocation_count: 4104
security.mac.asp.cache_release_count: 687
security.mac.asp.exec_hook_time: 616061
security.mac.asp.exec_hook_count: 252162
security.mac.asp.library_hook_time: 2385933
security.mac.asp.library_hook_count: 1051821
SystemPolicy.framework
Similar to the KextManager
subsystem. The SystemPolicy.framework
is one of the main clients of the ExecManager
service. You need to search for the SPExecutionPolicy
class to find true clients of the XPC service. Doing that results in only a single client:
- /System/Library/SystemProfiler/SPLegacySoftwareReporter.spreporter/Contents/MacOS/SPLegacySoftwareReporter
launchd
The launchd
plist for syspolicyd
is located at /System/Library/LaunchDaemons/com.apple.security.syspolicy.plist
. In addition to all the services we already covered there are a handful of com.apple.xpc.activity
entries in the plist. You can read more details about what XPC Activities are in the Energy Efficiency Guide for Mac Apps. I am a huge fan of XPC Activities. I think most 3rd party daemons do not make use of this functionality and if they would it would greatly improve their efficiency on the Mac. Taking this approach lets launchd
make intelligent decision about when to launch certain background activities taking into account things like whether the device is on battery or not or if it can batch up certain requests to be more efficient. The list below just summarizes the ones defined and what they do but does not dive into them in any detail.
com.apple.security.syspolicy.report
<key>com.apple.security.syspolicy.report</key>
<dict>
<key>Delay</key>
<integer>86400</integer>
<key>GracePeriod</key>
<integer>43200</integer>
<key>Priority</key>
<string>Maintenance</string>
<key>AllowBattery</key>
<false/>
<key>Repeating</key>
<true/>
</dict>
This job runs once a day on an inexact schedule and never on battery. This gives launchd
flexibility to batch up this job when the computer is on power and awake. The job makes use of the ExecutableMeasurementWorker
class and runs the following query in the /var/db/SystemPolicyConfiguration/ExecPolicy
database:
SELECT is_signed, file_identifier, bundle_identifier, bundle_version, team_identifier,
signing_identifier, cdhash, main_executable_hash, executable_timestamp, file_size,
is_library, is_used, responsible_file_identifier, is_valid, is_quarantined
FROM executable_measurements_v2
WHERE strftime('%W', reported_timestamp, 'unixepoch')/2 < strftime('%W', 'now')/2
ORDER BY reported_timestamp ASC, timestamp ASC
It then calls into the private WirelessDiagnostics.framework
and calls AWDPostMetric
to log the information.
com.apple.security.syspolicy.find.bundles
<key>com.apple.security.syspolicy.find.bundles</key>
<dict>
<key>Priority</key>
<string>Maintenance</string>
<key>AllowBattery</key>
<false/>
<key>Interval</key>
<integer>604800</integer>
<key>Repeating</key>
<true/>
</dict>
This job runs every 7 days as long as the machine isn’t on battery power. It calls into the BundleFinder
class calling the searchKnownBundleLocations
method. I didn’t look into this in detail but it looks like it attempts to find bundles on the system that it wants to scan and then adds the records into the scan_targets_v2
table in the /var/db/SystemPolicyConfiguration/ExecPolicy
database.
com.apple.security.syspolicy.measure
<key>com.apple.security.syspolicy.measure</key>
<dict>
<key>Priority</key>
<string>Maintenance</string>
<key>AllowBattery</key>
<false/>
<key>Interval</key>
<integer>259200</integer>
<key>Repeating</key>
<true/>
<key>CPUIntensive</key>
<true/>
<key>PowerNap</key>
<true/>
</dict>
This job runs every 3 days and isn’t allowed to run on battery. There’s not good documentation on all of launchd
’s keys but I would guess that the CPUIntensive
flag is an indicator to launchd
that this job might use a lot of CPU. The PowerNap
key might indicate that the job can run while the machine is asleep during other PowerNap work periods. This job again makes use of the ExecutableMeasurementWorker
class this time calling into the performMeasurementsWithCancelCheck
method. This will in turn run the following sql query:
SELECT path, responsible_path, is_used, is_library
FROM scan_targets_v2
WHERE strftime('%W', measured_timestamp, 'unixepoch')/2 < strftime('%W', 'now')/2 AND
deferral_count < 5
ORDER BY measured_timestamp ASC, timestamp ASC
Based on the scan targets found it looks like the job will then attempt to call SecStaticCodeCreateWithPath
to create a SecStaticCode
object and pull out some information around the measured run time of the binary.
com.apple.security.syspolicy.kext.mt
<key>com.apple.security.syspolicy.kext.mt</key>
<dict>
<key>Delay</key>
<real>518400</real>
<key>GracePeriod</key>
<real>43200</real>
<key>Priority</key>
<string>Maintenance</string>
<key>Repeating</key>
<true/>
</dict>
This job runs every 6 days on an inexact schedule. It calls into the KextManagerPolicy
class calling the logPolicyState
method. This job will retrieve the current list of KEXTs from the /var/db/SystemPolicyConfiguration/KextPolicy
database and then for each one attempt to locate it on the file system and eventually call into the SPMessageTracer
class calling the logPolicyItem
method. This in turn calls into the msgtracer_log_with_keys
function which is provided by the libDiagnosticMessagesClient.dylib
. Based on the name of this job and the initial KextManagerPolicy
method called it seems to simply be logging the current state of approved KEXTs.
com.apple.security.syspolicy.rearm
<key>com.apple.security.syspolicy.rearm</key>
<dict>
<key>Delay</key>
<integer>86400</integer>
<key>GracePeriod</key>
<integer>3600</integer>
<key>Priority</key>
<string>Maintenance</string>
<key>Repeating</key>
<true/>
</dict>
This job runs once a day. Depending on whether Gatekeeper is enabled and some other internal checks in the Security.framework
this job will re-enable Gatekeeper if it has been disabled.
Miscellaneous
Entitlements
When reviewing a system daemon I’m always interested to see what entitlements it has itself. In the case of syspolicyd
it has the following entitlements.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.private.iokit.nvram-panicmedic</key>
<true/>
<key>com.apple.private.managedclient.configurationprofiles</key>
<true/>
<key>com.apple.private.security.storage.SystemPolicyConfiguration</key>
<true/>
<key>com.apple.private.storagekitd.info</key>
<true/>
<key>com.apple.private.syspolicy.perform-evaluations</key>
<true/>
<key>com.apple.rootless.storage.SystemPolicyConfiguration</key>
<true/>
</dict>
</plist>
Nothing really surprising here based on everything described above. It has entitlements that let it access and update the SIP restricted configuration databases for the various services as well as one related to MDM. I’m not sure what the com.apple.private.iokit.nvram-panicmedic
but I think syspolicyd
is the only binary on the system with this entitlement.
Defaults
A lot of system daemons make use of the macOS defaults system to store certain configuration options for itself. Below are the various defaults values I found while reviewing syspolicyd:
com.apple.security.syspolicy.KextPolicyForceEnable
This system default gets used in the KextManagerPolicyClass
in the canLoadKernelExtensionAtURL:isCacheLoad:
method. It checks if the value is set and seems to be a way to potentially force the KextManager
subsystem on even if other things like SIP is disabled.
com.apple.security.syspolicy.ExecutableMeasurementDisable
Controls whether or not the com.apple.security.syspolicy.report
XPC activity job runs.
com.apple.security.syspolicy.ExecutableMeasurementDisableFullValidation
Controls if full validation is done when performing a measurement.
com.apple.security.syspolicy.ExecutableMeasurementDisableCalculateFMJ
Whether code signing hash should be calculated during a measurement.
com.apple.security.GKAutoRearm
If the key is present and has a value of YES, Gatekeeper will turn itself on automatically if it’s disabled. If the key is present and has a value of NO, Gatekeeper settings will not change.
Revision History
What follows is a release history of syspolicyd
pieced together from the previously open sourced security_systemkeychain code as well as looking through the closed source binary on different versions of macOS. Aside from macOS 10.7 I did not go through each minor OS revision only the most recent version of each major OS release. Where possible I’ve included links to the source code for each version.
macOS 10.7.3
- First release of Gatekeeper and
syspolicyd
. - Only supported assess functionality.
macOS 10.7.5
- Added the update functionality.
security_systemkeychain-55119.2
macOS 10.8.4
- No major changes.
security_systemkeychain-55120.7
macOS 10.9.5
- Added the record functionality.
security_systemkeychain-55191.2
macOS 10.10.5
- Last version of
syspolicyd
that was open sourced. - Added cancel functionality for long running assessments.
macOS 10.11.6
- No major changes.
syspolicyd-9
macOS 10.12.6
- Added check-dev-id functionality.
syspolicyd-12.50.1
macOS 10.13.6
- KextManager subsystem introduced along with user approved KEXT loading.
- ExecManager subsystem introduced tracking 32-bit application launches.
- AppleSystemPolicy.kext introduced along with ExecManager.
- SystemPolicy.framework introduced so clients can query KextManager and ExecManager subsystems.
syspolicyd-45.60.3
macOS 10.14.x
- ExecManager was expanded to more thoroughly track 32-but application launches.
- Gatekeeper notary service is introduced.
- Added check-notarized functionality.
- Added ticket-register functionality.
- Added ticket-lookup functionality.
syspolicyd-45.230.2