CVE-2022-32902: Patch One Issue and Introduce Two

A year ago, I discovered a TCC-bypass issue in the system daemon service named com.apple.fontmover. Three months later, Apple addressed it as CVE-2022-32902. After checking how Apple addressed the issue, I found two new issues introduced by patching the issue. I reported them to Apple immediately and waited for about 9 months.

Yesterday, Apple told me that they had addressed the issues in macOS 13.0 and that they will credit me into their Additional Recognitions and no longer assign CVEs. I think it is time to disclose the issues now.

CVE-2022-32902

There is a launch daemon service (/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/Support/fontmover) registered in macOS.

/System/Library/LaunchDaemons/com.apple.fontmover.plist :

{
  "EnablePressuredExit" => 0
  "Label" => "com.apple.fontmover"
  "MachServices" => {
    "com.apple.fontmover" => 1
  }
  "POSIXSpawnType" => "Interactive"
  "ProgramArguments" => [
    0 => "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/Support/fontmover"
    1 => "-d"
  ]
}

The main logic of the service is simple. It listens at a message port named com.apple.fontmover by using the API CFMessagePortCreateLocal:

image-20230606214515663

Next, it handles the client’s requests in the callback function:

image-20230606215657401

This is a little different from the traditional high-level XPC communication methods. But it is similarly simple, an IPC client can create an IPC connection by using the API CFMessagePortCreateRemote and then send the IPC request by using the API CFMessagePortSendRequest.

The issue existed in the handling routine for msgid==2, here is the pseudocode:

    case 2:
      v7 = (UInt8 *)CFDataGetBytePtr(a3);
      v8 = CFDataGetLength(a3);
      Message_Ctor((Message *)message, v7, v8);
      Dict = (CFDictionaryRef)MessageGetDict(message); // Dict(a3) is controllable from the IPC client
      if ( Dict )
      {
          authorization = (CFDataRef)CFDictionaryGetValue(Dict, CFSTR("authorization"));
          if ( authorization )
          {
              if (CFDataGetLength(authorization) == 32 )
              {
                v15 = CFDataGetBytePtr(authorization);
                if ( !(unsigned int)XTAuthenticateAsAdminFromExternalAuthorization(2LL, v15) )
                {
                  files= (CFArrayRef)CFDictionaryGetValue(Dict, CFSTR("files"));
                  if ( files && CFArrayGetCount(files) > 0)
                  {
                      v19 = (CFNumberRef)CFDictionaryGetValue(Dict, CFSTR("destination"));
                      CFNumber_IntValue(v19, &valuePtr);
                      if ( (((_DWORD)valuePtr - 1) & (unsigned int)valuePtr) == 0 && valuePtr != 0 )
                      {
                        dirs = (CFArrayRef)CFCopySearchPathForDirectoriesInDomains(5LL, valuePtr, 0LL);
                        if ( dirs && CFArrayGetCount(dirs) > 0 )
                        {
                            destination = CFArrayGetValueAtIndex(dirs, 0LL);
                            CFURLAppendPath(destination, CFSTR("Fonts"), 1u);
                            if ( destination )
                            {
                              numOfFiles = CFArrayGetCount(files);
                              if ( numOfFiles > 0 )
                              {
                                for ( j = 0LL; j != numOfFiles; ++j )
                                {
                                  file = (CFURLRef)CFArrayGetValueAtIndex(files, j);
                                  CopyFonts(file, destionation); // do copy here!
                                }
                              }
                            }
                            ...

At line 36, it copies the font files to the destionation location (/Library/Fonts). The font file path is controllable by the IPC client, and the destionation path is readable by everyone.

Note that the service has the special FDA (Full Disk Access) entitlement:

	[Key] com.apple.private.tcc.allow
	[Value]
		[Array]
			[String] kTCCServiceSystemPolicyAllFiles

Therefore, an attacker could exploit the issue to steal the user’s privacy files, even they are TCC-protected!

However, there is a validation at line 14: XTAuthenticateAsAdminFromExternalAuthorization. So the IPC client must pass a valid authorization with root privilege.

The exploit code is uploaded to GitHub for research purpose only. It requires the root privilege to run.

Here is the demo video of the exploit.

Patch in macOS 12.6 (2 New Issues Introduced)

Now, it uses a more restricted sandbox profile.

/usr/share/sandbox/fontmoverinternal.sb :

(allow file-read* (extension "com.apple.app-sandbox.read"))
(allow file-read* file-write*
    (subpath "/Library/Fonts")
    (subpath "/Library/Fonts (Removed)")
)
(allow file-issue-extension)
(allow mach-lookup)
...

Only few locations are readable now. But it can issue file extensions freely.

Issue 1: IPC Client Validation Bypass

In order to address the CVE-2022-32902, it validates the signature of the requesting client. Only the system Application (/System/Applications/Font Book.app, com.apple.FontBook) is allowed to request the service:

image-20230606225643766

However, the code signing identifier is easy to bypass :

codesign -f -s - -i com.apple.FontBook /path/to/program

image-20230606225916098

Then the program can bypass the verification and request the service routines as before!

Issue 2: Using Unsafe Deserialisation

Next, in the message handling callback function (msgid==2), it used an unsafe deserialisation implementation:

image-20220915160244940

It could be exploited to execute arbitrary code in the context of the current process.

More details about the exploitation could be referred to an awesome topic in the BH USA 2022:

image-20230606230651400

After getting the code execution in the context of the daemon servcie fontmover, an attacker can issue file extension of an arbitrary TCC-protected file and thus bypass the TCC protection.

Patch again in macOS 13.0

Now, it checks the client’s entitlement by using the audit token as expected:

image-20230606095122493

Next, it deserializes the controllable object safely with the specified class NSSecurityScopedURLWrapper:

image-20230606231159177

Timeline

Date Action
2022-06-15 Initial report sent to Apple
2022-09-12 Apple patched it in macOS 12.6 and assigned CVE-2022-32902
2022-09-16 Two new issues reported to Apple
2022-10-27 Apple asked me whether I can reproduce them in macOS 13.0
2022-10-27 I found the two issues were addressed in macOS 13.0, but I got no credit and asked Apple for the reason.
2022-11-02 Apple requested me to withhold the disclosure of the two issues because not all platforms were patched.
2023-04-27 I asked for an update of the report
2023-05-04 Apple said they were looking into this and will provide me an update soon.
2023-06-05 Apple said they will credit me in the Additional Recognitions, without assigning new CVEs.
2023-06-06 Public disclosure
Written on June 6, 2023