Description
Hello, I work on Starnix which employs the rust-netlink library. We have a fuzzer that helps catch bugs in our kernel implementation, and recently it identified a bug that I've traced down to rust-netlink. Apologies that I don't have a better reproduction environment, but do let me know if it's a barrier and I can draft something up. In any case, here's a sample C program which exemplifies the issue:
#define _GNU_SOURCE
#include <endian.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/socket.h>
int main(void)
{
const uintptr_t BASE_ADDRESS = 0x20000000ul;
const size_t MAP_SIZE = 0x1000000ul;
const uintptr_t POINTER_TO_MSGVEC = BASE_ADDRESS + 0x2d0;
const uintptr_t MSGVEC_COUNT_ADDRESS = BASE_ADDRESS + 0x2d8;
const uintptr_t MSGVEC_ADDRESS = BASE_ADDRESS + 0x280;
const uintptr_t MESSAGE_DATA_ADDRESS = BASE_ADDRESS + 0x300;
const uintptr_t MESSAGE_LENGTH_ADDRESS = BASE_ADDRESS + 0x288;
const uintptr_t MMSGHDR_ADDRESS = BASE_ADDRESS + 0x2c0;
const size_t MESSAGE_DATA_SIZE = 55;
// Allocate executable memory using mmap
syscall(__NR_mmap,
BASE_ADDRESS,
MAP_SIZE,
PROT_WRITE | PROT_READ | PROT_EXEC,
MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE,
/* file descriptor, irrelevant since we're MAP_ANONYMOUS */ -1,
/* offset, also irrelevant */ 0);
// Construct the message vector object for sendmmsg
// struct mmsghdr {
// struct msghdr msg_hdr; /* Message header */
// unsigned int msg_len; /* Number of bytes transmitted */
// };
//
// struct msghdr {
// void *msg_name; /* optional address */
// socklen_t msg_namelen; /* size of address */
// struct iovec *msg_iov; /* scatter/gather array */
// size_t msg_iovlen; /* # elements in msg_iov */
// void *msg_control; /* ancillary data, see below */
// size_t msg_controllen; /* ancillary data buffer len */
// int msg_flags; /* flags on received message */
// };
*(uint64_t *) POINTER_TO_MSGVEC = MSGVEC_ADDRESS;
*(uint64_t *) MSGVEC_COUNT_ADDRESS = 1;
*(uint64_t *) MSGVEC_ADDRESS = MESSAGE_DATA_ADDRESS;
const unsigned char messageData[] = {
0x4c, 0x00, 0x00, 0x00, 0x18, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x85, 0x0a, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x14, 0x00, 0x05, 0x00, 0x20, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1c, 0x00, 0x09, 0x00, 0x08, 0x00, 0x00
};
memcpy((void *) MESSAGE_DATA_ADDRESS,
messageData,
MESSAGE_DATA_SIZE);
// Create a Netlink socket and send the message over said socket.
*(uint64_t *) MESSAGE_LENGTH_ADDRESS = 0x4c;
intptr_t socket_fd = syscall(__NR_socket, AF_NETLINK, SOCK_RAW, 0);
// Print mmsghdr before sendmmsg
struct mmsghdr *mmsghdr_ptr = (struct mmsghdr *) MMSGHDR_ADDRESS;
printf("mmsghdr before sendmmsg:\n");
printf(" msg_hdr.msg_name: %p\n", mmsghdr_ptr->msg_hdr.msg_name);
printf(" msg_hdr.msg_namelen: %u\n", mmsghdr_ptr->msg_hdr.msg_namelen);
printf(" msg_hdr.msg_iov: %p\n", mmsghdr_ptr->msg_hdr.msg_iov);
printf(" msg_hdr.msg_iovlen: %lu\n", mmsghdr_ptr->msg_hdr.msg_iovlen);
printf(" msg_hdr.msg_control: %p\n", mmsghdr_ptr->msg_hdr.msg_control);
printf(" msg_hdr.msg_controllen: %lu\n", mmsghdr_ptr->msg_hdr.msg_controllen);
printf(" msg_hdr.msg_flags: %d\n", mmsghdr_ptr->msg_hdr.msg_flags);
printf(" msg_len: %u\n", mmsghdr_ptr->msg_len);
syscall(__NR_sendmmsg,
socket_fd,
/* mmsghdr, which should be a struct with hdr and len of bytes from hdr to send */ MMSGHDR_ADDRESS,
/* vlen, which is an upper limit on messages sent */ 0x40000000000009ful,
MSG_CONFIRM | /* ARPHRD_TUNNEL */ 0x300);
perror("sendmmsg done!");
return 0;
}
On Linux, this program cleanly completes (including the sendmmsg
invocation). When using the rust-netlink library as the socket implementation for sendmmsg, we see an integer underflow. Our forked code is slightly different from the current source, so I'll skip sharing the exact stacktrace. But the issue lies in the RTA_MULTIPATH implementation, specifically here.'
With the above program, the input payload for RouteAttribute parsing will be:
[8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
In this case, the first packet is successfully deconstructed. We end up with a remaining buffer like so:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
From my reading of the code, the current algorithm interprets the first two bytes as the length, which is zero. This appeases the RouteNextHopBuffer::check_buffer_length()
code, so we attempt to parse the next hop. While performing this parsing, we will attempt to slice out the attributes by taking the length of the input buffer and slicing out the payload offset (source). Since the self-reported length()
is actually zero, we end up with the equation 0 - 8
, which causes the integer underflow panic.
Identifying the correct fix is a little more tricky than slapping in some guards, since I'm not sure how the Netlink spec indicates we should handle this payload. It seems intuitive to me that we should just breeze past the remainder of the data, since the multi packet self-reported claim is a nothing-packet. But I figured it'd be wise to start a thread here to discuss. Any thoughts?