Share
## https://sploitus.com/exploit?id=PACKETSTORM:189856
While reviewing a preview patch for https://bugs.chromium.org/p/project-zero/issues/detail?id=2540 , I noticed some issues - most of them minor, but the following two seem like they probably have bigger security impact:
    
    
    ** F.5 **
    After _PmrZombieCleanup() has picked an item from the sZombieList, it
    drops the hPMRZombieListLock temporarily to avoid a deadlock. When the
    hPMRZombieListLock has been retaken, _PmrZombieCleanup() tries to make
    sure that the PMR is still a zombie, but it does that by checking
    PMR_IsZombie(); so if another thread concurrently revives the zombie
    with PMRDequeueZombieAndRef(), maps it on the GPU, unmaps it, and
    zombifies it again, _PmrZombieCleanup() could wrongly assume that the
    PMR stayed a zombie while the lock was dropped, and destroy it
    immediately.
    In other words, the problem is that after retaking the lock,
    _PmrZombieCleanup() doesn't check whether the zombie is still on the
    same list as before. Instead of rechecking PMR_IsZombie(), you might
    want to recheck that
    `dllist_get_next_node(&psCleanupItem->sZombieList) == psNode`.
    It might also be nice to have a comment in _PmrZombieCleanup() describing this.
    
    
    ** F.9 **
    The refactor of the bools in `struct _PMR_` into the flags bitfield
    uiInternalFlags looks racy to me (though I think really the old code
    was theoretically wrong already, the refactor is probably just making
    that worse): Bits in this field are updated with BITMASK_SET() /
    BITMASK_UNSET(), which are non-atomic; and the callers of these
    helpers don't seem to be using any other locking to avoid concurrent
    writes to this field. In theory, this kind of concurrency is
    forbidden; in practice, the consequence is that updates can get lost
    when two threads try to update different parts of the bitfield, like
    so:
    
    thread A: reads old value of uiInternalFlags (0) thread B: reads old
    value of uiInternalFlags (0) thread A: writes observed value ORed with
    PMR_FLAG_INTERNAL_NO_LAYOUT_CHANGE
    thread B: writes observed value ORed with PMR_FLAG_INTERNAL_DEFER_FREE
    
    causing the final value of uiInternalFlags to be
    PMR_FLAG_INTERNAL_DEFER_FREE, with thread A's write having been lost.
    
    There are two ways around this: Either ensure all places that access
    this flags field hold the same lock, or use atomic RMW operations
    (such as set_bit() on Linux, though note that that's a relaxed atomic
    operation and I haven't checked whether any of your uses of these
    flags would require stronger memory ordering).
    
    
    Marked as fixed.
    The fix is public at https://chromium.googlesource.com/chromiumos/third_party/kernel/+/b61103dc9ad5aa2ae3229dfa64066ed9414abe2e%5E%21/ .
    
    F.5 has been addressed by changing the recheck-after-relock in _PmrZombieCleanup() to use `psNode == dllist_get_next_node(&psCleanupItem->sZombieList)` instead of `PMR_IsZombie(psPMR)`.
    
    F.9 has been addressed with the introduction of _IntFlagSet() / _IntFlagClr() / _IntFlagIsSet() for atomic bitops on the ->uiInternalFlags. (It looks like PMRPinPMR/PMRUnpinPMR/PMR_IsUnpinned weren't changed, but at least in ChromeOS, PMRPinPMR/PMRUnpinPMR seem to be dead code.)
    
    
    Related CVE Number: CVE-2024-40670
    
    Credit: Jann Horn