iOS14.8: Patch CVE-2021-1740 again silently

As well known, iOS14.8 patched two 0 days in the wild, one of which is the pegasus 0-click vulnerability. You can get the root cause and more interesting findings by reading my analysis from here.

By the way, it also patched CVE-2021-1740 again silently, no CVE assigned (Updated: iOS15 released on Sep, 20th and it seems that CVE-2021-30855 matches this new patch).

When I study the DefCon topic Caught you - reveal and exploit IPC logic bugs inside Apple , I found the patch of CVE-2021-1740 is still vulnerable.

The DefCon slides has already given the details of the root causes, and also shared 2 exploitations.

The first exploitation (Arbitrary File Read) :

image-20210915145918426

The second exploitation (Arbitrary File Write) :

image-20210915145944800

The old patch (Still vulnerable) :

image-20210915144258578

  • Yes, it can stop the second exploitation. We have no time window to replace the target temporary file with a symbolic link, because we can’t know the temporary file name before the API clonefileat.
  • Unfortunately, it cannot stop the first exploitation. If the plist_file is replaced with a symbolic link before the API clonefileat, then the temporary file with a random name could still be cloned. Therefore, the arbitrary file read issue still exists because the temporary file is accessible by common users.

Proof Of Concept

It is a TOCTOU issue, so I made a quick POC through my debugger :

  • Attach my debugger to the cfprefsd process, set a breakpoint before the API call clonefileat in the function -[CFPDSource cloneAndOpenPropertyListWithoutDrainingPendingChangesOrValidatingPlist]

    image-20210915151138304

  • Create a normal plist file (whose size larger than 0x100000 = 1M) at /tmp directory.

  • Fire a XPC request to the cfprefsd, for reading the preference value in the plist file.

  • Then it hit my breakpoint, it is time to replace the plist file with a symbolic link now (point to a privacy file).

  • Step over the API clonefileat, we can see the privacy file was cloned to a file with a random name as expected. The temporary file is readable by common users even it is a random name !

  • Before the unlink of the temporary file, we still have a time window to backup it.

  • How can we know the random file name ? There could be many methods to get it. The simplest way could be enumerating the files inside the directory :

    chdir("/tmp/test");
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            DIR *dp;
            struct dirent *ep;
            int cnt=0;
            while (1) {
                dp = opendir ("./");
                while ((ep = readdir (dp))) {
                    if (strlen(ep->d_name) == 21) { //strlen(abc.plist.cfp.XXXXXXX)==21
                        copyfile(ep->d_name, "secret_backup", 0, COPYFILE_ALL);
                        printf("Got it:%s.\n", ep->d_name);
                        goto END;
                    }
                }
                closedir (dp);
                cnt++;
            }
        END:
            printf("done with %d loops.\n", cnt);
    });
    

New fix in iOS14.8

image-20210915152721001

We can see it uses API fclonefileat now, and the src_fd is returned by API openat, with flag=0x100 (O_NOFOLLOW) to stop following the symlinks attack.

Written on September 15, 2021