// For flags

CVE-2022-48644

net/sched: taprio: avoid disabling offload when it was never enabled

Severity Score

"-"
*CVSS v-

Exploit Likelihood

*EPSS

Affected Versions

*CPE

Public Exploits

0
*Multiple Sources

Exploited in Wild

-
*KEV

Decision

Track
*SSVC
Descriptions

In the Linux kernel, the following vulnerability has been resolved:

net/sched: taprio: avoid disabling offload when it was never enabled

In an incredibly strange API design decision, qdisc->destroy() gets
called even if qdisc->init() never succeeded, not exclusively since
commit 87b60cfacf9f ("net_sched: fix error recovery at qdisc creation"),
but apparently also earlier (in the case of qdisc_create_dflt()).

The taprio qdisc does not fully acknowledge this when it attempts full
offload, because it starts off with q->flags = TAPRIO_FLAGS_INVALID in
taprio_init(), then it replaces q->flags with TCA_TAPRIO_ATTR_FLAGS
parsed from netlink (in taprio_change(), tail called from taprio_init()).

But in taprio_destroy(), we call taprio_disable_offload(), and this
determines what to do based on FULL_OFFLOAD_IS_ENABLED(q->flags).

But looking at the implementation of FULL_OFFLOAD_IS_ENABLED()
(a bitwise check of bit 1 in q->flags), it is invalid to call this macro
on q->flags when it contains TAPRIO_FLAGS_INVALID, because that is set
to U32_MAX, and therefore FULL_OFFLOAD_IS_ENABLED() will return true on
an invalid set of flags.

As a result, it is possible to crash the kernel if user space forces an
error between setting q->flags = TAPRIO_FLAGS_INVALID, and the calling
of taprio_enable_offload(). This is because drivers do not expect the
offload to be disabled when it was never enabled.

The error that we force here is to attach taprio as a non-root qdisc,
but instead as child of an mqprio root qdisc:

$ tc qdisc add dev swp0 root handle 1: \n mqprio num_tc 8 map 0 1 2 3 4 5 6 7 \n queues 1@0 1@1 1@2 1@3 1@4 1@5 1@6 1@7 hw 0
$ tc qdisc replace dev swp0 parent 1:1 \n taprio num_tc 8 map 0 1 2 3 4 5 6 7 \n queues 1@0 1@1 1@2 1@3 1@4 1@5 1@6 1@7 base-time 0 \n sched-entry S 0x7f 990000 sched-entry S 0x80 100000 \n flags 0x0 clockid CLOCK_TAI
Unable to handle kernel paging request at virtual address fffffffffffffff8
[fffffffffffffff8] pgd=0000000000000000, p4d=0000000000000000
Internal error: Oops: 96000004 [#1] PREEMPT SMP
Call trace:
taprio_dump+0x27c/0x310
vsc9959_port_setup_tc+0x1f4/0x460
felix_port_setup_tc+0x24/0x3c
dsa_slave_setup_tc+0x54/0x27c
taprio_disable_offload.isra.0+0x58/0xe0
taprio_destroy+0x80/0x104
qdisc_create+0x240/0x470
tc_modify_qdisc+0x1fc/0x6b0
rtnetlink_rcv_msg+0x12c/0x390
netlink_rcv_skb+0x5c/0x130
rtnetlink_rcv+0x1c/0x2c

Fix this by keeping track of the operations we made, and undo the
offload only if we actually did it.

I've added "bool offloaded" inside a 4 byte hole between "int clockid"
and "atomic64_t picos_per_byte". Now the first cache line looks like
below:

$ pahole -C taprio_sched net/sched/sch_taprio.o
struct taprio_sched {
struct Qdisc * * qdiscs; /* 0 8 */
struct Qdisc * root; /* 8 8 */
u32 flags; /* 16 4 */
enum tk_offsets tk_offset; /* 20 4 */
int clockid; /* 24 4 */
bool offloaded; /* 28 1 */

/* XXX 3 bytes hole, try to pack */

atomic64_t picos_per_byte; /* 32 0 */

/* XXX 8 bytes hole, try to pack */

spinlock_t current_entry_lock; /* 40 0 */

/* XXX 8 bytes hole, try to pack */

struct sched_entry * current_entry; /* 48 8 */
struct sched_gate_list * oper_sched; /* 56 8 */
/* --- cacheline 1 boundary (64 bytes) --- */

En el kernel de Linux, se ha resuelto la siguiente vulnerabilidad: net/sched: taprio: evita deshabilitar la descarga cuando nunca estuvo habilitada. En una decisión de diseño de API increíblemente extraña, se llama a qdisc->destroy() incluso si qdisc->init() nunca tuvo éxito, no exclusivamente desde el commit 87b60cfacf9f ("net_sched: corregir recuperación de error en la creación de qdisc"), sino aparentemente también antes (en el caso de qdisc_create_dflt()). La qdisc taprio no reconoce completamente esto cuando intenta una descarga completa, porque comienza con q->flags = TAPRIO_FLAGS_INVALID en taprio_init(), luego reemplaza q->flags con TCA_TAPRIO_ATTR_FLAGS analizado desde netlink (en taprio_change(), cola llamada de taprio_init()). Pero en taprio_destroy(), llamamos a taprio_disable_offload(), y esto determina qué hacer en función de FULL_OFFLOAD_IS_ENABLED(q->flags). Pero al observar la implementación de FULL_OFFLOAD_IS_ENABLED() (una verificación bit a bit del bit 1 en q->flags), no es válido llamar a esta macro en q->flags cuando contiene TAPRIO_FLAGS_INVALID, porque está configurado en U32_MAX y, por lo tanto, FULL_OFFLOAD_IS_ENABLED () devolverá verdadero en un conjunto de indicadores no válido. Como resultado, es posible bloquear el kernel si el espacio del usuario fuerza un error entre configurar q->flags = TAPRIO_FLAGS_INVALID y la llamada de taprio_enable_offload(). Esto se debe a que los conductores no esperan que se deshabilite la descarga cuando nunca estuvo habilitada. El error que forzamos aquí es adjuntar taprio como una qdisc no raíz, sino como hija de una qdisc raíz mqprio: $ tc qdisc add dev swp0 root handle 1: \ mqprio num_tc 8 map 0 1 2 3 4 5 6 7 \ colas 1@0 1@1 1@2 1@3 1@4 1@5 1@6 1@7 hw 0 $ tc qdisc reemplazar dev swp0 padre 1:1 \ taprio num_tc 8 map 0 1 2 3 4 5 6 7 \ colas 1@0 1@1 1@2 1@3 1@4 1@5 1@6 1@7 tiempo base 0 \ entrada programada S 0x7f 990000 entrada programada S 0x80 100000 \ banderas 0x0 clockid CLOCK_TAI No se puede para manejar la solicitud de paginación del kernel en la dirección virtual ffffffffffffffff8 [ffffffffffffffff8] pgd=0000000000000000, p4d=0000000000000000 Error interno: Vaya: 96000004 [#1] Seguimiento de llamada SMP PREEMPT: taprio_dump+0x27c/0x310 vsc9959_port _setup_tc+0x1f4/0x460 felix_port_setup_tc+0x24/0x3c dsa_slave_setup_tc +0x54/0x27c taprio_disable_offload.isra.0+0x58/0xe0 taprio_destroy+0x80/0x104 qdisc_create+0x240/0x470 tc_modify_qdisc+0x1fc/0x6b0 rtnetlink_rcv_msg+0x12c/0x390 x5c/0x130 rtnetlink_rcv+0x1c/0x2c Solucione este problema manteniendo un registro de operaciones que hicimos, y deshacer la descarga solo si realmente lo hicimos. Agregué "bool descargado" dentro de un hueco de 4 bytes entre "int clockid" y "atomic64_t picos_per_byte". Ahora la primera línea de caché se ve así: $ pahole -C taprio_sched net/sched/sch_taprio.o struct taprio_sched { struct Qdisc * * qdiscs; /* 0 8 */ struct Qdisc * raíz; /* 8 8 */ u32 banderas; /* 16 4 */ enum tk_offsets tk_offset; /* 20 4 */ int relojid; /* 24 4 */ bool descargado; /* 28 1 */ /* XXX agujero de 3 bytes, intenta empaquetar */ atomic64_t picos_per_byte; /* 32 0 */ /* XXX agujero de 8 bytes, intenta empaquetar */ spinlock_t current_entry_lock; /* 40 0 */ /* XXX agujero de 8 bytes, intenta empaquetar */ struct sched_entry * current_entry; /* 48 8 */ struct sched_gate_list * oper_sched; /* 56 8 */ /* --- límite de línea de caché 1 (64 bytes) --- */

*Credits: N/A
CVSS Scores
Attack Vector
-
Attack Complexity
-
Privileges Required
-
User Interaction
-
Scope
-
Confidentiality
-
Integrity
-
Availability
-
* Common Vulnerability Scoring System
SSVC
  • Decision:Track
Exploitation
None
Automatable
No
Tech. Impact
Partial
* Organization's Worst-case Scenario
Timeline
  • 2024-02-25 CVE Reserved
  • 2024-04-28 CVE Published
  • 2024-04-29 EPSS Updated
  • 2024-12-19 CVE Updated
  • ---------- Exploited in Wild
  • ---------- KEV Due Date
  • ---------- First Exploit
CWE
CAPEC
Affected Vendors, Products, and Versions
Vendor Product Version Other Status
Vendor Product Version Other Status <-- --> Vendor Product Version Other Status
Linux
Search vendor "Linux"
Linux Kernel
Search vendor "Linux" for product "Linux Kernel"
>= 5.4 < 5.4.215
Search vendor "Linux" for product "Linux Kernel" and version " >= 5.4 < 5.4.215"
en
Affected
Linux
Search vendor "Linux"
Linux Kernel
Search vendor "Linux" for product "Linux Kernel"
>= 5.4 < 5.10.146
Search vendor "Linux" for product "Linux Kernel" and version " >= 5.4 < 5.10.146"
en
Affected
Linux
Search vendor "Linux"
Linux Kernel
Search vendor "Linux" for product "Linux Kernel"
>= 5.4 < 5.15.71
Search vendor "Linux" for product "Linux Kernel" and version " >= 5.4 < 5.15.71"
en
Affected
Linux
Search vendor "Linux"
Linux Kernel
Search vendor "Linux" for product "Linux Kernel"
>= 5.4 < 5.19.12
Search vendor "Linux" for product "Linux Kernel" and version " >= 5.4 < 5.19.12"
en
Affected
Linux
Search vendor "Linux"
Linux Kernel
Search vendor "Linux" for product "Linux Kernel"
>= 5.4 < 6.0
Search vendor "Linux" for product "Linux Kernel" and version " >= 5.4 < 6.0"
en
Affected