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