CVE-2025-21756
POC and Analysis of CVE-2025-21756 , a refcount mishandling leading to UAF in the VSOCK implementation . Tested on kernel 6.6.75
CVE-2025-21756
demo

credits
i wanna thank hoefler on his amazing article on exploiting this CVE , we will be using 2 techniques he used in his exploit .
starting up
looking up the advisory , we can see the bug is in the VSOCK implementation . It also mentions a UAF .
ressources
this was my first time exploiting a socket bug , let alone a VSOCK one . So i needed to familiarize myself a bit with the VSOCK concepts so i can maybe understand the socket API’s later . Here is a banger article i stumbled upon .
analysis
looking up the patch commit , i personally didnt find the patch very useful at first , unlike the explication and code flow provided in the commit description .
code analysis
i will update this with a diagram later .
primitve
achieving this UAF , here is how we can interact with our dangling pointer .
- through normal VSOCK operations , like
close , connect , bind , recv ..., all of these functions trigger anull pointer dereferancedue to SElinux , before we even interact with our freedskobject (except close , we will use that later) - through
vsock_bind_table, remember our sock is still registered atvsock_bind_table, to be exact , thevsock_unbound_socketsone . CTRL-SHIFT-F the termvsock_bind_tablewe can see it being used only invsock_diag_dump.
We will go for the second approach
We Will also need to cross cache this slab
b* at sk_free
p $rdi
$1 = 0xffff888004419e00
gef> slab-contains 0xffff888004419e00
[+] Wait for memory scan
slab: 0xffffea0000110640
kmem_cache: 0xffffea0000110601
[*] Detected invalid value, continue exploring...
slab: 0xffffea0000110600
kmem_cache: 0xffff88800409cc00
base: 0xffff888004418000
name: RAWv6 size: 0x500 num_pages: 0x4
for the spraying , i did it a bit differently (maybe also simpler :3 ) than hoefler , here is my approach :
- spray half ((cpu_partial + 1) * objs_per_slab ) objects
- create our victim object
- spray the other half , to ensure our victim object is inside a slab where we fully control its objects
- free the 12 objects allocated before and after our victim socket , that way we ensure the slab containing our victim object, has only our victim object allocated
- free one object from each slab (we are supposed to do it
cpu_partialtimes , but when testing and debugging , onlycpu_partial - 3times where needed) - uaf our object . triggering a free tries to put the slab to
partial list, but partial_list is full (length==cpu_partial) , it returns the slab to the page allocator , which is the case
With this , we exactly free one page of order 2 , which can be reclaimed easily later with msg-msg of size 1024-2048 , or spraying a bunch of pipes (not very deterministic because pipe pages are of order 0) . The reclaiming using msg_msg was very straight forward and easy , yet for some reason i kept getting a lot of unexpected behavior when trying the next step which is getting leaks . So i stuck to pipes .
leaks
Now comes hoefler brilliant idea to get leaks using a side-channel , i’m gonna let him explain it on my behalf in his article , you can can scroll to Diag Dump Sidechannel For Fun & Profit
Now i used the same idea , but for the KASLR leak , i didnt brute force the entire first 32 bits, all i did was brute force KASLR entropy which is 3 nibbles (0xfff max iterations at most) knowing that init_net offset is fixed with fg-kaslr disabled .
RIP
after getting the leak , all that is left is get RIP control , again i used his idea so i’m gonna let him explain (again xd)
putting it all together
really learnt a lot from this nday , especially about socket internals , maybe it would have been a better idea to start with an older socket CVE .
If you made it this far , and have a cool CVE i can try to POC , please shoot me up at my discord .
Hope you enjoyed the read .