git: 14598537acec - main - db/hash.c: Allow O_WRONLY in dbm_open

From: Bojan Novković <bnovkov_at_FreeBSD.org>
Date: Wed, 30 Jul 2025 09:31:56 UTC
The branch main has been updated by bnovkov:

URL: https://cgit.FreeBSD.org/src/commit/?id=14598537acecb512432e33e001762c912cc25799

commit 14598537acecb512432e33e001762c912cc25799
Author:     Bojan Novković <bnovkov@FreeBSD.org>
AuthorDate: 2025-07-25 09:01:52 +0000
Commit:     Bojan Novković <bnovkov@FreeBSD.org>
CommitDate: 2025-07-30 09:31:16 +0000

    db/hash.c: Allow O_WRONLY in dbm_open
    
    The dbm(3) manpage explicitly states that O_WRONLY is not allowed in
    dbm_open, but a more recent comment in ` __hash_open` suggests otherwise.
    Furthermore, POSIX.1 allows O_WRONLY in dbm_open and states
    that the underlying file must be opened for both reading and writing.
    
    Fix this by correcting the O_WRONLY check and moving it further into
    the function to make sure that the original flags are stored in hashp.
    
    Sponsored by:   Klara, Inc.
    Reviewed by:    markj
    Differential Revision:  https://reviews.freebsd.org/D51514
---
 lib/libc/db/hash/hash.c           | 15 +++---
 lib/libc/db/man/dbm.3             |  5 +-
 lib/libc/db/man/dbopen.3          |  5 +-
 lib/libc/tests/db/Makefile        |  1 +
 lib/libc/tests/db/dbm_open_test.c | 23 ++++++---
 lib/libc/tests/db/dbm_perm_test.c | 98 +++++++++++++++++++++++++++++++++++++++
 6 files changed, 126 insertions(+), 21 deletions(-)

diff --git a/lib/libc/db/hash/hash.c b/lib/libc/db/hash/hash.c
index cc96fb5ce326..1eb01ee0f0c5 100644
--- a/lib/libc/db/hash/hash.c
+++ b/lib/libc/db/hash/hash.c
@@ -99,11 +99,6 @@ __hash_open(const char *file, int flags, int mode,
 	DB *dbp;
 	int bpages, hdrsize, new_table, nsegs, save_errno;
 
-	if ((flags & O_ACCMODE) == O_WRONLY) {
-		errno = EINVAL;
-		return (NULL);
-	}
-
 	if (!(hashp = (HTAB *)calloc(1, sizeof(HTAB))))
 		return (NULL);
 	hashp->fp = -1;
@@ -115,6 +110,10 @@ __hash_open(const char *file, int flags, int mode,
 	 * we can check accesses.
 	 */
 	hashp->flags = flags;
+	if ((flags & O_ACCMODE) == O_WRONLY) {
+		flags &= ~O_WRONLY;
+		flags |= O_RDWR;
+	}
 
 	if (file) {
 		if ((hashp->fp = _open(file, flags | O_CLOEXEC, mode)) == -1)
@@ -180,7 +179,7 @@ __hash_open(const char *file, int flags, int mode,
 		__buf_init(hashp, DEF_BUFSIZE);
 
 	hashp->new_file = new_table;
-	hashp->save_file = file && (hashp->flags & O_RDWR);
+	hashp->save_file = file && (flags & O_RDWR);
 	hashp->cbucket = -1;
 	if (!(dbp = (DB *)malloc(sizeof(DB)))) {
 		save_errno = errno;
@@ -524,6 +523,10 @@ hash_get(const DB *dbp, const DBT *key, DBT *data, u_int32_t flag)
 		hashp->error = errno = EINVAL;
 		return (ERROR);
 	}
+	if ((hashp->flags & O_ACCMODE) == O_WRONLY) {
+		hashp->error = errno = EPERM;
+		return (ERROR);
+	}
 	return (hash_access(hashp, HASH_GET, (DBT *)key, data));
 }
 
diff --git a/lib/libc/db/man/dbm.3 b/lib/libc/db/man/dbm.3
index c5a83c7acef4..30787600ad2d 100644
--- a/lib/libc/db/man/dbm.3
+++ b/lib/libc/db/man/dbm.3
@@ -13,7 +13,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd April 2, 2022
+.Dd July 25, 2025
 .Dt DBM 3
 .Os
 .Sh NAME
@@ -99,9 +99,6 @@ is a typical value for
 .Li 0660
 is a typical value for
 .Fa mode .
-.Dv O_WRONLY
-is not allowed in
-.Fa flags .
 The pointer returned by
 .Fn dbm_open
 identifies the database and is the
diff --git a/lib/libc/db/man/dbopen.3 b/lib/libc/db/man/dbopen.3
index 64cef88506d8..7fe515f17849 100644
--- a/lib/libc/db/man/dbopen.3
+++ b/lib/libc/db/man/dbopen.3
@@ -76,13 +76,10 @@ are as specified to the
 .Xr open 2
 routine, however, only the
 .Dv O_CREAT , O_EXCL , O_EXLOCK , O_NOFOLLOW , O_NONBLOCK ,
-.Dv O_RDONLY , O_RDWR , O_SHLOCK , O_SYNC
+.Dv O_RDONLY , O_RDWR , O_SHLOCK , O_SYNC, O_WRONLY,
 and
 .Dv O_TRUNC
 flags are meaningful.
-(Note, opening a database file
-.Dv O_WRONLY
-is not possible.)
 .\"Three additional options may be specified by
 .\".Em or Ns 'ing
 .\"them into the
diff --git a/lib/libc/tests/db/Makefile b/lib/libc/tests/db/Makefile
index 54b38b94a581..cc181cc81160 100644
--- a/lib/libc/tests/db/Makefile
+++ b/lib/libc/tests/db/Makefile
@@ -8,6 +8,7 @@ PROGS+=		h_lfsr
 ${PACKAGE}FILES+=		README
 
 ATF_TESTS_C+=		dbm_open_test
+ATF_TESTS_C+=		dbm_perm_test
 
 NETBSD_ATF_TESTS_C+=	db_hash_seq_test
 NETBSD_ATF_TESTS_SH+=	db_test
diff --git a/lib/libc/tests/db/dbm_open_test.c b/lib/libc/tests/db/dbm_open_test.c
index 18d398e16b2a..8a3e888bf72c 100644
--- a/lib/libc/tests/db/dbm_open_test.c
+++ b/lib/libc/tests/db/dbm_open_test.c
@@ -4,14 +4,15 @@
  * SPDX-License-Identifier: BSD-2-Clause
  */
 
-#include <sys/mman.h>
-
 #include <fcntl.h>
 #include <ndbm.h>
 #include <stdio.h>
 
 #include <atf-c.h>
 
+static const char *path = "tmp";
+static const char *dbname = "tmp.db";
+
 ATF_TC(dbm_open_missing_test);
 ATF_TC_HEAD(dbm_open_missing_test, tc)
 {
@@ -21,23 +22,31 @@ ATF_TC_HEAD(dbm_open_missing_test, tc)
 
 ATF_TC_BODY(dbm_open_missing_test, tc)
 {
-	const char *path = "tmp";
-	const char *dbname = "tmp.db";
 
 	/*
 	 * POSIX.1 specifies that a missing database file should
 	 * always get created if O_CREAT is present, except when
 	 * O_EXCL is specified as well.
 	 */
-	ATF_CHECK(dbm_open(path, O_RDONLY, _PROT_ALL) == NULL);
+	ATF_CHECK(dbm_open(path, O_RDONLY, 0755) == NULL);
+	ATF_REQUIRE(!atf_utils_file_exists(dbname));
+	ATF_CHECK(dbm_open(path, O_RDONLY | O_CREAT, 0755) != NULL);
+	ATF_REQUIRE(atf_utils_file_exists(dbname));
+	ATF_CHECK(dbm_open(path, O_RDONLY | O_CREAT | O_EXCL, 0755) == NULL);
+}
+
+ATF_TC_WITHOUT_HEAD(dbm_open_wronly_test);
+ATF_TC_BODY(dbm_open_wronly_test, tc)
+{
+	ATF_CHECK(dbm_open(path, O_WRONLY, 0755) == NULL);
 	ATF_REQUIRE(!atf_utils_file_exists(dbname));
-	ATF_CHECK(dbm_open(path, O_RDONLY | O_CREAT, _PROT_ALL) != NULL);
+	ATF_CHECK(dbm_open(path, O_WRONLY | O_CREAT, 0755) != NULL);
 	ATF_REQUIRE(atf_utils_file_exists(dbname));
-	ATF_CHECK(dbm_open(path, O_RDONLY | O_CREAT | O_EXCL, _PROT_ALL) == NULL);
 }
 
 ATF_TP_ADD_TCS(tp)
 {
 	ATF_TP_ADD_TC(tp, dbm_open_missing_test);
+	ATF_TP_ADD_TC(tp, dbm_open_wronly_test);
 	return (atf_no_error());
 }
diff --git a/lib/libc/tests/db/dbm_perm_test.c b/lib/libc/tests/db/dbm_perm_test.c
new file mode 100644
index 000000000000..c07210292014
--- /dev/null
+++ b/lib/libc/tests/db/dbm_perm_test.c
@@ -0,0 +1,98 @@
+/*-
+ * Copyright (c) 2025 Klara, Inc.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <ndbm.h>
+#include <stdio.h>
+
+#include <atf-c.h>
+
+static const char *path = "tmp";
+static const char *dbname = "tmp.db";
+
+static void
+create_db(void)
+{
+	DB *db;
+	datum data, key;
+
+	data.dptr = "bar";
+	data.dsize = strlen("bar");
+	key.dptr = "foo";
+	key.dsize = strlen("foo");
+
+	db = dbm_open(path, O_RDWR | O_CREAT, 0755);
+	ATF_CHECK(db != NULL);
+	ATF_REQUIRE(atf_utils_file_exists(dbname));
+	ATF_REQUIRE(dbm_store(db, key, data, DBM_INSERT) != -1);
+	dbm_close(db);
+}
+
+ATF_TC_WITHOUT_HEAD(dbm_rdonly_test);
+ATF_TC_BODY(dbm_rdonly_test, tc)
+{
+	DB *db;
+	datum data, key;
+
+	bzero(&data, sizeof(data));
+	key.dptr = "foo";
+	key.dsize = strlen("foo");
+	create_db();
+
+	db = dbm_open(path, O_RDONLY, 0755);
+	data = dbm_fetch(db, key);
+	ATF_REQUIRE(data.dptr != NULL);
+	ATF_REQUIRE(strncmp((const char*)data.dptr, "bar", data.dsize) == 0);
+	ATF_REQUIRE(dbm_store(db, key, data, DBM_REPLACE) == -1);
+	ATF_REQUIRE(errno == EPERM);
+}
+
+ATF_TC_WITHOUT_HEAD(dbm_wronly_test);
+ATF_TC_BODY(dbm_wronly_test, tc)
+{
+	DB *db;
+	datum data, key;
+
+	key.dptr = "foo";
+	key.dsize = strlen("foo");
+	data.dptr = "baz";
+	data.dsize = strlen("baz");
+	create_db();
+
+	db = dbm_open(path, O_WRONLY, 0755);
+	data = dbm_fetch(db, key);
+	ATF_REQUIRE(data.dptr == NULL);
+	ATF_REQUIRE(errno == EPERM);
+	ATF_REQUIRE(dbm_store(db, key, data, DBM_REPLACE) != -1);
+}
+
+ATF_TC_WITHOUT_HEAD(dbm_rdwr_test);
+ATF_TC_BODY(dbm_rdwr_test, tc)
+{
+	DB *db;
+	datum data, key;
+
+	key.dptr = "foo";
+	key.dsize = strlen("foo");
+	create_db();
+
+	db = dbm_open(path, O_RDWR, 0755);
+	data = dbm_fetch(db, key);
+	ATF_REQUIRE(data.dptr != NULL);
+	data.dptr = "baz";
+	data.dsize = strlen("baz");
+	ATF_REQUIRE(dbm_store(db, key, data, DBM_REPLACE) != -1);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+	ATF_TP_ADD_TC(tp, dbm_rdonly_test);
+	ATF_TP_ADD_TC(tp, dbm_wronly_test);
+	ATF_TP_ADD_TC(tp, dbm_rdwr_test);
+
+	return (atf_no_error());
+}