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