1 /*- 2 * Copyright (c) 2026. Klara, Inc. 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7 #include <sys/types.h> 8 #include <sys/wait.h> 9 10 #include <netinet/in.h> 11 12 #include <atf-c.h> 13 #include <db.h> 14 #include <fcntl.h> 15 #include <signal.h> 16 #include <stdint.h> 17 #include <stdio.h> 18 #include <stdlib.h> 19 20 /* 21 * The internal db/hash/hash.h header is needed to 22 * avoid hardcoding header structure offsets. 23 */ 24 #include "hash.h" 25 26 #define SET_HDR_VAR(hdr, field, val) (hdr)->field = htonl((uint32_t)val) 27 #define GET_HDR_VAR(hdr, field) ((uint32_t)ntohl((hdr)->field);) 28 29 static const char *dbname = "tmp.db"; 30 31 /* Create a database file with one entry. */ 32 static void 33 create_db(void) 34 { 35 DB *db; 36 DBT key, val; 37 38 key.data = "foo"; 39 key.size = strlen("foo"); 40 41 val.data = "bar"; 42 val.size = strlen("bar"); 43 44 if (atf_utils_file_exists(dbname)) 45 unlink(dbname); 46 db = dbopen(dbname, O_CREAT | O_RDWR | O_TRUNC, 0755, DB_HASH, NULL); 47 ATF_CHECK(db != NULL); 48 ATF_REQUIRE(atf_utils_file_exists(dbname)); 49 50 ATF_REQUIRE(db->put(db, &key, &val, 0) == 0); 51 52 db->close(db); 53 } 54 55 static void 56 read_hdr(HASHHDR *hdr) 57 { 58 int fd; 59 60 ATF_REQUIRE(atf_utils_file_exists(dbname)); 61 fd = open(dbname, O_RDONLY); 62 ATF_CHECK(fd != -1); 63 ATF_CHECK(read(fd, hdr, sizeof(*hdr)) == sizeof(*hdr)); 64 close(fd); 65 } 66 67 static void 68 write_hdr(HASHHDR *hdr) 69 { 70 int fd; 71 72 ATF_REQUIRE(atf_utils_file_exists(dbname)); 73 fd = open(dbname, O_WRONLY); 74 ATF_CHECK(fd != -1); 75 ATF_CHECK(write(fd, hdr, sizeof(*hdr)) == sizeof(*hdr)); 76 close(fd); 77 } 78 79 ATF_TC(db_hash_ovflw_point_test); 80 ATF_TC_HEAD(db_hash_ovflw_point_test, tc) 81 { 82 atf_tc_set_md_var(tc, "descr", 83 "Test hash(3) operations with a corrupted 'ovfl_point' header variable."); 84 } 85 86 ATF_TC_BODY(db_hash_ovflw_point_test, tc) 87 { 88 HASHHDR hdr; 89 90 create_db(); 91 92 read_hdr(&hdr); 93 /* 94 * An unvalidated 'ovfl_point' variable may trigger 95 * an OOB read from the SPARES field. 96 */ 97 SET_HDR_VAR(&hdr, ovfl_point, NCACHED + 1); 98 write_hdr(&hdr); 99 100 ATF_REQUIRE(dbopen(dbname, O_RDONLY, 0755, DB_HASH, NULL) == NULL); 101 } 102 103 ATF_TC(db_hash_bpages_test); 104 ATF_TC_HEAD(db_hash_bpages_test, tc) 105 { 106 atf_tc_set_md_var(tc, "descr", 107 "Test hash(3) operations with a corrupted 'spares' header variable."); 108 } 109 110 ATF_TC_BODY(db_hash_bpages_test, tc) 111 { 112 HASHHDR hdr; 113 114 create_db(); 115 116 read_hdr(&hdr); 117 /* 118 * An unvalidated combination of the 'ovfl_point' variable 119 * and the 'spares' array may be used to manipulate 120 * a memset in _hash_open. 121 */ 122 SET_HDR_VAR(&hdr, ovfl_point, 0); 123 hdr.spares[0] = htonl(0x10000000UL); 124 write_hdr(&hdr); 125 126 ATF_REQUIRE(dbopen(dbname, O_RDONLY, 0755, DB_HASH, NULL) == NULL); 127 } 128 129 ATF_TC(db_hash_bsize_test); 130 ATF_TC_HEAD(db_hash_bsize_test, tc) 131 { 132 atf_tc_set_md_var(tc, "descr", 133 "Test hash(3) operations with a corrupted 'bsize' header variable."); 134 } 135 136 ATF_TC_BODY(db_hash_bsize_test, tc) 137 { 138 HASHHDR hdr; 139 140 create_db(); 141 142 read_hdr(&hdr); 143 /* 144 * An unvalidated 'bsize' variable may be 145 * used to manipulate a memset in _hash_open. 146 */ 147 SET_HDR_VAR(&hdr, bsize, 0x100000); 148 write_hdr(&hdr); 149 150 ATF_REQUIRE(dbopen(dbname, O_RDONLY, 0755, DB_HASH, NULL) == NULL); 151 } 152 153 ATF_TC(db_hash_masks_test); 154 ATF_TC_HEAD(db_hash_masks_test, tc) 155 { 156 atf_tc_set_md_var(tc, "descr", 157 "Test hash(3) operations with corrupted '{high,low}_mask' header variables."); 158 } 159 160 ATF_TC_BODY(db_hash_masks_test, tc) 161 { 162 HASHHDR hdr; 163 164 /* 'high_mask' must be greater than 'low_mask'. */ 165 create_db(); 166 read_hdr(&hdr); 167 SET_HDR_VAR(&hdr, high_mask, 0x1); 168 SET_HDR_VAR(&hdr, low_mask, 0xF); 169 write_hdr(&hdr); 170 ATF_REQUIRE(dbopen(dbname, O_RDONLY, 0755, DB_HASH, NULL) == NULL); 171 172 /* 'high_mask' and 'low_mask' must be derived from power-of-2 values. */ 173 create_db(); 174 read_hdr(&hdr); 175 SET_HDR_VAR(&hdr, high_mask, 0x13); 176 write_hdr(&hdr); 177 ATF_REQUIRE(dbopen(dbname, O_RDONLY, 0755, DB_HASH, NULL) == NULL); 178 179 create_db(); 180 read_hdr(&hdr); 181 SET_HDR_VAR(&hdr, high_mask, 0xFF); 182 SET_HDR_VAR(&hdr, low_mask, 0x13); 183 write_hdr(&hdr); 184 ATF_REQUIRE(dbopen(dbname, O_RDONLY, 0755, DB_HASH, NULL) == NULL); 185 } 186 187 ATF_TC(db_hash_call_hash_oob_test); 188 ATF_TC_HEAD(db_hash_call_hash_oob_test, tc) 189 { 190 atf_tc_set_md_var(tc, "descr", 191 "Attempt to trigger an OOB read with corrupted '{high,low}_mask' header variables."); 192 } 193 194 ATF_TC_BODY(db_hash_call_hash_oob_test, tc) 195 { 196 DBT key, val; 197 HASHHDR hdr; 198 DB *db; 199 200 key.data = "foo"; 201 key.size = strlen("foo"); 202 203 /* 204 * Invalid values of the '{high,low}_mask' header variables 205 * will cause __call_hash to return OOB bucket indices. 206 */ 207 create_db(); 208 read_hdr(&hdr); 209 SET_HDR_VAR(&hdr, low_mask, 0xFFFF); 210 SET_HDR_VAR(&hdr, high_mask, 0xFFFFF); 211 write_hdr(&hdr); 212 db = dbopen(dbname, O_RDONLY, 0755, DB_HASH, NULL); 213 ATF_REQUIRE(db != NULL); 214 /* Attempt to trigger an OOB read. */ 215 ATF_REQUIRE(db->get(db, &key, &val, 0) != 0); 216 } 217 218 ATF_TP_ADD_TCS(tp) 219 { 220 ATF_TP_ADD_TC(tp, db_hash_ovflw_point_test); 221 ATF_TP_ADD_TC(tp, db_hash_bpages_test); 222 ATF_TP_ADD_TC(tp, db_hash_bsize_test); 223 ATF_TP_ADD_TC(tp, db_hash_masks_test); 224 ATF_TP_ADD_TC(tp, db_hash_call_hash_oob_test); 225 226 return (atf_no_error()); 227 } 228