CVE-2022-50483
Fecha de publicación:
04/10/2025
*** Pendiente de traducción *** In the Linux kernel, the following vulnerability has been resolved:<br />
<br />
net: enetc: avoid buffer leaks on xdp_do_redirect() failure<br />
<br />
Before enetc_clean_rx_ring_xdp() calls xdp_do_redirect(), each software<br />
BD in the RX ring between index orig_i and i can have one of 2 refcount<br />
values on its page.<br />
<br />
We are the owner of the current buffer that is being processed, so the<br />
refcount will be at least 1.<br />
<br />
If the current owner of the buffer at the diametrically opposed index<br />
in the RX ring (i.o.w, the other half of this page) has not yet called<br />
kfree(), this page&#39;s refcount could even be 2.<br />
<br />
enetc_page_reusable() in enetc_flip_rx_buff() tests for the page<br />
refcount against 1, and [ if it&#39;s 2 ] does not attempt to reuse it.<br />
<br />
But if enetc_flip_rx_buff() is put after the xdp_do_redirect() call,<br />
the page refcount can have one of 3 values. It can also be 0, if there<br />
is no owner of the other page half, and xdp_do_redirect() for this<br />
buffer ran so far that it triggered a flush of the devmap/cpumap bulk<br />
queue, and the consumers of those bulk queues also freed the buffer,<br />
all by the time xdp_do_redirect() returns the execution back to enetc.<br />
<br />
This is the reason why enetc_flip_rx_buff() is called before<br />
xdp_do_redirect(), but there is a big flaw with that reasoning:<br />
enetc_flip_rx_buff() will set rx_swbd->page = NULL on both sides of the<br />
enetc_page_reusable() branch, and if xdp_do_redirect() returns an error,<br />
we call enetc_xdp_free(), which does not deal gracefully with that.<br />
<br />
In fact, what happens is quite special. The page refcounts start as 1.<br />
enetc_flip_rx_buff() figures they&#39;re reusable, transfers these<br />
rx_swbd->page pointers to a different rx_swbd in enetc_reuse_page(), and<br />
bumps the refcount to 2. When xdp_do_redirect() later returns an error,<br />
we call the no-op enetc_xdp_free(), but we still haven&#39;t lost the<br />
reference to that page. A copy of it is still at rx_ring->next_to_alloc,<br />
but that has refcount 2 (and there are no concurrent owners of it in<br />
flight, to drop the refcount). What really kills the system is when<br />
we&#39;ll flip the rx_swbd->page the second time around. With an updated<br />
refcount of 2, the page will not be reusable and we&#39;ll really leak it.<br />
Then enetc_new_page() will have to allocate more pages, which will then<br />
eventually leak again on further errors from xdp_do_redirect().<br />
<br />
The problem, summarized, is that we zeroize rx_swbd->page before we&#39;re<br />
completely done with it, and this makes it impossible for the error path<br />
to do something with it.<br />
<br />
Since the packet is potentially multi-buffer and therefore the<br />
rx_swbd->page is potentially an array, manual passing of the old<br />
pointers between enetc_flip_rx_buff() and enetc_xdp_free() is a bit<br />
difficult.<br />
<br />
For the sake of going with a simple solution, we accept the possibility<br />
of racing with xdp_do_redirect(), and we move the flip procedure to<br />
execute only on the redirect success path. By racing, I mean that the<br />
page may be deemed as not reusable by enetc (having a refcount of 0),<br />
but there will be no leak in that case, either.<br />
<br />
Once we accept that, we have something better to do with buffers on<br />
XDP_REDIRECT failure. Since we haven&#39;t performed half-page flipping yet,<br />
we won&#39;t, either (and this way, we can avoid enetc_xdp_free()<br />
completely, which gives the entire page to the slab allocator).<br />
Instead, we&#39;ll call enetc_xdp_drop(), which will recycle this half of<br />
the buffer back to the RX ring.
Gravedad CVSS v3.1: MEDIA
Última modificación:
23/01/2026