The bug was introduced in Linux v3.17 by this commit back in 2014. It requires User Namespaces
to trigger. This bug is very similar to CVE-2021-3715, which was caused by improper operation on the route4_filter
‘s linked list. More details of CVE-2021-3715 could be found at the blackhat talk (page 16). The following are some brief details of CVE-2022-2588.
The following shows some important code snippets of function route4_change
for understanding CVE-2022-2588.
static int route4_change(...) { ... f = kzalloc(sizeof(struct route4_filter), GFP_KERNEL); [0] ... // if there exists a filter with the same handler, copy some information if (fold) { [1] f->id = fold->id; f->iif = fold->iif; f->res = fold->res; f->handle = fold->handle; f->tp = fold->tp; f->bkt = fold->bkt; new = false; } // initialize the new filter err = route4_set_parms(net, tp, base, f, handle, head, tb, [2] tca[TCA_RATE], new, flags, stack); if (err < 0) goto about; // insert the new filter into the list h = from_hash(f->handle >> 16); [3] fp = &f->bkt->ht[h]; for (pfp = rtnl_dereference(*fp); (f1 = rtnl_dereference(*fp)) != NULL; fp = &f1->next) if (f->handle < f1->handle) break; tcf_block_netif_keep_dst(tp-> chain->block); rcu_assign_pointer(f->next, f1); rcu_assign_pointer(*fp, f); // remove fold filter from the list if fold exists if (fold && fold->handle && f->handle != fold->handle) { [4] th = to_hash(fold->handle); h = from_hash(fold->handle >> 16); b = rtnl_dereference(head->table[th]); if (b) { fp = &b->ht[h]; for (pfp = rtnl_dereference(*fp); pp; fp = &pfp->next, pfp = rtnl_dereference(*fp)) { if (pfp == fold) { rcu_assign_pointer(*fp, fold->next); [5]// remove the old from the linked list break; } } } } ... // free the fold filter if it exists [6] if (fold) { tcf_unbind_filter(tp, &fold->res); tcf_exts_get_net(&fold->exts); tcf_queue_work(&fold->rwork, route4_delete_filter_work); }
The function is implemented to initialize/replace the route4_filter
object. The filter uses the handle
as a unique id to distinguish between each filter. If there exists a handle that has been initialized before (i.e. the fold
variable is not null), it will update the filter by removing the old filter and adding a new filter, otherwise, it will just add a new filter.
In [0], the kernel allocates the route4_filter
object. In [1], if the fold is not empty, which means there exists a filter with the same handle, it will copy some information to the new filter and initialize the new filter in [2], then insert the new filter into the list in [3]. If the old filter exists, it gets removed from the list in [4] and gets freed in [6].
The bug happens in [4], which checks whether there exists an old filter to be removed. The condition ensures that the handle shouldn’t be zero and it should match with the new filter’s handle. This condition doesn’t align with the condition of freeing the filter in [6], which only checks if the old filter exists. Therefore, if users create a filter whose handle is 0, and then trigger the replacement of it, the filter will not be unlinked in [4] but gets freed in [6] since their condition isn’t the same.
The exploitation
Since this bug is similar to CVE-2021-3715, their primitives are nearly the same. Readers could refer to the blackhat talk for a more detailed description of primitives. This write-up shows the exploitation of the idea of DirtyCred.
Since the freed fold
is still on the linked list after triggering the bug, we could free the fold
once again, which eventually will cause a double free on the route4_filer
object and route4_filter->exts. action
object if CONFIG_NET_CLS_ACT
is enabled.
The exploit codes utilize those two double-free capabilities to demonstrate the attack on task credentials (utilizing kmalloc-192 double free, to be coming) and open file credentials (utilizing kmalloc-256 double free).
Attacking file credential
Following the idea of DirtyCred, the exploit code swaps the file credential after the permission checks, so we could write any content to files with reading permission. Ideally, the code could work across all kernel versions affected by the bug. It is noted that to make sure the code will work on older kernels where msg_msg
is isolated in malloc-rcl-*
, the exploit uses a different spray object.
The bug is fixed in Linux v5.19 by this commit.
GIPHY App Key not set. Please check settings