Linux Kernel 6.3.8 is out with an interesting vulnerability fixed in IPv6 code

In early May a vulnerability in the Linux kernel IPv6 routing code was published, although this bug became known to network hardware vendors much earlier and remained under investigation. Now it's officially addressed by the community.

A "new" IPv6 routing protocol, RPL

It's not fair to call it technically new, but in the world of networking protocols the things get developed really slowly. IPv6 started its journey in late 90x and became an official standard only in 2017, while operating system vendors had already advanced with IPv6 implementation in their kernels.

Due to IPv6's high versatility and great (designed) applicability for today omni-connected digital world, one of the areas where it is effectively used is wireless low-range networks. Small IoT devices consuming dozens of miliamps can be installed all over the place to generate a mesh network of sensors. Their constrained computing resources and unreliable networking between them conventional IP stack are hardly appropriate here. This is when the RPL, or Routing Protocol for Low-Power and Lossy Networks, was invented. It was specifically designed to be computationally "cheap", having low memory footprint, and being tolerant to packet loss. Linux kernel added its initial support back in 2020. RPL belongs to a class of so called source routing protocols where the path is stated in advance to dictate further routing devices how to route it. This is opposed to destination routing where the next router is decided on the fly based on routing tables prepared (statically or dynamically) inside routers themselves.

In the heart of RPL there are various algorithms (we won't go into their details) that allow these small devices to construct a network topology they belong to. Each node knows its rank that is proportional to how far away it is distanced from the so-called root node. All nodes constantly exchange special service packets with the neighbors to keep their rank in sync and to discover adjacent nodes and network in general. 

Linux kernel approach to RPL

According to good tradition, Linux kernel developers often implement a feature not from the grounds, but on top of some existing foundation. In our case it's called lightweight tunnels subsystem. From the kernel documentation:

This feature provides an infrastructure to support light weight
tunnels like mpls. There is no netdevice associated with a light
weight tunnel endpoint. Tunnel encapsulation parameters are stored
with light weight tunnel state associated with fib routes.

This feature, is enabled during kernel build time, allows creating different types of tunnels. An enum exists in the kernel API for that purpose :

lwtunnel_encap_types {
    LWTUNNEL_ENCAP_NONE,
    LWTUNNEL_ENCAP_MPLS,
    LWTUNNEL_ENCAP_IP,
    LWTUNNEL_ENCAP_ILA,        IPv6 Identifier Locator Addressing (ILA)
    LWTUNNEL_ENCAP_IP6,
    LWTUNNEL_ENCAP_SEG6,        Segment Routing Header encapsulation
    LWTUNNEL_ENCAP_BPF,
    LWTUNNEL_ENCAP_SEG6_LOCAL,    
    LWTUNNEL_ENCAP_RPL,
    LWTUNNEL_ENCAP_IOAM6,        In-situ Operations, Administration, and Maintenance
    LWTUNNEL_ENCAP_XFRM,
    __LWTUNNEL_ENCAP_MAX,
};

For example, LWTUNNEL_ENCAP_ILA stands for IPv6 Identifier Locator Addressing encapsulation, LWTUNNEL_ENCAP_SEG6 for Segment Routing Header encapsulation, and LWTUNNEL_ENCAP_IOAM6 - for In-situ Operations, Administration, and Maintenance protocol.

Similarly, well-known ip-route2 toolset utilizes the constants above to setup networks, as it's presented in its man page:

ip route { add | del | change | append | replace } ROUTE
...
ROUTE := NODE_SPEC [ INFO_SPEC ]
...
INFO_SPEC := { NH | nhid ID } OPTIONS FLAGS [ nexthop NH ] ...
...
NH := [ encap ENCAP ] [ via [ FAMILY ] ADDRESS ] [ dev STRING ] [ weight NUMBER ] NHFLAGS
...
ENCAP := [ ENCAP_MPLS | ENCAP_IP | ENCAP_BPF | ENCAP_SEG6 | ENCAP_SEG6LOCAL | ENCAP_IOAM6 ]

Beware that your concrete ip-route program may support more or less encapsulation types, depending on the version.

For example, to create an IP-in-IP route, one may use something like this:

ip route add 10.1.1.1/24 encap ip id 200 dst 20.1.1.1 dev vxlan0

Finally note that RPL protocol reserves its own constant, LWTUNNEL_ENCAP_RPL. And for exhaustive explanation, we can go do RFC-6554 called "An IPv6 Routing Header for Source Routes with the Routing Protocol for Low-Power and Lossy Networks (RPL)".

Nature of the bug under CVE-2023-2156

The RPL header is added to existing IPv6 headers and has the following format:

Due to scarce memory available and limited network capacity for low power devices, the algorithm uses compaction mechanism to minimize the size of this header whenever possible. The list of source addresses (each of 128 bit long) can be compressed: CmprI and CmprE are used for that and become non zero. These fields denote the number of prefix octets of Addresses that are shared with the IPv6 Destination Address of the packet carrying this header. Specifically, CmprI compresses Address[1] .. Address[n-1] fields, and CmprE compresses the last item, Address[n]. Another important field is the Segments Left field: it is decremented upon re-routing of the packet to the next hop, and Address list is reduced similarly, one by one. Decompression and re-compression of this list also happens. Once Segments Left approaches zero, it means the packet has reached its final destination. 

The bug was about not enough memory room allocated for the skb structure. It was assumed that a certain fixed amount was enough, but real required memory depends on the number of items in the Address list. Fortunately, the kernel code is able to detect this buffer overrun and panics, thus not allowing a silent exploitation. However, the denial-of-service type of attack is still applicable.

Useful Links