🇰🇷 한글 설명
Linux 커널의 PREEMPT_RT 환경에서 bpf devmap 사용 시 발생하는 레이스 컨디션 취약점입니다. 동일한 CPU 내에서 여러 태스크가 동시에 xdp_dev_bulk_queue에 접근할 수 있어, 메모리 해제 후 재사용(Use-After-Free)이나 데이터 오염이 발생할 수 있습니다.
💡 쉬운 설명
컴퓨터가 한 번에 한 가지 일만 처리해야 하는 구간에서, 작업이 끝나기 전에 다른 작업이 끼어들어 같은 메모리 공간을 건드리는 문제입니다. 이로 인해 시스템이 갑자기 멈추거나 보안상 위험한 상태가 될 수 있습니다.
🇺🇸 원문 (English)
In the Linux kernel, the following vulnerability has been resolved:
bpf: Fix race in devmap on PREEMPT_RT
On PREEMPT_RT kernels, the per-CPU xdp_dev_bulk_queue (bq) can be
accessed concurrently by multiple preemptible tasks on the same CPU.
The original code assumes bq_enqueue() and __dev_flush() run atomically
with respect to each other on the same CPU, relying on
local_bh_disable() to prevent preemption. However, on PREEMPT_RT,
local_bh_disable() only calls migrate_disable() (when
PREEMPT_RT_NEEDS_BH_LOCK is not set) and does not disable
preemption, which allows CFS scheduling to preempt a task during
bq_xmit_all(), enabling another task on the same CPU to enter
bq_enqueue() and operate on the same per-CPU bq concurrently.
This leads to several races:
1. Double-free / use-after-free on bq->q[]: bq_xmit_all() snapshots
cnt = bq->count, then iterates bq->q[0..cnt-1] to transmit frames.
If preempted after the snapshot, a second task can call bq_enqueue()
-> bq_xmit_all() on the same bq, transmitting (and freeing) the
same frames. When the first task resumes, it operates on stale
pointers in bq->q[], causing use-after-free.
2. bq->count and bq->q[] corruption: concurrent bq_enqueue() modifying
bq->count and bq->q[] while bq_xmit_all() is reading them.
3. dev_rx/xdp_prog teardown race: __dev_flush() clears bq->dev_rx and
bq->xdp_prog after bq_xmit_all(). If preempted between
bq_xmit_all() return and bq->dev_rx = NULL, a preempting
bq_enqueue() sees dev_rx still set (non-NULL), skips adding bq to
the flush_list, and enqueues a frame. When __dev_flush() resumes,
it clears dev_rx and removes bq from the flush_list, orphaning the
newly enqueued frame.
4. __list_del_clearprev() on flush_node: similar to the cpumap race,
both tasks can call __list_del_clearprev() on the same flush_node,
the second dereferences the prev pointer already set to NULL.
The race between task A (__dev_flush -> bq_xmit_all) and task B
(bq_enqueue -> bq_xmit_all) on the same CPU:
Task A (xdp_do_flush) Task B (ndo_xdp_xmit redirect)
---------------------- --------------------------------
__dev_flush(flush_list)
bq_xmit_all(bq)
cnt = bq->count /* e.g. 16 */
/* start iterating bq->q[] */
<-- CFS preempts Task A -->
bq_enqueue(dev, xdpf)
bq->count == DEV_MAP_BULK_SIZE
bq_xmit_all(bq, 0)
cnt = bq->count /* same 16! */
ndo_xdp_xmit(bq->q[])
/* frames freed by driver */
bq->count = 0
<-- Task A resumes -->
ndo_xdp_xmit(bq->q[])
/* use-after-free: frames already freed! */
Fix this by adding a local_lock_t to xdp_dev_bulk_queue and acquiring
it in bq_enqueue() and __dev_flush(). These paths already run under
local_bh_disable(), so use local_lock_nested_bh() which on non-RT is
a pure annotation with no overhead, and on PREEMPT_RT provides a
per-CPU sleeping lock that serializes access to the bq.
📍 영향받는 시스템
PREEMPT_RT 설정이 활성화된 Linux 커널 (bpf devmap 사용 시)
⚔️ 공격 벡터
동일한 CPU에서 여러 태스크가 동시에 bpf devmap의 enqueue 및 flush 작업을 수행하도록 유도하여 메모리 오류 유발
🛡️ 대응 방법
커널 패치를 통해 xdp_dev_bulk_queue에 per-CPU 잠금(local_lock_t)을 도입하여 접근을 직렬화해야 함
🔧 검증 코드
출력 결과에 'PREEMPT_RT'가 포함되어 있다면 해당 커널 환경에서 취약점이 발생할 가능성이 있습니다.
uname -a | grep -i 'PREEMPT_RT'