xref: /freebsd/lib/libc/tests/db/db_hash_tamper_test.c (revision b774fbdb0077571dd34ca55ecb87199895ff47d3)
17f5f07b1SBojan Novković /*-
27f5f07b1SBojan Novković  * Copyright (c) 2026. Klara, Inc.
37f5f07b1SBojan Novković  *
47f5f07b1SBojan Novković  * SPDX-License-Identifier: BSD-2-Clause
57f5f07b1SBojan Novković  */
67f5f07b1SBojan Novković 
77f5f07b1SBojan Novković #include <sys/types.h>
87f5f07b1SBojan Novković #include <sys/wait.h>
97f5f07b1SBojan Novković 
107f5f07b1SBojan Novković #include <netinet/in.h>
117f5f07b1SBojan Novković 
127f5f07b1SBojan Novković #include <atf-c.h>
137f5f07b1SBojan Novković #include <db.h>
147f5f07b1SBojan Novković #include <fcntl.h>
157f5f07b1SBojan Novković #include <signal.h>
167f5f07b1SBojan Novković #include <stdint.h>
177f5f07b1SBojan Novković #include <stdio.h>
187f5f07b1SBojan Novković #include <stdlib.h>
197f5f07b1SBojan Novković 
207f5f07b1SBojan Novković /*
217f5f07b1SBojan Novković  * The internal db/hash/hash.h header is needed to
227f5f07b1SBojan Novković  * avoid hardcoding header structure offsets.
237f5f07b1SBojan Novković  */
247f5f07b1SBojan Novković #include "hash.h"
257f5f07b1SBojan Novković 
267f5f07b1SBojan Novković #define SET_HDR_VAR(hdr, field, val) (hdr)->field = htonl((uint32_t)val)
277f5f07b1SBojan Novković #define GET_HDR_VAR(hdr, field)	     ((uint32_t)ntohl((hdr)->field);)
287f5f07b1SBojan Novković 
297f5f07b1SBojan Novković static const char *dbname = "tmp.db";
307f5f07b1SBojan Novković 
317f5f07b1SBojan Novković /* Create a database file with one entry. */
327f5f07b1SBojan Novković static void
create_db(void)337f5f07b1SBojan Novković create_db(void)
347f5f07b1SBojan Novković {
357f5f07b1SBojan Novković 	DB *db;
367f5f07b1SBojan Novković 	DBT key, val;
377f5f07b1SBojan Novković 
387f5f07b1SBojan Novković 	key.data = "foo";
397f5f07b1SBojan Novković 	key.size = strlen("foo");
407f5f07b1SBojan Novković 
417f5f07b1SBojan Novković 	val.data = "bar";
427f5f07b1SBojan Novković 	val.size = strlen("bar");
437f5f07b1SBojan Novković 
447f5f07b1SBojan Novković 	if (atf_utils_file_exists(dbname))
457f5f07b1SBojan Novković 		unlink(dbname);
467f5f07b1SBojan Novković 	db = dbopen(dbname, O_CREAT | O_RDWR | O_TRUNC, 0755, DB_HASH, NULL);
477f5f07b1SBojan Novković 	ATF_CHECK(db != NULL);
487f5f07b1SBojan Novković 	ATF_REQUIRE(atf_utils_file_exists(dbname));
497f5f07b1SBojan Novković 
50*b774fbdbSBojan Novković 	ATF_REQUIRE(db->put(db, &key, &val, 0) == 0);
517f5f07b1SBojan Novković 
527f5f07b1SBojan Novković 	db->close(db);
537f5f07b1SBojan Novković }
547f5f07b1SBojan Novković 
557f5f07b1SBojan Novković static void
read_hdr(HASHHDR * hdr)567f5f07b1SBojan Novković read_hdr(HASHHDR *hdr)
577f5f07b1SBojan Novković {
587f5f07b1SBojan Novković 	int fd;
597f5f07b1SBojan Novković 
607f5f07b1SBojan Novković 	ATF_REQUIRE(atf_utils_file_exists(dbname));
617f5f07b1SBojan Novković 	fd = open(dbname, O_RDONLY);
627f5f07b1SBojan Novković 	ATF_CHECK(fd != -1);
637f5f07b1SBojan Novković 	ATF_CHECK(read(fd, hdr, sizeof(*hdr)) == sizeof(*hdr));
647f5f07b1SBojan Novković 	close(fd);
657f5f07b1SBojan Novković }
667f5f07b1SBojan Novković 
677f5f07b1SBojan Novković static void
write_hdr(HASHHDR * hdr)687f5f07b1SBojan Novković write_hdr(HASHHDR *hdr)
697f5f07b1SBojan Novković {
707f5f07b1SBojan Novković 	int fd;
717f5f07b1SBojan Novković 
727f5f07b1SBojan Novković 	ATF_REQUIRE(atf_utils_file_exists(dbname));
737f5f07b1SBojan Novković 	fd = open(dbname, O_WRONLY);
747f5f07b1SBojan Novković 	ATF_CHECK(fd != -1);
757f5f07b1SBojan Novković 	ATF_CHECK(write(fd, hdr, sizeof(*hdr)) == sizeof(*hdr));
767f5f07b1SBojan Novković 	close(fd);
777f5f07b1SBojan Novković }
787f5f07b1SBojan Novković 
797f5f07b1SBojan Novković ATF_TC(db_hash_ovflw_point_test);
ATF_TC_HEAD(db_hash_ovflw_point_test,tc)807f5f07b1SBojan Novković ATF_TC_HEAD(db_hash_ovflw_point_test, tc)
817f5f07b1SBojan Novković {
827f5f07b1SBojan Novković 	atf_tc_set_md_var(tc, "descr",
837f5f07b1SBojan Novković 	    "Test hash(3) operations with a corrupted 'ovfl_point' header variable.");
847f5f07b1SBojan Novković }
857f5f07b1SBojan Novković 
ATF_TC_BODY(db_hash_ovflw_point_test,tc)867f5f07b1SBojan Novković ATF_TC_BODY(db_hash_ovflw_point_test, tc)
877f5f07b1SBojan Novković {
887f5f07b1SBojan Novković 	HASHHDR hdr;
897f5f07b1SBojan Novković 
907f5f07b1SBojan Novković 	create_db();
917f5f07b1SBojan Novković 
927f5f07b1SBojan Novković 	read_hdr(&hdr);
937f5f07b1SBojan Novković 	/*
947f5f07b1SBojan Novković 	 * An unvalidated 'ovfl_point' variable may trigger
957f5f07b1SBojan Novković 	 * an OOB read from the SPARES field.
967f5f07b1SBojan Novković 	 */
977f5f07b1SBojan Novković 	SET_HDR_VAR(&hdr, ovfl_point, NCACHED + 1);
987f5f07b1SBojan Novković 	write_hdr(&hdr);
997f5f07b1SBojan Novković 
1007f5f07b1SBojan Novković 	ATF_REQUIRE(dbopen(dbname, O_RDONLY, 0755, DB_HASH, NULL) == NULL);
1017f5f07b1SBojan Novković }
1027f5f07b1SBojan Novković 
1037f5f07b1SBojan Novković ATF_TC(db_hash_bpages_test);
ATF_TC_HEAD(db_hash_bpages_test,tc)1047f5f07b1SBojan Novković ATF_TC_HEAD(db_hash_bpages_test, tc)
1057f5f07b1SBojan Novković {
1067f5f07b1SBojan Novković 	atf_tc_set_md_var(tc, "descr",
1077f5f07b1SBojan Novković 	    "Test hash(3) operations with a corrupted 'spares' header variable.");
1087f5f07b1SBojan Novković }
1097f5f07b1SBojan Novković 
ATF_TC_BODY(db_hash_bpages_test,tc)1107f5f07b1SBojan Novković ATF_TC_BODY(db_hash_bpages_test, tc)
1117f5f07b1SBojan Novković {
1127f5f07b1SBojan Novković 	HASHHDR hdr;
1137f5f07b1SBojan Novković 
1147f5f07b1SBojan Novković 	create_db();
1157f5f07b1SBojan Novković 
1167f5f07b1SBojan Novković 	read_hdr(&hdr);
1177f5f07b1SBojan Novković 	/*
1187f5f07b1SBojan Novković 	 * An unvalidated combination of the 'ovfl_point' variable
1197f5f07b1SBojan Novković 	 * and the 'spares' array may be used to manipulate
1207f5f07b1SBojan Novković 	 * a memset in _hash_open.
1217f5f07b1SBojan Novković 	 */
1227f5f07b1SBojan Novković 	SET_HDR_VAR(&hdr, ovfl_point, 0);
1237f5f07b1SBojan Novković 	hdr.spares[0] = htonl(0x10000000UL);
1247f5f07b1SBojan Novković 	write_hdr(&hdr);
1257f5f07b1SBojan Novković 
1267f5f07b1SBojan Novković 	ATF_REQUIRE(dbopen(dbname, O_RDONLY, 0755, DB_HASH, NULL) == NULL);
1277f5f07b1SBojan Novković }
1287f5f07b1SBojan Novković 
1297f5f07b1SBojan Novković ATF_TC(db_hash_bsize_test);
ATF_TC_HEAD(db_hash_bsize_test,tc)1307f5f07b1SBojan Novković ATF_TC_HEAD(db_hash_bsize_test, tc)
1317f5f07b1SBojan Novković {
1327f5f07b1SBojan Novković 	atf_tc_set_md_var(tc, "descr",
1337f5f07b1SBojan Novković 	    "Test hash(3) operations with a corrupted 'bsize' header variable.");
1347f5f07b1SBojan Novković }
1357f5f07b1SBojan Novković 
ATF_TC_BODY(db_hash_bsize_test,tc)1367f5f07b1SBojan Novković ATF_TC_BODY(db_hash_bsize_test, tc)
1377f5f07b1SBojan Novković {
1387f5f07b1SBojan Novković 	HASHHDR hdr;
1397f5f07b1SBojan Novković 
1407f5f07b1SBojan Novković 	create_db();
1417f5f07b1SBojan Novković 
1427f5f07b1SBojan Novković 	read_hdr(&hdr);
1437f5f07b1SBojan Novković 	/*
1447f5f07b1SBojan Novković 	 * An unvalidated 'bsize' variable may be
1457f5f07b1SBojan Novković 	 * used to manipulate a memset in _hash_open.
1467f5f07b1SBojan Novković 	 */
1477f5f07b1SBojan Novković 	SET_HDR_VAR(&hdr, bsize, 0x100000);
1487f5f07b1SBojan Novković 	write_hdr(&hdr);
1497f5f07b1SBojan Novković 
1507f5f07b1SBojan Novković 	ATF_REQUIRE(dbopen(dbname, O_RDONLY, 0755, DB_HASH, NULL) == NULL);
1517f5f07b1SBojan Novković }
1527f5f07b1SBojan Novković 
1537f5f07b1SBojan Novković ATF_TC(db_hash_masks_test);
ATF_TC_HEAD(db_hash_masks_test,tc)1547f5f07b1SBojan Novković ATF_TC_HEAD(db_hash_masks_test, tc)
1557f5f07b1SBojan Novković {
1567f5f07b1SBojan Novković 	atf_tc_set_md_var(tc, "descr",
1577f5f07b1SBojan Novković 	    "Test hash(3) operations with corrupted '{high,low}_mask' header variables.");
1587f5f07b1SBojan Novković }
1597f5f07b1SBojan Novković 
ATF_TC_BODY(db_hash_masks_test,tc)1607f5f07b1SBojan Novković ATF_TC_BODY(db_hash_masks_test, tc)
1617f5f07b1SBojan Novković {
1627f5f07b1SBojan Novković 	HASHHDR hdr;
1637f5f07b1SBojan Novković 
1647f5f07b1SBojan Novković 	/* 'high_mask' must be greater than 'low_mask'. */
1657f5f07b1SBojan Novković 	create_db();
1667f5f07b1SBojan Novković 	read_hdr(&hdr);
1677f5f07b1SBojan Novković 	SET_HDR_VAR(&hdr, high_mask, 0x1);
1687f5f07b1SBojan Novković 	SET_HDR_VAR(&hdr, low_mask, 0xF);
1697f5f07b1SBojan Novković 	write_hdr(&hdr);
1707f5f07b1SBojan Novković 	ATF_REQUIRE(dbopen(dbname, O_RDONLY, 0755, DB_HASH, NULL) == NULL);
1717f5f07b1SBojan Novković 
1727f5f07b1SBojan Novković 	/* 'high_mask' and 'low_mask' must be derived from power-of-2 values. */
1737f5f07b1SBojan Novković 	create_db();
1747f5f07b1SBojan Novković 	read_hdr(&hdr);
1757f5f07b1SBojan Novković 	SET_HDR_VAR(&hdr, high_mask, 0x13);
1767f5f07b1SBojan Novković 	write_hdr(&hdr);
1777f5f07b1SBojan Novković 	ATF_REQUIRE(dbopen(dbname, O_RDONLY, 0755, DB_HASH, NULL) == NULL);
1787f5f07b1SBojan Novković 
1797f5f07b1SBojan Novković 	create_db();
1807f5f07b1SBojan Novković 	read_hdr(&hdr);
1817f5f07b1SBojan Novković 	SET_HDR_VAR(&hdr, high_mask, 0xFF);
1827f5f07b1SBojan Novković 	SET_HDR_VAR(&hdr, low_mask, 0x13);
1837f5f07b1SBojan Novković 	write_hdr(&hdr);
1847f5f07b1SBojan Novković 	ATF_REQUIRE(dbopen(dbname, O_RDONLY, 0755, DB_HASH, NULL) == NULL);
1857f5f07b1SBojan Novković }
1867f5f07b1SBojan Novković 
1877f5f07b1SBojan Novković ATF_TC(db_hash_call_hash_oob_test);
ATF_TC_HEAD(db_hash_call_hash_oob_test,tc)1887f5f07b1SBojan Novković ATF_TC_HEAD(db_hash_call_hash_oob_test, tc)
1897f5f07b1SBojan Novković {
1907f5f07b1SBojan Novković 	atf_tc_set_md_var(tc, "descr",
1917f5f07b1SBojan Novković 	    "Attempt to trigger an OOB read with corrupted '{high,low}_mask' header variables.");
1927f5f07b1SBojan Novković }
1937f5f07b1SBojan Novković 
ATF_TC_BODY(db_hash_call_hash_oob_test,tc)1947f5f07b1SBojan Novković ATF_TC_BODY(db_hash_call_hash_oob_test, tc)
1957f5f07b1SBojan Novković {
1967f5f07b1SBojan Novković 	DBT key, val;
1977f5f07b1SBojan Novković 	HASHHDR hdr;
1987f5f07b1SBojan Novković 	DB *db;
1997f5f07b1SBojan Novković 
2007f5f07b1SBojan Novković 	key.data = "foo";
2017f5f07b1SBojan Novković 	key.size = strlen("foo");
2027f5f07b1SBojan Novković 
2037f5f07b1SBojan Novković 	/*
2047f5f07b1SBojan Novković 	 * Invalid values of the '{high,low}_mask' header variables
2057f5f07b1SBojan Novković 	 * will cause __call_hash to return OOB bucket indices.
2067f5f07b1SBojan Novković 	 */
2077f5f07b1SBojan Novković 	create_db();
2087f5f07b1SBojan Novković 	read_hdr(&hdr);
2097f5f07b1SBojan Novković 	SET_HDR_VAR(&hdr, low_mask, 0xFFFF);
2107f5f07b1SBojan Novković 	SET_HDR_VAR(&hdr, high_mask, 0xFFFFF);
2117f5f07b1SBojan Novković 	write_hdr(&hdr);
2127f5f07b1SBojan Novković 	db = dbopen(dbname, O_RDONLY, 0755, DB_HASH, NULL);
2137f5f07b1SBojan Novković 	ATF_REQUIRE(db != NULL);
2147f5f07b1SBojan Novković 	/* Attempt to trigger an OOB read. */
2157f5f07b1SBojan Novković 	ATF_REQUIRE(db->get(db, &key, &val, 0) != 0);
2167f5f07b1SBojan Novković }
2177f5f07b1SBojan Novković 
ATF_TP_ADD_TCS(tp)2187f5f07b1SBojan Novković ATF_TP_ADD_TCS(tp)
2197f5f07b1SBojan Novković {
2207f5f07b1SBojan Novković 	ATF_TP_ADD_TC(tp, db_hash_ovflw_point_test);
2217f5f07b1SBojan Novković 	ATF_TP_ADD_TC(tp, db_hash_bpages_test);
2227f5f07b1SBojan Novković 	ATF_TP_ADD_TC(tp, db_hash_bsize_test);
2237f5f07b1SBojan Novković 	ATF_TP_ADD_TC(tp, db_hash_masks_test);
2247f5f07b1SBojan Novković 	ATF_TP_ADD_TC(tp, db_hash_call_hash_oob_test);
2257f5f07b1SBojan Novković 
2267f5f07b1SBojan Novković 	return (atf_no_error());
2277f5f07b1SBojan Novković }
228