Return of the iOS sandbox escape: lightspeed's back in the race!!

Written by Luca Moro - 29/05/2020 - in Exploit - Download
Last week-end a new version of the iOS jailbreak unc0ver1 was released with the support of the latest iOS 13.5.

Since iOS 8 in 2014, this is the first jailbreak using a 0-day vulnerability, a vulnerability still unknown to Apple at the time of the release, to break iPhone security measures. To keep this vulnerability secret, the jailbreak is heavily obfuscated and protected against dynamic inspection.

However, since this vulnerability is not exactly new to us and since the cat is out of the bag, now seems a good time to write about it :).

TL;DR Disclosure of an old new XNU kernel bug that was reintroduced with iOS 13

Quick recap of the situation

During late 2018, we published the details about a XNU kernel vulnerability that we dubbed lightspeed. This vulnerability started as a racy UaF in the syscall lio_listio that allows the liberation of a kernel object twice. You can read all the details about it in our post.

This vulnerability was patched early in iOS 12 and 11.4.1 was the last vulnerable iOS version. We were wondering if someone would make something out of it, and were thrilled to see that both the JakeBlair420 team (@s1guza, @stek29, @sparkey, @littlelailo) and @tihmstar1 were successful in doing so.

So when iOS 12 came out this vulnerability died and the lio_listio implementation was correct, right? Well, not really... In the blogpost we explained that, while the issue was fixed, a memory leak was introduced, and it was now possible to force the kernel to panic. As the conclusion of the blogpost we were wondering if Apple would fix it or not.

Apple cares about mem leaks!

We don't know if the XNU developers read the Synacktiv blog, but it turned out that they were bothered by the memory leak, and they even opened a radar issue about it. So they fixed it during the early version of iOS 13. And here is the fix from the xnu-6153.4.3 sources (that has not changed since).


int lio_listio(proc_t p, struct lio_listio_args *uap, int *retval )

{

    // [...]

    for (i = 0; i < uap->nent; i++) {

        // [...]

        aio_proc_lock_spin(p);

        // [...]

        lck_mtx_convert_spin(aio_proc_mutex(p));

        aio_enqueue_work(p, entryp, 1); // I/O enqueuing 

        aio_proc_unlock(p);

        // [...]

    }

    // [...]



    aio_proc_lock_spin(p);

    switch (uap->mode) {

        // [...]

    case LIO_NOWAIT:

        /* If no IOs were issued must free it (rdar://problem/45717887) */

        if (lio_context->io_issued == 0) {

            free_context = TRUE;

        }

        break;

    }

    aio_proc_unlock(p);

    // [...]



    if (free_context) {

        free_lio_context(lio_context);

    }

    // [...]

    return call_result;

} /* lio_listio */

We observe that if no I/O were issued in the LIO_NOWAIT case (lio_context->io_issued == 0), the context is now freed again, preventing the allocation to remain in memory. We also notice that they reworked the way they take the proc lock a bit. But we also spot that they fixed it wrongly and reintroduced lightspeed!

Indeed, taking the lock with aio_proc_lock_spin(p) is irrelevant here to prevent the race. At this point the kernel worker may already have raced the main thread and freed the allocation (see our previous blogpost for more details).

light_speed_is_back_meme

 

As a consequence, the exact same vulnerability is back, with the same primitives. You can even test it with our old PoC (but you may need to tweak the parameters). From our tests it seems that the race probability changed a bit since the proc lock is now taken but that is not a big deal.

Conclusion

There is no doubt that lightspeed will be fixed again in the next iOS version. One way to fix that would be to never free the lio_context once I/O were scheduled, while using another variable to keep trace of the submitted tasks to free the context if none were.

Even if this is still a 0-day at the time this blog post is released, we think that it is the right time to publish as an exploit is out. Moreover, the regression was really easy to spot by diffing the sources months ago. So it was merely an open secret and it is likely that most researchers and bad actors already knew about it. The good news is that this vulnerability cannot be exploited from the WebKit renderer process since its sandbox has been considerably tightened since iOS 13 with the introduction of syscall whitelist.

This means that the risk for this vulnerability to be used by malicious actors is relatively low. An attacker would first have to compromise a vulnerable service or application in order to exploit this vulnerability. Nevertheless, you should update your iPhone as soon as a patch is made available by Apple.

As a final note, we would like to congratulate @Pwn20wnd and the unc0ver team for their successful exploit.