xref: /freebsd/lib/libc/tests/db/db_hash_tamper_test.c (revision b774fbdb0077571dd34ca55ecb87199895ff47d3)
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
create_db(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
read_hdr(HASHHDR * hdr)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
write_hdr(HASHHDR * hdr)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);
ATF_TC_HEAD(db_hash_ovflw_point_test,tc)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 
ATF_TC_BODY(db_hash_ovflw_point_test,tc)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);
ATF_TC_HEAD(db_hash_bpages_test,tc)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 
ATF_TC_BODY(db_hash_bpages_test,tc)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);
ATF_TC_HEAD(db_hash_bsize_test,tc)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 
ATF_TC_BODY(db_hash_bsize_test,tc)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);
ATF_TC_HEAD(db_hash_masks_test,tc)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 
ATF_TC_BODY(db_hash_masks_test,tc)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);
ATF_TC_HEAD(db_hash_call_hash_oob_test,tc)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 
ATF_TC_BODY(db_hash_call_hash_oob_test,tc)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 
ATF_TP_ADD_TCS(tp)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