01 Dec 24

TCC and the macOS Platform Sandbox Policy

Background

What is TCC?

TCC is a subsystem on macOS that is responsible for managing which applications a user has permitted to access certain resources. Its full name is "Transparency, Consent and Control". If you've ever seen an "Application" would like to access the camera prompt… that's TCC.

TCC is used to gate access to resources that Apple considers to be sensitive. Protected resources include:

  • Hardware devices such as the camera and microphone
  • Location services
  • A user's photos, contacts, calendar, or reminders
  • Files in a user's desktop, downloads or documents folders
  • Data managed by other third-party applications

TCC permissions are mediated by the tccd process that runs as part of macOS. System frameworks that access sensitive resources use private APIs, such as TCCAccessRequest to determine whether they have permission to access the resource. The API performs an interprocess call to tccd, which will trigger a prompt if it is the first time the application has attempted to access that class of resource. Otherwise, it will return the stored permission decision.

What is the Platform Sandbox Policy?

For background about what sandboxing is on macOS and how it works, see Sandboxing on macOS.

The Platform Sandbox Policy is a sandbox policy that is applied to all processes running on macOS. It is applied transparently to all processes on the system, irrespective of whether they are explicitly sandboxed.

The Platform Sandbox Policy implements one part of System Integrity Protection on macOS. It defines and enforces the restrictions on access to the file system, Mach bootstrap names, IOKit devices, and other resources. Amongst other things, the platform sandbox policy uses process attributes (such as signing identity, bundle identifier, and entitlements) to allow specific applications to bypass restrictions that System Integrity Protection would typically apply to them. This allows applications that are part of macOS to provide system functionality, such as app installation and software updates, that would otherwise be prohibited by System Integrity Protection.

(with user-approval …)

One aspect of TCC that most existing analyses of it miss is that the Platform Sandbox Policy also backstops TCC. The sandbox kernel extension supports triggering TCC prompts when a program accesses specific resources, rather than being limited to merely allowing or denying the access, and the platform sandbox policy makes use of this facility

Specifically, allow actions in the platform sandbox policy can have a (with user-approval "<type>") modifier attached to them. This triggers an up-call from the Sandbox kernel extension to the sandboxd user-space helper asking for TCC approval of the specified type. sandboxd translates this into a call to tccd, much like a system framework using TCCAccessRequest to verify that the calling application is permitted to access a given resource.

A "simple" example

Access to the computer's camera is gated behind the kTCCServiceCamera TCC policy. Frameworks that provide access to the camera, such as AVFoundation, explicitly call TCCAccessRequest(kTCCServiceCamera, …) to ensure that the application is permitted to access the camera. But as the camera is a hardware device, a sufficiently motivated application could access it directly via the IOKit framework. To safeguard against this, the Platform Sandbox Profile has a policy in place for iokit-open-user-client operations that will trigger a TCC prompt if a camera device is accessed directly via IOKit:

(allow iokit-open-user-client
  (with user-approval "kTCCServiceCamera")
  (require-all
    (process-attribute is-sandcastle-constrained)
    (require-any
      (require-all
        (iokit-registry-entry-class "IOFireWireAVCUserClient")
        (require-any
          (require-not
            (signing-identifier "com.apple.AVCAssistant"))
          (require-not
            (process-attribute is-platform-binary))))
      (require-all
        (iokit-registry-entry-class "IOUSBInterfaceUserClientV2")
        (iokit-usb-interface-class kUSBVideoInterfaceClass)
        (require-any
          (require-not
            (process-attribute is-platform-binary))
          (require-not
            (signing-identifier "com.apple.VDCAssistant"))))
      (require-all
        (require-not
          (%entitlement-is-bool-true "com.apple.camera.iokit-user-access"))
        (iokit-registry-entry-class "AppleCamInUserClient")))))

This triggers a kTCCServiceCamera prompt for any access to IOFireWireAVCUserClient, IOUSBInterfaceUserClientV2 with class kUSBVideoInterfaceClass, and AppleCamInUserClient. Platform binaries and binaries with certain entitlements or identifiers are excluded from the prompting.

Storage classes

One of the key attributes used within the Platform Sandbox Policy is the concept of the storage class of a file system object. This is a way of classifying a given file system object as containing some type of data that may need special attention.

File system objects are tracked in the sandbox kernel extension as kernel vnode objects. A given vnode is assigned to exactly one storage class at a time, though the storage class it is assigned to can change. The sandbox kernel extension caches the mapping from vnode objects to storage class to avoid recomputing them. The cache is invalidated in response to certain events that could cause the mapping to change.

Storage classes are assigned by the Platform Sandbox Policy. The special storage-class-map sandbox operation is used along with the (with assign-storage-class "<class>") action modifier to determine which storage class should be assigned to a given file system object. Within this portion of the policy, the same filter operations that are applicable to file system operations are available, along with predicates involving process or system attributes.

There are around 130 storage classes defined by the Platform Sandbox Policy as of macOS 15.1. Most of the storage classes describe data as belonging to a specific application or framework (CloudKit, FaceTime, Safari, and many others), while a handful correspond directly to TCC policies (for instance, kTCCServiceAddressBook, kTCCServiceSystemPolicyAppBundles, kTCCServiceSsytemPolicySysAdminFiles). You can see the complete list of storage classes here.

TCC prompting based on storage classes

Much like the iokit-open-user-client / kTCCServiceCamera case presented above, file system operations consider a combination of path, storage class, and process attributes to determine whether an operation should result in a TCC prompt.

A sampling of storage classes

kTCCServiceSystemPolicyNetworkVolumes

(file-attribute local-filesystem)

kTCCServiceSystemPolicyAppBundles

(file-attribute app-bundle)

kTCCServiceSystemPolicyDownloadsFolder

(path (subpath "${any_user_home}/downloads"))

kTCCServiceSystemPolicySysAdminFiles

(path
  "/library/application support/apple/remote desktop/remotemanagement.launchd"
  "/library/preferences/com.apple.security.smartcard.plist"
  "/library/preferences/directoryservice/directoryservice.plist"
  "/library/preferences/systemconfiguration/com.apple.smb.server.plist"
  "/private/etc/auto_home"
  "/private/etc/auto_master"
  "/private/etc/autofs.conf"
  "/private/etc/crontab"
  "/private/etc/exports"
  "/private/etc/master.passwd"
  "/private/etc/passwd"
  "/private/etc/sudo.conf"
  "/usr/lib/cron"
  (prefix "/private/etc/rc.")
  (subpath "/library/directoryservices/plugins")
  (subpath "/library/perl")
  (subpath "/library/preferences/logging")
  (subpath "/private/etc/pam.d")
  (subpath "/private/etc/postfix")
  (subpath "/private/var/at")
  (subpath "/private/var/db/com.apple.xpc.launchd"))

Safari

(path
  (subpath "${any_user_home}/library/caches/com.apple.safari")
  (subpath "${any_user_home}/library/caches/com.apple.safari.safebrowsing")
  (subpath "${any_user_home}/library/caches/com.apple.safaridavclient")
  (subpath "${any_user_home}/library/caches/com.apple.safaritechnologypreview")
  (subpath "${any_user_home}/library/containers/com.apple.safari")
  (subpath "${any_user_home}/library/containers/com.apple.safari.webapp")
  (subpath
    "${any_user_home}/library/containers/com.apple.safaritechnologypreview")
  (subpath "${any_user_home}/library/safari")
  (subpath "${any_user_home}/library/safaritechnologypreview"))