1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * Copyright (c) 2021 SUSE LLC <rpalethorpe@suse.com> 4 * Based on reproducer by Nicolai Stange based on PoC Andy Nguyen 5 */ 6/*\ 7 * [Description] 8 * 9 * This will reproduce the bug on x86_64 in 32bit compatibility 10 * mode. It is most reliable with KASAN enabled. Otherwise it relies 11 * on the out-of-bounds write corrupting something which leads to a 12 * crash. It will run in other scenarious, but is not a test for the 13 * CVE. 14 * 15 * See https://google.github.io/security-research/pocs/linux/cve-2021-22555/writeup.html 16 * 17 * Also below is Nicolai's detailed description of the bug itself. 18 * 19 * The problem underlying CVE-2021-22555 fixed by upstream commit 20 * b29c457a6511 ("netfilter: x_tables: fix compat match/target pad 21 * out-of-bound write") is that the (now removed) padding zeroing code 22 * in xt_compat_target_from_user() had been based on the premise that 23 * the user specified ->u.user.target_size, which will be considered 24 * for the target buffer allocation size, is greater or equal than 25 * what's needed to fit the corresponding xt_target instance's 26 * ->targetsize: if OTOH the user specified ->u.user.target_size is 27 * too small, then the memset() destination address calculated by 28 * adding ->targetsize to the payload start will not point at, but 29 * into or even past the padding. 30 * 31 * For the table's last entry's target record, this will result in an 32 * out-of-bounds write past the destination buffer allocated for the converted 33 * table. The code below will create a (compat) table such that the converted 34 * table's calculated size will fit exactly into a slab size of 1024 bytes and 35 * that the memset() in xt_compat_target_from_user() will write past this slab. 36 * 37 * The table will consist of 38 * 39 * * the mandatory struct compat_ipt_replace header, 40 * * a single entry consisting of 41 * ** the mandatory compat_ipt_entry header 42 * ** a single 'state' match entry of appropriate size for 43 * controlling the out-of-bounds write when converting 44 * the target entry following next, 45 * ** a single 'REJECT' target entry. 46 * 47 * The kernel will transform this into a buffer containing (in 48 * this order) 49 * 50 * * a xt_table_info 51 * * a single entry consisting of 52 * ** its ipt_entry header 53 * ** a single 'state' match entry 54 * ** followed by a single 'REJECT' target entry. 55 * 56 * The expected sizes for the 'state' match entries as well as the 57 * 'REJECT' target are the size of the base header struct (32 bytes) 58 * plus the size of an unsigned int (4 bytes) each. 59 * 60 * In the course of the compat => non-compat conversion, the kernel will insert 61 * four bytes of padding after the unsigned int payload (c.f. 'off' adjustments 62 * via xt_compat_match_offset() and xt_compat_target_offset() in 63 * xt_compat_match_from_user() and xt_compat_target_from_user() resp.). 64 * 65 * This code is based on the premise that the user sets the given 66 * ->u.user.match_size or ->u.user.target_size consistent to the 67 * COMPAT_XT_ALIGN()ed payload size as specified by the corresponding xt_match 68 * instance's ->matchsize or xt_target instance's ->targetsize. 69 * 70 * That is, the padding gets inserted unconditionally during the transformation, 71 * independent of the actual values of ->u.user.match_size or 72 * ->u.user.target_size and the result ends up getting layed out with proper 73 * alignment only if said values match the expectations. 74 * 75 * That's not a problem in itself, but this unconditional insertion of padding 76 * must be taken into account in the match_size calculation below. 77 * 78 * For the match_size calculation below, note that the chosen 79 * target slab size is 1024 and that 80 * 81 * * sizeof(xt_table_info) = 64 82 * * sizeof(ipt_entry) = 112 83 * * the kernel will insert four bytes of padding 84 * after the match and target entries each. 85 * * sizeof(struct xt_entry_target) = 32 86 */ 87 88#include <netinet/in.h> 89 90#include "tst_test.h" 91#include "tst_safe_net.h" 92#include "lapi/ip_tables.h" 93 94static void *buffer; 95 96void setup(void) 97{ 98 if (tst_kernel_bits() == 32 || sizeof(long) > 4) { 99 tst_res(TINFO, 100 "The vulnerability was only present in 32-bit compat mode"); 101 } 102 103 tst_setup_netns(); 104} 105 106void run(void) 107{ 108 const char *const res_fmt_str = 109 "setsockopt(%d, IPPROTO_IP, IPT_SO_SET_REPLACE, %p, 1)"; 110 struct ipt_replace *ipt_replace = buffer; 111 struct ipt_entry *ipt_entry = &ipt_replace->entries[0]; 112 struct xt_entry_match *xt_entry_match = 113 (struct xt_entry_match *)&ipt_entry->elems[0]; 114 const size_t tgt_size = 32; 115 const size_t match_size = 1024 - 64 - 112 - 4 - tgt_size - 4; 116 struct xt_entry_target *xt_entry_tgt = 117 ((struct xt_entry_target *) (&ipt_entry->elems[0] + match_size)); 118 int fd = SAFE_SOCKET(AF_INET, SOCK_DGRAM, 0); 119 int result; 120 121 xt_entry_match->u.user.match_size = (u_int16_t)match_size; 122 strcpy(xt_entry_match->u.user.name, "state"); 123 124 xt_entry_tgt->u.user.target_size = (u_int16_t)tgt_size; 125 strcpy(xt_entry_tgt->u.user.name, "REJECT"); 126 127 ipt_entry->target_offset = 128 (__builtin_offsetof(struct ipt_entry, elems) + match_size); 129 ipt_entry->next_offset = ipt_entry->target_offset + tgt_size; 130 131 strcpy(ipt_replace->name, "filter"); 132 ipt_replace->num_entries = 1; 133 ipt_replace->num_counters = 1; 134 ipt_replace->size = ipt_entry->next_offset; 135 136 TEST(setsockopt(fd, IPPROTO_IP, IPT_SO_SET_REPLACE, buffer, 1)); 137 138 if (TST_RET == -1 && TST_ERR == ENOPROTOOPT) 139 tst_brk(TCONF | TTERRNO, res_fmt_str, fd, buffer); 140 141 result = (TST_RET == -1 && TST_ERR == EINVAL) ? TPASS : TFAIL; 142 tst_res(result | TTERRNO, res_fmt_str, fd, buffer); 143 144 SAFE_CLOSE(fd); 145} 146 147static struct tst_test test = { 148 .setup = setup, 149 .test_all = run, 150 .taint_check = TST_TAINT_W | TST_TAINT_D, 151 .forks_child = 1, 152 .bufs = (struct tst_buffers []) { 153 {&buffer, .size = 2048}, 154 {}, 155 }, 156 .needs_kconfigs = (const char *[]) { 157 "CONFIG_NETFILTER_XT_MATCH_STATE", 158 "CONFIG_IP_NF_TARGET_REJECT", 159 "CONFIG_USER_NS=y", 160 "CONFIG_NET_NS=y", 161 NULL 162 }, 163 .save_restore = (const struct tst_path_val[]) { 164 {"/proc/sys/user/max_user_namespaces", "1024", TST_SR_SKIP}, 165 {} 166 }, 167 .tags = (const struct tst_tag[]) { 168 {"linux-git", "b29c457a6511"}, 169 {"CVE", "2021-22555"}, 170 {} 171 } 172}; 173